├── .github ├── dependabot.yml └── workflows │ ├── rust.yml │ └── security-audit.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── UPGRADING.md ├── assets ├── IGC_A.der ├── IGC_A.pem ├── ca_minimalcrl.der ├── certificate.der ├── certificate.pem ├── crl-ext │ ├── crl-complex.der │ ├── crl-no-crl.der │ └── crl-simple.der ├── crl-idp │ ├── indirect.der │ ├── minimal.der │ ├── only_attribute_certs.der │ ├── only_ca_certs.der │ ├── only_some_reasons.der │ └── only_user_certs.der ├── csr-challenge-password.pem ├── csr-custom-extension.pem ├── csr-empty-attributes.csr ├── duplicate_value_in_authority_info_access.der ├── ed25519.der ├── empty.crl ├── example.crl ├── extension1.der ├── extension2.der ├── gen_minimal_crl.py ├── lets-encrypt-x3-cross-signed.der ├── minimal.crl ├── no_end.pem ├── no_extensions.der ├── no_extensions.pem ├── rsa-pss │ ├── README.md │ ├── self_signed_sha256.der │ ├── self_signed_sha384.der │ └── self_signed_sha512.der ├── test.csr ├── unique_ids.der └── v1.der ├── examples ├── print-cert.rs └── print-crl.rs ├── fuzz ├── Cargo.toml └── fuzz_targets │ ├── certreq.rs │ ├── crl.rs │ ├── x509_parse.rs │ └── x509_with_mutator.rs ├── src ├── certificate.rs ├── certification_request.rs ├── cri_attributes.rs ├── error.rs ├── extensions │ ├── authority_info_access.rs │ ├── authority_key_identifier.rs │ ├── basic_constraints.rs │ ├── certificate_policies.rs │ ├── distribution_point.rs │ ├── extended_key_usage.rs │ ├── generalname.rs │ ├── inhibitant_policy.rs │ ├── issuer_alt_name.rs │ ├── issuing_distribution_point.rs │ ├── key_usage.rs │ ├── mod.rs │ ├── name_constraints.rs │ ├── ns_cert_type.rs │ ├── ns_comment.rs │ ├── policy_constraints.rs │ ├── policy_mappings.rs │ ├── sct.rs │ ├── subject_alt_name.rs │ ├── subject_info_access.rs │ └── subject_key_identifier.rs ├── lib.rs ├── objects.rs ├── parser_utils.rs ├── pem.rs ├── prelude.rs ├── public_key.rs ├── revocation_list.rs ├── signature_algorithm.rs ├── signature_value.rs ├── time.rs ├── validate │ ├── certificate.rs │ ├── extensions.rs │ ├── loggers.rs │ ├── mod.rs │ ├── name.rs │ └── structure.rs ├── verify.rs ├── visitor │ ├── certificate_visitor.rs │ ├── crl_visitor.rs │ └── mod.rs └── x509.rs └── tests ├── pem.rs ├── readcert.rs ├── readcrl.rs ├── readcsr.rs ├── run_all_fuzz_files.rs ├── test01.rs └── verify.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "cargo" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: github-actions 8 | directory: "/" 9 | schedule: 10 | interval: weekly 11 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Continuous integration 2 | 3 | on: 4 | push: 5 | pull_request: 6 | merge_group: 7 | schedule: 8 | - cron: '0 18 * * *' 9 | 10 | jobs: 11 | check: 12 | name: Check 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | rust: 17 | - stable 18 | - 1.67.1 19 | - nightly 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: dtolnay/rust-toolchain@master 23 | with: 24 | toolchain: ${{ matrix.rust }} 25 | - run: RUSTFLAGS="-D warnings" cargo check --locked 26 | 27 | check-notlocked: 28 | name: Check (not locked) 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v4 32 | - uses: dtolnay/rust-toolchain@stable 33 | - name: Cargo update 34 | run: cargo update 35 | - run: RUSTFLAGS="-D warnings" cargo check 36 | 37 | check-all-features: 38 | name: Check All Features 39 | runs-on: ubuntu-latest 40 | strategy: 41 | matrix: 42 | rust: 43 | - stable 44 | - 1.67.1 45 | - nightly 46 | steps: 47 | - uses: actions/checkout@v4 48 | - uses: dtolnay/rust-toolchain@master 49 | with: 50 | toolchain: ${{ matrix.rust }} 51 | - run: RUSTFLAGS="-D warnings" cargo check --locked --all-targets --all-features 52 | 53 | test: 54 | name: Test Suite 55 | needs: check 56 | runs-on: ubuntu-latest 57 | steps: 58 | - uses: actions/checkout@v4 59 | - uses: dtolnay/rust-toolchain@stable 60 | - run: cargo test --locked --all-features 61 | 62 | test_features: 63 | name: Test suite (with features) 64 | needs: check-all-features 65 | runs-on: ubuntu-latest 66 | strategy: 67 | matrix: 68 | features: 69 | - --no-default-features 70 | - --features=default 71 | - --all-features 72 | - --features=verify 73 | - --features=validate 74 | steps: 75 | - uses: actions/checkout@v4 76 | - name: Install stable toolchain 77 | uses: dtolnay/rust-toolchain@stable 78 | - run: cargo test --locked ${{ matrix.features }} 79 | 80 | fmt: 81 | name: Rustfmt 82 | runs-on: ubuntu-latest 83 | steps: 84 | - uses: actions/checkout@v4 85 | - uses: dtolnay/rust-toolchain@stable 86 | with: 87 | components: rustfmt 88 | - run: cargo fmt --all -- --check 89 | 90 | clippy: 91 | name: Clippy 92 | needs: check 93 | runs-on: ubuntu-latest 94 | steps: 95 | - uses: actions/checkout@v4 96 | - uses: dtolnay/rust-toolchain@nightly 97 | with: 98 | components: clippy 99 | - run: cargo clippy --locked --all-features -- -D warnings 100 | 101 | doc: 102 | name: Build documentation 103 | needs: check 104 | runs-on: ubuntu-latest 105 | env: 106 | RUSTDOCFLAGS: --cfg docsrs 107 | steps: 108 | - uses: actions/checkout@v4 109 | - uses: dtolnay/rust-toolchain@nightly 110 | - run: cargo doc --workspace --no-deps --all-features 111 | 112 | semver: 113 | name: Check semver compatibility 114 | runs-on: ubuntu-latest 115 | steps: 116 | - name: Checkout sources 117 | uses: actions/checkout@v4 118 | - name: Check semver 119 | uses: obi1kenobi/cargo-semver-checks-action@v2 120 | -------------------------------------------------------------------------------- /.github/workflows/security-audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | schedule: 4 | - cron: '0 8 * * *' 5 | push: 6 | paths: 7 | - '**/Cargo.toml' 8 | - '**/Cargo.lock' 9 | pull_request: 10 | 11 | jobs: 12 | security_audit: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | - name: Run security audit 18 | uses: rustsec/audit-check@v2.0.0 19 | with: 20 | token: ${{ secrets.GITHUB_TOKEN }} 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | target 3 | /.idea 4 | tags 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "x509-parser" 3 | version = "0.18.0-beta.1" 4 | description = "Parser for the X.509 v3 format (RFC 5280 certificates)" 5 | license = "MIT OR Apache-2.0" 6 | keywords = ["X509","Certificate","parser","nom"] 7 | authors = ["Pierre Chifflier "] 8 | homepage = "https://github.com/rusticata/x509-parser" 9 | repository = "https://github.com/rusticata/x509-parser.git" 10 | categories = ["parser-implementations", "cryptography"] 11 | readme = "README.md" 12 | edition = "2018" 13 | rust-version = "1.67.1" 14 | 15 | include = [ 16 | "CHANGELOG.md", 17 | "LICENSE-*", 18 | "README.md", 19 | ".gitignore", 20 | ".travis.yml", 21 | "Cargo.toml", 22 | "src/*.rs", 23 | "src/extensions/*.rs", 24 | "src/validate/*.rs", 25 | "src/visitor/*.rs", 26 | "tests/*.rs", 27 | "assets/*.crl", 28 | "assets/*.csr", 29 | "assets/*.der", 30 | "assets/*.pem", 31 | "assets/crl-ext/*.der", 32 | "examples/*.rs" 33 | ] 34 | 35 | [package.metadata.docs.rs] 36 | all-features = true 37 | rustdoc-args = ["--cfg", "docsrs"] 38 | 39 | [features] 40 | default = [] 41 | verify = ["ring"] 42 | validate = [] 43 | 44 | [dependencies] 45 | asn1-rs = { version = "0.8.0-beta.1", features=["bigint", "datetime"] } 46 | data-encoding = "2.2.1" 47 | lazy_static = "1.4" 48 | nom = "8.0" 49 | oid-registry = { version="0.9.0-beta.1", features=["crypto", "x509", "x962"] } 50 | rusticata-macros = "5.0" 51 | ring = { version="0.17.12", optional=true } 52 | thiserror = "2.0" 53 | time = { version="0.3.35", features=["formatting"] } 54 | 55 | [dev-dependencies] 56 | hex-literal = "0.4" 57 | 58 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Pierre Chifflier 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE-MIT) 4 | [![Apache License 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](./LICENSE-APACHE) 5 | [![docs.rs](https://docs.rs/x509-parser/badge.svg)](https://docs.rs/x509-parser) 6 | [![crates.io](https://img.shields.io/crates/v/x509-parser.svg)](https://crates.io/crates/x509-parser) 7 | [![Download numbers](https://img.shields.io/crates/d/x509-parser.svg)](https://crates.io/crates/x509-parser) 8 | [![Github CI](https://github.com/rusticata/x509-parser/workflows/Continuous%20integration/badge.svg)](https://github.com/rusticata/x509-parser/actions) 9 | [![Minimum rustc version](https://img.shields.io/badge/rustc-1.67.1+-lightgray.svg)](#rust-version-requirements) 10 | 11 | # X.509 Parser 12 | 13 | A X.509 v3 ([RFC5280]) parser, implemented with the [nom](https://github.com/Geal/nom) 14 | parser combinator framework. 15 | 16 | It is written in pure Rust, fast, and makes extensive use of zero-copy. A lot of care is taken 17 | to ensure security and safety of this crate, including design (recursion limit, defensive 18 | programming), tests, and fuzzing. It also aims to be panic-free. 19 | 20 | The code is available on [Github](https://github.com/rusticata/x509-parser) 21 | and is part of the [Rusticata](https://github.com/rusticata) project. 22 | 23 | Certificates are usually encoded in two main formats: PEM (usually the most common format) or 24 | DER. A PEM-encoded certificate is a container, storing a DER object. See the 25 | [`pem`](https://docs.rs/x509-parser/latest/x509_parser/pem/index.html) module for more documentation. 26 | 27 | To decode a DER-encoded certificate, the main parsing method is 28 | `X509Certificate::from_der` ( 29 | part of the [`FromDer`](https://docs.rs/x509-parser/latest/x509_parser/prelude/trait.FromDer.html) trait 30 | ), which builds a 31 | [`X509Certificate`](https://docs.rs/x509-parser/latest/x509_parser/certificate/struct.X509Certificate.html) object. 32 | 33 | An alternative method is to use [`X509CertificateParser`](https://docs.rs/x509-parser/latest/x509_parser/certificate/struct.X509CertificateParser.html), 34 | which allows specifying parsing options (for example, not automatically parsing option contents). 35 | 36 | The returned objects for parsers follow the definitions of the RFC. This means that accessing 37 | fields is done by accessing struct members recursively. Some helper functions are provided, for 38 | example [`X509Certificate::issuer()`](https://docs.rs/x509-parser/latest/x509_parser/certificate/struct.X509Certificate.html#method.issuer) returns the 39 | same as accessing `.tbs_certificate.issuer`. 40 | 41 | For PEM-encoded certificates, use the [`pem`](https://docs.rs/x509-parser/latest/x509_parser/pem/index.html) module. 42 | 43 | This crate also provides visitor traits: [`X509CertificateVisitor`](crate::visitor::X509CertificateVisitor). 44 | 45 | # Examples 46 | 47 | Parsing a certificate in DER format: 48 | 49 | ```rust 50 | use x509_parser::prelude::*; 51 | 52 | static IGCA_DER: &[u8] = include_bytes!("../assets/IGC_A.der"); 53 | 54 | let res = X509Certificate::from_der(IGCA_DER); 55 | match res { 56 | Ok((rem, cert)) => { 57 | assert!(rem.is_empty()); 58 | // 59 | assert_eq!(cert.version(), X509Version::V3); 60 | }, 61 | _ => panic!("x509 parsing failed: {:?}", res), 62 | } 63 | ``` 64 | 65 | To parse a CRL and print information about revoked certificates: 66 | 67 | ```rust 68 | # 69 | # 70 | let res = CertificateRevocationList::from_der(DER); 71 | match res { 72 | Ok((_rem, crl)) => { 73 | for revoked in crl.iter_revoked_certificates() { 74 | println!("Revoked certificate serial: {}", revoked.raw_serial_as_string()); 75 | println!(" Reason: {}", revoked.reason_code().unwrap_or_default().1); 76 | } 77 | }, 78 | _ => panic!("CRL parsing failed: {:?}", res), 79 | } 80 | ``` 81 | 82 | See also `examples/print-cert.rs`. 83 | 84 | # Features 85 | 86 | - The `verify` feature adds support for (cryptographic) signature verification, based on `ring`. 87 | It adds the 88 | [`X509Certificate::verify_signature()`](https://docs.rs/x509-parser/latest/x509_parser/certificate/struct.X509Certificate.html#method.verify_signature) 89 | to `X509Certificate`. 90 | 91 | ```rust 92 | /// Cryptographic signature verification: returns true if certificate was signed by issuer 93 | #[cfg(feature = "verify")] 94 | pub fn check_signature(cert: &X509Certificate<'_>, issuer: &X509Certificate<'_>) -> bool { 95 | let issuer_public_key = issuer.public_key(); 96 | cert 97 | .verify_signature(Some(issuer_public_key)) 98 | .is_ok() 99 | } 100 | ``` 101 | 102 | - The `validate` features add methods to run more validation functions on the certificate structure 103 | and values using the [`Validate`](https://docs.rs/x509-parser/latest/x509_parser/validate/trait.Validate.html) trait. 104 | It does not validate any cryptographic parameter (see `verify` above). 105 | 106 | ## Rust version requirements 107 | 108 | `x509-parser` requires **Rustc version 1.67.1 or greater**, based on der-parser 109 | dependencies and for proc-macro attributes support. 110 | 111 | [RFC5280]: https://tools.ietf.org/html/rfc5280 112 | 113 | 114 | ## Changes 115 | 116 | See [CHANGELOG.md](CHANGELOG.md) and [`UPGRADING.md`](UPGRADING.md) for instructions for upgrading major versions. 117 | 118 | # License 119 | 120 | Licensed under either of 121 | 122 | * Apache License, Version 2.0 123 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 124 | * MIT license 125 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 126 | 127 | at your option. 128 | 129 | ## Contribution 130 | 131 | Unless you explicitly state otherwise, any contribution intentionally submitted 132 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 133 | dual licensed as above, without any additional terms or conditions. 134 | -------------------------------------------------------------------------------- /UPGRADING.md: -------------------------------------------------------------------------------- 1 | ## Upgrading from 0.17 to 0.18 2 | 3 | The major changes in version 0.18 are described here. 4 | 5 | ### Cargo and dependencies 6 | 7 | Dependencies: 8 | 9 | - `nom` updated to 8.0 `asn1-rs` updated to 0.8 10 | - note that `bigint` is enabled in `asn1-rs` import 11 | - `bitvec` is present by default 12 | - `der-parser` dependency (and re-export) removed 13 | 14 | ### Global API 15 | 16 | - The default parsing trait is now `DerParser`. The parsing function `.parse_der()` now expects an `Input` object, which can be built from bytes using `Input::from`. All X.509 objects and sub-objects will provide this trait 17 | + This improves error handling (`Input` tracks offsets) and helps simplifying code for parsers 18 | - The legacy trait `FromDer` is still provided for compatibility, for top-level objects. 19 | 20 | ### Changed struct fields and methods 21 | 22 | General: 23 | - `UniqueIdentifier` has no lifetime anymore 24 | - Removed constant `MAX_OBJECT_SIZE`. This is not required in this crate since `asn1-rs` takes care of reading valid data. 25 | - Module `utils.rs` has been removed, functions are now part of `x509.rs` 26 | 27 | CSR: 28 | - `X509CertificationRequest` extensions contains a **SET** of values, not a single value 29 | - `csr.requested_extensions()` returns an `Iterator`, not an `Option` 30 | 31 | Extensions: 32 | 33 | - `InhibitAnyPolicy` is now an anonymous struct: 34 | `InhibitAnyPolicy { skip_certs: 2 }` => `InhibitAnyPolicy(2)` 35 | - `SubjectAlternativeName` iteration changed from `&san.general_names` to `san.general_names()` (or `.0`) 36 | 37 | ### Changes in types from `asn1-rs` 38 | 39 | The following changes are not part of this crate, but are exposed in `Any` objects: 40 | 41 | - Any.data() now has type `Input` 42 | - Use `.as_bytes2()` to get `&[u8]` 43 | + Note: recoding `.as_bytes()` or `.as_ref()` may seem useless, but this is necessary to work around problem with lifetimes. 44 | - `BitString` does not have a lifetime parameter anymore 45 | - `bitstring.data` is replaced by `bitstring.as_raw_slice()` 46 | 47 | ### Changes in types from `nom` 48 | 49 | - The nom `Parser` trait has changed (it now uses associated types) 50 | - Add `.parse`, for ex: `length_data(be_u16).parse(i)` 51 | 52 | ### Notes for crate developers 53 | 54 | - Many parsers have been replaced by derive attributes (like `Sequence` or `Choice`) when possible. This reduces risks of errors and makes code more easier to maintain 55 | + Encoders are not derived for now 56 | - File `extensions/mod.rs` has been split in multiple files -------------------------------------------------------------------------------- /assets/IGC_A.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/IGC_A.der -------------------------------------------------------------------------------- /assets/IGC_A.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYT 3 | AkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQ 4 | TS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG 5 | 9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMB4XDTAyMTIxMzE0MjkyM1oXDTIw 6 | MTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAM 7 | BgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEO 8 | MAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2 9 | LmZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaI 10 | s9z4iPf930Pfeo2aSVz2TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2 11 | xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCWSo7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4 12 | u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYyHF2fYPepraX/z9E0+X1b 13 | F8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNdfrGoRpAx 14 | Vs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGd 15 | PDPQtQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNV 16 | HSAEDjAMMAoGCCqBegF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAx 17 | NjAfBgNVHSMEGDAWgBSjBS8YYFDCiQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUF 18 | AAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RKq89toB9RlPhJy3Q2FLwV3duJ 19 | L92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3QMZsyK10XZZOY 20 | YLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg 21 | Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2a 22 | NjSaTFR+FwNIlQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R 23 | 0982gaEbeC9xs/FZTEYYKKuF0mBWWg== 24 | -----END CERTIFICATE----- 25 | -------------------------------------------------------------------------------- /assets/ca_minimalcrl.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/ca_minimalcrl.der -------------------------------------------------------------------------------- /assets/certificate.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/certificate.der -------------------------------------------------------------------------------- /assets/certificate.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFWzCCBEOgAwIBAgISAyBIAwu7NBD5CTxX8suDCMgFMA0GCSqGSIb3DQEBCwUA 3 | MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD 4 | ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xOTA3MTIxMTEyMzBaFw0x 5 | OTEwMTAxMTEyMzBaMB0xGzAZBgNVBAMTEmxpc3RzLmZvci1vdXIuaW5mbzCCASIw 6 | DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVoti34X46DaI2nX24C+aZ2Ofkm 7 | hKbidiXiRTon1MLSMGl1oNW9MyRyYYCzP4j6DNKChJnr8ZnVShh2oZD+yHWP9lpn 8 | XMGkbsUxejRMU9hnaAB50pXRIDAzavkVFCguFlJ8nKkv/Y1Avlw7tc2aZOd3lOZB 9 | Er8gJ8mRDGqqsNU+Z12I6slEstzGMpsq6AewCVw4lMjdWWgugzUrxQTRAsG87on6 10 | gOiQH2cMODN3L7Fq4KOLQIjb3/luQhAQhpdKmEGFLin3c+f5or3thCDuwwDtOU1l 11 | Zf+8t9S8pZPLrZrIs6H2xjXqCRuUY7iRNbO18Ukc6rlDYhBj9LT+cpmBbHECAwEA 12 | AaOCAmYwggJiMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYI 13 | KwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUJj2pvRtl3GloH3He6FX1 14 | ds3X0VEwHwYDVR0jBBgwFoAUqEpqYwR93brm0Tm3pkVl7/Oo7KEwbwYIKwYBBQUH 15 | AQEEYzBhMC4GCCsGAQUFBzABhiJodHRwOi8vb2NzcC5pbnQteDMubGV0c2VuY3J5 16 | cHQub3JnMC8GCCsGAQUFBzAChiNodHRwOi8vY2VydC5pbnQteDMubGV0c2VuY3J5 17 | cHQub3JnLzAdBgNVHREEFjAUghJsaXN0cy5mb3Itb3VyLmluZm8wTAYDVR0gBEUw 18 | QzAIBgZngQwBAgEwNwYLKwYBBAGC3xMBAQEwKDAmBggrBgEFBQcCARYaaHR0cDov 19 | L2Nwcy5sZXRzZW5jcnlwdC5vcmcwggEDBgorBgEEAdZ5AgQCBIH0BIHxAO8AdgAp 20 | PFGWVMg5ZbqqUPxYB9S3b79Yeily3KTDDPTlRUf0eAAAAWvmGV7yAAAEAwBHMEUC 21 | ICQL2Sm14aCMLxX9a9RbySgyBfichMRdbu6QA2Mbrl4eAiEA1vgJ7snqUWCgoqEE 22 | 3SEfK3ioMopzWBsPvG6LdCuCMRAAdQBvU3asMfAxGdiZAKRRFf93FRwR2QLBACkG 23 | jbIImjfZEwAAAWvmGV9oAAAEAwBGMEQCIExGqw3Lo0nSCyUuTRf92FgGASwWYji5 24 | UGnXuYnpJrAvAiBw8AWVag8fzZ4ogAhY9EFRNdLrUcBjStipL888vyuxKzANBgkq 25 | hkiG9w0BAQsFAAOCAQEAF8BBLDvSWZg57B6aDtzfUTSGetCYs3k0vJqCJlL+Pz7/ 26 | UruCSsojQzp5R6jvvgYQ83MaIdwe2mgt+OCQB5v7ylctyBzBmYIw9nPnxEC7HlcJ 27 | L2K/k5ZjJFRnv4kV1Si8+TIpEAV0ksf39KGKemG8kGi4GXV1v03zSv0p8aCarpuo 28 | SKBJ4qlB0CvmS2MqV4KnzO0O2h0c/ZQ4jg7l53eiN7VPdRMMO1DRw+MaW6I/hEZp 29 | +oZQ7hhKXgKUBvF4IGwyrfyIZ8AeWKG4IP98COgyRbz7qtrAVevRKCM0ZC2t04A2 30 | Fcix40FKEeiE093Aj3cweMYxNLPgwgQP8Xu3kA5QEw== 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /assets/crl-ext/crl-complex.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/crl-ext/crl-complex.der -------------------------------------------------------------------------------- /assets/crl-ext/crl-no-crl.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/crl-ext/crl-no-crl.der -------------------------------------------------------------------------------- /assets/crl-ext/crl-simple.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/crl-ext/crl-simple.der -------------------------------------------------------------------------------- /assets/crl-idp/indirect.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/crl-idp/indirect.der -------------------------------------------------------------------------------- /assets/crl-idp/minimal.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/crl-idp/minimal.der -------------------------------------------------------------------------------- /assets/crl-idp/only_attribute_certs.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/crl-idp/only_attribute_certs.der -------------------------------------------------------------------------------- /assets/crl-idp/only_ca_certs.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/crl-idp/only_ca_certs.der -------------------------------------------------------------------------------- /assets/crl-idp/only_some_reasons.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/crl-idp/only_some_reasons.der -------------------------------------------------------------------------------- /assets/crl-idp/only_user_certs.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/crl-idp/only_user_certs.der -------------------------------------------------------------------------------- /assets/csr-challenge-password.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIFuDCCA6ACAQAwgb4xCzAJBgNVBAYTAkdCMR8wHQYDVQQIDBZUZXN0IFN0YXRl 3 | IG9yIFByb3ZpbmNlMRYwFAYDVQQHDA1UZXN0IExvY2FsaXR5MRowGAYDVQQKDBFP 4 | cmdhbml6YXRpb24gTmFtZTEhMB8GA1UECwwYT3JnYW5pemF0aW9uYWwgVW5pdCBO 5 | YW1lMRQwEgYDVQQDDAtDb21tb24gTmFtZTEhMB8GCSqGSIb3DQEJARYSdGVzdEBl 6 | bWFpbC5hZGRyZXNzMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2pTx 7 | B0wQkImcsMaHCO1jYsFRii4JhWJQT326yzI/1FM7pWA/s/kecMOkWVn/KtGDroqE 8 | 9Jw6UQOEsmFpLJEOTzAaGbNMM2BkJBNdhO2Vmmhyjks1sJljTftA+aKu4zpxs0i6 9 | r83oMc6iJ08kMMyDYrKCt4oJjln7dpJXc8OoV9INoRpyU2KhTiqJdw0+4V8lB3BI 10 | QGP6o4mN1Jx/ElJogWyS4CB3wt9I7n1RH08+f/StCs0iAkASKFxAoCLrdIKSk4CM 11 | yUFilaZz6SvHXiLLi+NoLkc+nDaDu4Nu7Pj/e2fYtq8dLRB8A25v5wqQplF7s/ZJ 12 | wiFHVfFJDY8wtdBJ12bgLOPQbqmpBiWVrnBZHGMQTsc5YSMHy6EPv33w16cUWneu 13 | Ho/ryqBwTWww+fCnjOapkSajckmVpm8e4fijhNmq6N6VTpfgOZ3loCjVOp4/EW3M 14 | L+qsYQyM5Y4trY4h0zZh9hZZmQig6vLMIl9n1r+580rLeGsdeyHwHAc6jfwsy4Zz 15 | /jOfbWF3rB1xNgSBHL0po02k8PWFr+uBfKPmfTK8Yvlz3fsTJfzLr6u6y3XAejw/ 16 | ZZjjPydwLn3hL/q+J9SAhA3wxnCu3puwiyjhQQHgUHLkQsYpivdF1a7Pbz+l1D89 17 | cTOwwkYonkReosLI5QiKbQAX2NbSfVqv8ALn09sCAwEAAaCBszAjBgkqhkiG9w0B 18 | CQcxFgwUQSBjaGFsbGVuZ2UgcGFzc3dvcmQwgYsGCSqGSIb3DQEJDjF+MHwwDgYD 19 | VR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNV 20 | HQ4EFgQUkDknz2bwybBXuXEG1RmUpP84GxMwLAYDVR0RBCUwI4cEfwAAAYcQAAAA 21 | AAAAAAAAAAAAAAAAAYIJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4ICAQAll1fo 22 | 6YIKQHINhdj6aKRHKZ/CluFSTAvctga5+JQG8clbWWxjI+RDUZhnw/r5Jr+8OaBW 23 | rm7c/aDePP9AOQ93tBfIEv4OMwP4jQVOrZJuAOuyrUoSTSehMPZoR7IZv2QcyRk+ 24 | RUi2aqZbYLPFptsJ6+8FlX/ieSM+GEBmj2W9U5LsvI2y6B34N0uy/if9IbQO8OHj 25 | afrjZc2jZ1QYj69OUJekEjPZzrb3kSIVELyWfZEocq+e0hLgQEdI0ZCIdzKFeH8i 26 | yQfCtJ7g7k/hVkrQcyd3JwFvhIJRlyhw8ORxOHqFsyZ2XWvg0lGVWk9ksRDGFcf3 27 | g38Erl1sU3jPzEfb/B2aio2DO+42ZY65P7nuLNB5Tatdyy+yYmd3OlxL0XtUb/VW 28 | DS9zKjNrjBLvgN9w5QUE0hc4HOHfcceEcPljALoeZWF/XmPjEUjbiKSIP7vIKDi2 29 | bkx1LiLnuDzjvapG/C6YmuFrTxoqjc9qZs/e2NjrxJ5tI3s/d9R9UET+jh/1swLH 30 | wz35SohLLDLc2ShaNlNuMOwWOySFV93RwjxcP6OsEFyCZrVsQ6UXz6odXk509A5e 31 | w3xhpMdQFlsNRpH0f7CjcxsTUx3qFEXMwq0iYt9w0wrTezOTL25oGouHH11JQsZh 32 | g7iXkOHZIfzZVIF3sagZhdp7stnJTWnUIhJvXw== 33 | -----END CERTIFICATE REQUEST----- 34 | -------------------------------------------------------------------------------- /assets/csr-custom-extension.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIHJMH0CAQAwLjEbMBkGA1UEAwwSY2xpZW50IGNlcnRpZmljYXRlMQ8wDQYDVQQK 3 | DAZteSBvcmcwKjAFBgMrZXADIQC3Gw0vJiIXML8uYH86NEMtmiMuAdWn2WPEZVKu 4 | mbsOTaAcMBoGCSqGSIb3DQEJDjENMAswCQYCKgMEAwECAzAFBgMrZXADQQDPcUvh 5 | yrGGTBlgv4W3//5/h5AkEG2UVa1Bvfbi/UL4tkFYhkc5sAFZ37Igr2tgDNhofu7r 6 | JVo5qjVH6T9QJ8gD 7 | -----END CERTIFICATE REQUEST----- 8 | -------------------------------------------------------------------------------- /assets/csr-empty-attributes.csr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/csr-empty-attributes.csr -------------------------------------------------------------------------------- /assets/duplicate_value_in_authority_info_access.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/duplicate_value_in_authority_info_access.der -------------------------------------------------------------------------------- /assets/ed25519.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/ed25519.der -------------------------------------------------------------------------------- /assets/empty.crl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/empty.crl -------------------------------------------------------------------------------- /assets/example.crl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/example.crl -------------------------------------------------------------------------------- /assets/extension1.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/extension1.der -------------------------------------------------------------------------------- /assets/extension2.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/extension2.der -------------------------------------------------------------------------------- /assets/gen_minimal_crl.py: -------------------------------------------------------------------------------- 1 | """Generates a DER encoded CRL with a single revoked serial and no extensions. 2 | This exercises the optional-parsing functionalitites of `parse_crl_der`. 3 | """ 4 | 5 | import os.path as osp 6 | 7 | from OpenSSL import crypto 8 | 9 | 10 | def main(): 11 | pkey = crypto.PKey() 12 | pkey.generate_key(crypto.TYPE_RSA, 2048) 13 | 14 | ca = crypto.X509() 15 | ca.set_version(2) 16 | ca.set_serial_number(1) 17 | ca.get_subject().CN = 'snakeoil' 18 | ca.set_notBefore(b'19700101000000Z') 19 | ca.set_notAfter(b'20991231235959Z') 20 | ca.set_issuer(ca.get_subject()) 21 | ca.set_pubkey(pkey) 22 | ca.sign(pkey, 'sha256') 23 | 24 | with open(osp.join(osp.dirname(__file__), 'ca_minimalcrl.der'), 'wb') as f_ca: 25 | f_ca.write(crypto.dump_certificate(crypto.FILETYPE_ASN1, ca)) 26 | 27 | revoked = crypto.Revoked() 28 | revoked.set_serial(b'2a') 29 | revoked.set_rev_date(b'19700101000000Z') 30 | revoked.set_reason(None) 31 | 32 | crl = crypto.CRL() 33 | crl.set_lastUpdate(b'19700101000000Z') 34 | crl.set_nextUpdate(b'20990101000000Z') 35 | crl.add_revoked(revoked) 36 | crl.sign(issuer_cert=ca, issuer_key=pkey, digest=b'sha256') 37 | 38 | with open(osp.join(osp.dirname(__file__), 'minimal.crl'), 'wb') as f_crl: 39 | f_crl.write(crypto.dump_crl(crypto.FILETYPE_ASN1, crl)) 40 | 41 | 42 | if __name__ == '__main__': 43 | main() 44 | -------------------------------------------------------------------------------- /assets/lets-encrypt-x3-cross-signed.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/lets-encrypt-x3-cross-signed.der -------------------------------------------------------------------------------- /assets/minimal.crl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/minimal.crl -------------------------------------------------------------------------------- /assets/no_end.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBUTCB2KADAgECAgkAtXGSKEzrTR4wCgYIKoZIzj0EAwIwEDEOMAwGA1UEAwwF 3 | YmVubm8wHhcNMTgxMTEzMDI1NDQwWhcNMTkxMTEzMDI1NDQwWjAQMQ4wDAYDVQQD 4 | DAViZW5ubzB2MBAGByqGSM49AgEGBSuBBAAiA2IABDhrHLVTMHC7GTyB/MNztToW 5 | ss2zlmvR62X1pQaBN6fYhBJE1XYa0V2C1fGGXj92MencOtXyfYVxn+DY07gyT/71 6 | HQ12TJOe90wwjy2/6N1W1jOv5HjphVT8JQlVNqAC+DAKBggqhkjOPQQDAgNoADBl 7 | AjAfzS1tmZ+GSAaXrsPcfAd1A9yfWVtB8tWxFNNo2j7/cL3puf2vQnlwV/0BoZZ1 8 | K4ICMQDaE0HemgYp6RPQzeb96a2gjlaEbwNy1B8O74fE24WRXiOUm4eln9wGQ3I1 9 | iV6NtZU= 10 | -------------------------------------------------------------------------------- /assets/no_extensions.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/no_extensions.der -------------------------------------------------------------------------------- /assets/no_extensions.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBUTCB2KADAgECAgkAtXGSKEzrTR4wCgYIKoZIzj0EAwIwEDEOMAwGA1UEAwwF 3 | YmVubm8wHhcNMTgxMTEzMDI1NDQwWhcNMTkxMTEzMDI1NDQwWjAQMQ4wDAYDVQQD 4 | DAViZW5ubzB2MBAGByqGSM49AgEGBSuBBAAiA2IABDhrHLVTMHC7GTyB/MNztToW 5 | ss2zlmvR62X1pQaBN6fYhBJE1XYa0V2C1fGGXj92MencOtXyfYVxn+DY07gyT/71 6 | HQ12TJOe90wwjy2/6N1W1jOv5HjphVT8JQlVNqAC+DAKBggqhkjOPQQDAgNoADBl 7 | AjAfzS1tmZ+GSAaXrsPcfAd1A9yfWVtB8tWxFNNo2j7/cL3puf2vQnlwV/0BoZZ1 8 | K4ICMQDaE0HemgYp6RPQzeb96a2gjlaEbwNy1B8O74fE24WRXiOUm4eln9wGQ3I1 9 | iV6NtZU= 10 | -----END CERTIFICATE----- 11 | -------------------------------------------------------------------------------- /assets/rsa-pss/README.md: -------------------------------------------------------------------------------- 1 | # Generating Test Certificates 2 | 3 | ```shell 4 | openssl req -new -x509 -newkey rsa:2048 -keyout /dev/null -nodes -sigopt rsa_padding_mode:pss -sha256 -sigopt rsa_pss_saltlen:-1 -outform der -out self_signed_sha256.der -batch 5 | openssl req -new -x509 -newkey rsa:2048 -keyout /dev/null -nodes -sigopt rsa_padding_mode:pss -sha384 -sigopt rsa_pss_saltlen:-1 -outform der -out self_signed_sha384.der -batch 6 | openssl req -new -x509 -newkey rsa:2048 -keyout /dev/null -nodes -sigopt rsa_padding_mode:pss -sha512 -sigopt rsa_pss_saltlen:-1 -outform der -out self_signed_sha512.der -batch 7 | ``` 8 | -------------------------------------------------------------------------------- /assets/rsa-pss/self_signed_sha256.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/rsa-pss/self_signed_sha256.der -------------------------------------------------------------------------------- /assets/rsa-pss/self_signed_sha384.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/rsa-pss/self_signed_sha384.der -------------------------------------------------------------------------------- /assets/rsa-pss/self_signed_sha512.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/rsa-pss/self_signed_sha512.der -------------------------------------------------------------------------------- /assets/test.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIBBjCBrQIBADAcMRowGAYDVQQDDBF0ZXN0LnJ1c3RpY2F0YS5mcjBZMBMGByqG 3 | SM49AgEGCCqGSM49AwEHA0IABMP1frFxwJLXiLU6UoqOPf31ucCm2NqR2yqpcHo6 4 | W7iWJe31OzYs0izP2qeUvdKfz2fpAbuGiRjwvN+H10dQQEGgLzAtBgkqhkiG9w0B 5 | CQ4xIDAeMBwGA1UdEQQVMBOCEXRlc3QucnVzdGljYXRhLmZyMAoGCCqGSM49BAMC 6 | A0gAMEUCIGqQHPHgpeyZa5YMLP2X5IwfmrvpIcg5fQ2xkXotGAa0AiEAydeBwr4r 7 | Iu7XDe015h8uz8xZs2QUEgRdr73lJXTX+Ck= 8 | -----END CERTIFICATE REQUEST----- 9 | -------------------------------------------------------------------------------- /assets/unique_ids.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/unique_ids.der -------------------------------------------------------------------------------- /assets/v1.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rusticata/x509-parser/6f214d37e1fcacf818cde6291b8b4ebc9aa21ebc/assets/v1.der -------------------------------------------------------------------------------- /examples/print-crl.rs: -------------------------------------------------------------------------------- 1 | use asn1_rs::Oid; 2 | use nom::HexDisplay; 3 | use std::cmp::min; 4 | use std::env; 5 | use std::io; 6 | use x509_parser::prelude::*; 7 | 8 | fn print_hex_dump(bytes: &[u8], max_len: usize) { 9 | let m = min(bytes.len(), max_len); 10 | print!("{}", &bytes[..m].to_hex(16)); 11 | if bytes.len() > max_len { 12 | println!("... "); 13 | } 14 | } 15 | 16 | fn format_oid(oid: &Oid) -> String { 17 | match oid2sn(oid, oid_registry()) { 18 | Ok(s) => s.to_owned(), 19 | _ => format!("{oid}"), 20 | } 21 | } 22 | 23 | fn print_authority_key_identifier(aki: &AuthorityKeyIdentifier, level: usize) { 24 | if let Some(id) = &aki.key_identifier { 25 | println!("{:indent$}keyid: {:x}", "", id, indent = level); 26 | } 27 | if aki.authority_cert_issuer.is_some() { 28 | unimplemented!(); 29 | } 30 | if let Some(serial) = &aki.authority_cert_serial { 31 | let s = format_serial(serial.as_raw_slice().unwrap()); 32 | println!("{:indent$}serial: {}", "", &s, indent = level); 33 | } 34 | } 35 | 36 | fn print_x509_extension(oid: &Oid, ext: &X509Extension, level: usize) { 37 | match ext.parsed_extension() { 38 | ParsedExtension::CRLNumber(num) => { 39 | println!("{:indent$}X509v3 CRL Number: {}", "", num, indent = level); 40 | } 41 | ParsedExtension::ReasonCode(code) => { 42 | println!( 43 | "{:indent$}X509v3 CRL Reason Code: {}", 44 | "", 45 | code, 46 | indent = level 47 | ); 48 | } 49 | ParsedExtension::InvalidityDate(date) => { 50 | println!("{:indent$}Invalidity Date: {}", "", date, indent = level); 51 | } 52 | ParsedExtension::AuthorityKeyIdentifier(aki) => { 53 | println!( 54 | "{:indent$}X509v3 Authority Key Identifier:", 55 | "", 56 | indent = level 57 | ); 58 | print_authority_key_identifier(aki, level + 2); 59 | } 60 | x => { 61 | print!("{:indent$}{}:", "", format_oid(oid), indent = level); 62 | print!(" Critical={}", ext.critical); 63 | print!(" len={}", ext.value.len()); 64 | println!(); 65 | println!(" {:indent$}{:?}", "", x, indent = level); 66 | } 67 | } 68 | } 69 | 70 | fn print_x509_digest_algorithm(alg: &AlgorithmIdentifier, level: usize) { 71 | println!( 72 | "{:indent$}Oid: {}", 73 | "", 74 | format_oid(&alg.algorithm), 75 | indent = level 76 | ); 77 | if let Some(parameter) = &alg.parameters { 78 | println!( 79 | "{:indent$}Parameter: {:?}", 80 | "", 81 | parameter.tag(), 82 | indent = level 83 | ); 84 | let bytes = parameter.as_bytes(); 85 | print_hex_dump(bytes, 32); 86 | } else { 87 | println!("{:indent$}Parameter: ", "", indent = level); 88 | } 89 | } 90 | 91 | fn print_revoked_certificate(revoked: &RevokedCertificate, level: usize) { 92 | println!( 93 | "{:indent$}Serial number: {}", 94 | "", 95 | revoked.raw_serial_as_string(), 96 | indent = level 97 | ); 98 | println!( 99 | "{:indent$}Revocation Date: {}", 100 | "", 101 | revoked.revocation_date, 102 | indent = level + 2 103 | ); 104 | println!("{:indent$}CRL Extensions:", "", indent = level + 2); 105 | for ext in revoked.extensions() { 106 | print_x509_extension(&ext.oid, ext, level + 4); 107 | } 108 | } 109 | 110 | fn print_crl_info(crl: &CertificateRevocationList) { 111 | println!(" Version: {}", crl.version().unwrap_or(X509Version(0))); 112 | // println!(" Subject: {}", crl.subject()); 113 | println!(" Signature Algorithm:"); 114 | print_x509_digest_algorithm(&crl.signature_algorithm, 4); 115 | println!(" Issuer: {}", crl.issuer()); 116 | // println!(" Serial: {}", crl.tbs_certificate.raw_serial_as_string()); 117 | println!(" Last Update: {}", crl.last_update()); 118 | println!( 119 | " Next Update: {}", 120 | crl.next_update() 121 | .map_or_else(|| "NONE".to_string(), |d| d.to_string()) 122 | ); 123 | println!("{:indent$}CRL Extensions:", "", indent = 2); 124 | for ext in crl.extensions() { 125 | print_x509_extension(&ext.oid, ext, 4); 126 | } 127 | println!(" Revoked certificates:"); 128 | for revoked in crl.iter_revoked_certificates() { 129 | print_revoked_certificate(revoked, 4); 130 | } 131 | println!(); 132 | } 133 | 134 | pub fn main() -> io::Result<()> { 135 | for file_name in env::args().skip(1) { 136 | // placeholder to store decoded PEM data, if needed 137 | let tmpdata; 138 | 139 | println!("File: {file_name}"); 140 | let data = std::fs::read(file_name.clone()).expect("Unable to read file"); 141 | let der_data: &[u8] = if (data[0], data[1]) == (0x30, 0x82) { 142 | // probably DER 143 | &data 144 | } else { 145 | // try as PEM 146 | let (_, data) = parse_x509_pem(&data).expect("Could not decode the PEM file"); 147 | tmpdata = data; 148 | &tmpdata.contents 149 | }; 150 | let (_, crl) = parse_x509_crl(der_data).expect("Could not decode DER data"); 151 | print_crl_info(&crl); 152 | } 153 | Ok(()) 154 | } 155 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "x509-parser-fuzz" 4 | version = "0.0.1" 5 | authors = ["Automatically generated"] 6 | publish = false 7 | edition = "2018" 8 | 9 | [package.metadata] 10 | cargo-fuzz = true 11 | 12 | [dependencies.nom] 13 | version = "8" 14 | [dependencies.rand] 15 | version = "0.8" 16 | [dependencies.x509-parser] 17 | path = ".." 18 | [dependencies.libfuzzer-sys] 19 | # git = "https://github.com/rust-fuzz/libfuzzer-sys.git" 20 | version = "0.4.0" 21 | 22 | # Prevent this from interfering with workspaces 23 | [workspace] 24 | members = ["."] 25 | 26 | [[bin]] 27 | name = "x509_parse" 28 | path = "fuzz_targets/x509_parse.rs" 29 | 30 | [[bin]] 31 | name = "x509_with_mutator" 32 | path = "fuzz_targets/x509_with_mutator.rs" 33 | test = false 34 | doc = false 35 | 36 | [patch.crates-io] 37 | # der-parser = { path="../../der-parser" } 38 | asn1-rs = { git="https://github.com/rusticata/asn1-rs", branch = "master" } 39 | oid-registry = { git="https://github.com/rusticata/oid-registry", branch="oid-registry-0.9" } 40 | 41 | [[bin]] 42 | name = "certreq" 43 | path = "fuzz_targets/certreq.rs" 44 | test = false 45 | doc = false 46 | 47 | [[bin]] 48 | name = "crl" 49 | path = "fuzz_targets/crl.rs" 50 | test = false 51 | doc = false 52 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/certreq.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | use x509_parser::prelude::*; 4 | 5 | fuzz_target!(|data: &[u8]| { 6 | // fuzzed code goes here 7 | let _ = X509CertificationRequest::from_der(data); 8 | }); 9 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/crl.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | 4 | use x509_parser::prelude::FromDer; 5 | use x509_parser::revocation_list::CertificateRevocationList; 6 | 7 | fuzz_target!(|data: &[u8]| { 8 | let _ = CertificateRevocationList::from_der(data); 9 | }); 10 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/x509_parse.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | 4 | fuzz_target!(|data: &[u8]| { 5 | // fuzzed code goes here 6 | let _ = x509_parser::parse_x509_certificate(data); 7 | }); 8 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/x509_with_mutator.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::{fuzz_mutator, fuzz_target}; 3 | use rand::rngs::ThreadRng; 4 | use rand::seq::SliceRandom; 5 | use rand::Rng; 6 | use std::cell::RefCell; 7 | use std::ops::DerefMut; 8 | 9 | thread_local! { 10 | pub static RNG: RefCell = RefCell::new(rand::thread_rng()); 11 | } 12 | 13 | fuzz_target!(|data: &[u8]| { 14 | // fuzzed code goes here 15 | let _ = x509_parser::parse_x509_certificate(data); 16 | }); 17 | 18 | fuzz_mutator!(|data: &mut [u8], size: usize, max_size: usize, seed: u32| { 19 | const MUTATORS: &[fn(&mut [u8], usize, usize, u32) -> usize] = &[ 20 | mutator_flip_constructed, 21 | mutator_change_type, 22 | mutator_change_length, 23 | ]; 24 | 25 | let mut rng = rand::thread_rng(); 26 | let idx = rng.gen_range(0..MUTATORS.len()); 27 | MUTATORS[idx](data, size, max_size, seed) 28 | }); 29 | 30 | // adapted from https://searchfox.org/mozilla-central/source/security/nss/fuzz/asn1_mutators.cc 31 | // with changes to port to rust, and new mutators (length, etc.) 32 | 33 | fn mutator_flip_constructed(data: &mut [u8], size: usize, _max_size: usize, _seed: u32) -> usize { 34 | // eprintln!("FLIP"); 35 | let items = parse_items(data); 36 | let s = RNG.with(|rng| items.choose(rng.borrow_mut().deref_mut()).unwrap()); 37 | let s: &mut [u8] = unsafe { std::slice::from_raw_parts_mut(s.as_ptr() as *mut _, s.len()) }; 38 | // Flip "constructed" type bit 39 | s[0] ^= 0x20; 40 | size 41 | } 42 | 43 | fn mutator_change_type(data: &mut [u8], size: usize, _max_size: usize, _seed: u32) -> usize { 44 | // eprintln!("CHANGE TYPE"); 45 | let items = parse_items(data); 46 | let s = RNG.with(|rng| items.choose(rng.borrow_mut().deref_mut()).unwrap()); 47 | let s: &mut [u8] = unsafe { std::slice::from_raw_parts_mut(s.as_ptr() as *mut _, s.len()) }; 48 | // Change type to a random int [0..=30] 49 | let ty = RNG.with(|rng| rng.borrow_mut().gen_range(0..=30)); 50 | s[0] = ty; 51 | size 52 | } 53 | 54 | fn mutator_change_length(data: &mut [u8], size: usize, _max_size: usize, _seed: u32) -> usize { 55 | // eprintln!("CHANGE LENGTH"); 56 | let items = parse_items(data); 57 | let s = RNG.with(|rng| items.choose(rng.borrow_mut().deref_mut()).unwrap()); 58 | let s: &mut [u8] = unsafe { std::slice::from_raw_parts_mut(s.as_ptr() as *mut _, s.len()) }; 59 | // Change type to a random int [0..=30] 60 | let rand = RNG.with(|rng| rng.borrow_mut().gen_range(0..=1)); 61 | // check if using short-form length 62 | if s.len() < 2 { 63 | return size; 64 | } 65 | if s[1] & 0x80 == 0 { 66 | match rand { 67 | 0 => s[1] = s[1].wrapping_sub(1), 68 | _ => s[1] = s[1].wrapping_add(1), 69 | } 70 | } 71 | size 72 | } 73 | 74 | fn parse_items(data: &[u8]) -> Vec<&[u8]> { 75 | // eprintln!("PARSE_ITEMS (len: {})", data.len()); 76 | // use nom::HexDisplay; 77 | // let l = std::cmp::min(data.len(), 32); 78 | // eprintln!("{}", data[..l].to_hex(16)); 79 | 80 | let mut v = Vec::new(); 81 | 82 | // The first item is always the whole corpus. 83 | v.push(data); 84 | 85 | // we cannot use iterators here, since we are iterating and modifying v 86 | // this is safe because we call v.len() at each iteration, and we only 87 | // append elements 88 | let mut i = 0; 89 | while i < v.len() { 90 | let mut item = v[i].clone(); 91 | let mut remaining = item.len(); 92 | 93 | // Empty or primitive items have no children. 94 | if remaining == 0 || (0x20 & item[0]) == 0 { 95 | i += 1; 96 | continue; 97 | } 98 | 99 | while remaining > 2 { 100 | // dbg!(remaining); 101 | // dbg!(item); 102 | if item.len() < 2 { 103 | break; 104 | } 105 | let content = parse_item(item); 106 | if !content.is_empty() { 107 | // Record the item 108 | v.push(content); 109 | } else { 110 | break; 111 | } 112 | 113 | // Reduce number of bytes left in current item. 114 | // if remaining < content.len() { 115 | // eprintln!("remaining: {}", remaining); 116 | // eprintln!("content.len: {}", content.len()); 117 | // let l = std::cmp::min(content.len(), 32); 118 | // dbg!(&content[..l]); 119 | // panic!(); 120 | // } 121 | remaining -= std::cmp::min(content.len(), remaining); 122 | 123 | // Skip the item we just parsed 124 | item = &item[content.len()..]; 125 | } 126 | 127 | i += 1; 128 | } 129 | 130 | // eprintln!("#v: {}", v.len()); 131 | // for s in &v { 132 | // eprintln!(" 0x{:x} +{}", s.as_ptr() as usize, s.len()); 133 | // // use nom::HexDisplay; 134 | // let l = std::cmp::min(s.len(), 32); 135 | // eprintln!("{}", s[..l].to_hex(16)); 136 | // } 137 | 138 | // loop { 139 | // // 140 | // } 141 | 142 | v 143 | } 144 | 145 | // ASSERT: data.len() > 2 146 | fn parse_item(data: &[u8]) -> &[u8] { 147 | // Short form. Bit 8 has value "0" and bits 7-1 give the length. 148 | if data[0] & 0x80 == 0 { 149 | let length = std::cmp::min(2 + data[1] as usize, data.len()); 150 | return &data[2..length]; 151 | } 152 | 153 | // Constructed, indefinite length. Read until {0x00, 0x00}. 154 | if data[1] == 0x80 { 155 | let length = data[2..] 156 | .windows(2) 157 | .position(|window| window == &[0, 0]) 158 | .unwrap_or(data.len() - 2); 159 | 160 | return &data[2..2 + length]; 161 | } 162 | 163 | // Long form. Two to 127 octets. Bit 8 of first octet has value "1" 164 | // and bits 7-1 give the number of additional length octets. 165 | let octets = std::cmp::min((data[1] & 0x7f) as usize, data.len() - 2); 166 | 167 | // Handle lengths bigger than 32 bits. 168 | if octets > 4 { 169 | // Ignore any further children, assign remaining length. 170 | return &data[2 + octets..]; 171 | } 172 | 173 | // parse the length 174 | let length = (0..octets).fold(0usize, |acc, b| (acc << 8) | (data[2 + b] as usize)); 175 | 176 | let length = std::cmp::min(2 + octets + length, data.len()); 177 | 178 | &data[2 + octets..length] 179 | } 180 | -------------------------------------------------------------------------------- /src/certification_request.rs: -------------------------------------------------------------------------------- 1 | use crate::cri_attributes::*; 2 | use crate::error::{X509Error, X509Result}; 3 | use crate::extensions::*; 4 | use crate::x509::{ 5 | parse_signature_value, AlgorithmIdentifier, SubjectPublicKeyInfo, X509Name, X509Version, 6 | }; 7 | 8 | #[cfg(feature = "verify")] 9 | use crate::verify::verify_signature; 10 | use asn1_rs::{ 11 | BitString, DerParser, FromDer, Header, Input, Oid, OptTaggedImplicit, Sequence, Tag, Tagged, 12 | }; 13 | use nom::{Err, IResult, Input as _}; 14 | use std::collections::HashMap; 15 | 16 | /// Certification Signing Request (CSR) 17 | /// 18 | ///
 19 | /// CertificationRequest ::= SEQUENCE {
 20 | ///     certificationRequestInfo CertificationRequestInfo,
 21 | ///     signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }},
 22 | ///     signature          BIT STRING
 23 | /// }
 24 | /// 
25 | #[derive(Debug, PartialEq, Sequence)] 26 | #[asn1(parse = "DER", encode = "")] 27 | #[error(X509Error)] 28 | pub struct X509CertificationRequest<'a> { 29 | pub certification_request_info: X509CertificationRequestInfo<'a>, 30 | pub signature_algorithm: AlgorithmIdentifier<'a>, 31 | #[asn1(parse = "parse_signature_value")] 32 | pub signature_value: BitString, 33 | } 34 | 35 | impl X509CertificationRequest<'_> { 36 | /// Return an iterator over the Requested Extensions 37 | /// 38 | /// The requested extensions can be specified in different attributes, each attribute being a set of values. 39 | /// The iterator will go through every value of type 'ExtensionRequest` of every attribute. 40 | /// 41 | /// The returned iterator can be empty. 42 | /// 43 | /// _Note_: only successfully parsed values are returned (invalid extensions are *not* returned) 44 | pub fn requested_extensions(&self) -> impl Iterator { 45 | // iterator on all attribute, and flatten for each value of attribute 46 | self.certification_request_info 47 | .iter_attributes() 48 | .flat_map(|cri_attribute| { 49 | // return an iterator matching ExtensionRequest only 50 | cri_attribute 51 | .parsed_attributes() 52 | .iter() 53 | .filter_map(|p| { 54 | if let ParsedCriAttribute::ExtensionRequest(r) = &p { 55 | Some(r.extensions.iter().map(|ext| &ext.parsed_extension)) 56 | } else { 57 | None 58 | } 59 | }) 60 | .flatten() 61 | }) 62 | } 63 | 64 | /// Verify the cryptographic signature of this certification request 65 | /// 66 | /// Uses the public key contained in the CSR, which must be the one of the entity 67 | /// requesting the certification for this verification to succeed. 68 | #[cfg(feature = "verify")] 69 | pub fn verify_signature(&self) -> Result<(), X509Error> { 70 | let spki = &self.certification_request_info.subject_pki; 71 | verify_signature( 72 | spki, 73 | &self.signature_algorithm, 74 | &self.signature_value, 75 | self.certification_request_info.raw.as_bytes2(), 76 | ) 77 | } 78 | } 79 | 80 | ///
 81 | /// CertificationRequest ::= SEQUENCE {
 82 | ///     certificationRequestInfo CertificationRequestInfo,
 83 | ///     signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }},
 84 | ///     signature          BIT STRING
 85 | /// }
 86 | /// 
87 | impl<'a> FromDer<'a, X509Error> for X509CertificationRequest<'a> { 88 | fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { 89 | let input = Input::from(i); 90 | // run parser with default options 91 | match Self::parse_der(input) { 92 | Ok((rem, res)) => Ok((rem.as_bytes2(), res)), 93 | Err(e) => Err(e), 94 | } 95 | } 96 | } 97 | 98 | /// Certification Request Info structure (RFC 2986 Section 4.1) 99 | /// 100 | /// Certification request information is defined by the following ASN.1 structure: 101 | /// 102 | ///
103 | /// -- IMPLICIT tags
104 | /// CertificationRequestInfo ::= SEQUENCE {
105 | ///      version       INTEGER { v1(0) } (v1,...),
106 | ///      subject       Name,
107 | ///      subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }},
108 | ///      attributes    [0] Attributes{{ CRIAttributes }}
109 | /// }
110 | /// 
111 | /// 112 | /// version is the version number; subject is the distinguished name of the certificate 113 | /// subject; subject_pki contains information about the public key being certified, and 114 | /// attributes is a collection of attributes providing additional information about the 115 | /// subject of the certificate. 116 | #[derive(Debug, PartialEq)] 117 | pub struct X509CertificationRequestInfo<'a> { 118 | pub version: X509Version, 119 | pub subject: X509Name<'a>, 120 | pub subject_pki: SubjectPublicKeyInfo<'a>, 121 | attributes: Vec>, 122 | /// `raw` is used for signature verification 123 | raw: Input<'a>, 124 | } 125 | 126 | impl X509CertificationRequestInfo<'_> { 127 | /// Get the CRL entry extensions. 128 | #[inline] 129 | pub fn attributes(&self) -> &[X509CriAttribute] { 130 | &self.attributes 131 | } 132 | 133 | /// Returns an iterator over the CRL entry extensions 134 | #[inline] 135 | pub fn iter_attributes(&self) -> impl Iterator { 136 | self.attributes.iter() 137 | } 138 | 139 | /// Searches for a CRL entry extension with the given `Oid`. 140 | /// 141 | /// Note: if there are several extensions with the same `Oid`, the first one is returned. 142 | pub fn find_attribute(&self, oid: &Oid) -> Option<&X509CriAttribute> { 143 | self.attributes.iter().find(|&ext| ext.oid == *oid) 144 | } 145 | 146 | /// Builds and returns a map of CRL entry extensions. 147 | /// 148 | /// If an extension is present twice, this will fail and return `DuplicateExtensions`. 149 | pub fn attributes_map(&self) -> Result, X509Error> { 150 | self.attributes 151 | .iter() 152 | .try_fold(HashMap::new(), |mut m, ext| { 153 | if m.contains_key(&ext.oid) { 154 | return Err(X509Error::DuplicateAttributes); 155 | } 156 | m.insert(ext.oid.clone(), ext); 157 | Ok(m) 158 | }) 159 | } 160 | 161 | /// Return a pointer to the raw data 162 | pub fn raw(&self) -> &[u8] { 163 | self.raw.as_bytes2() 164 | } 165 | } 166 | 167 | impl Tagged for X509CertificationRequestInfo<'_> { 168 | const CONSTRUCTED: bool = true; 169 | const TAG: Tag = Tag::Sequence; 170 | } 171 | 172 | impl<'i> DerParser<'i> for X509CertificationRequestInfo<'i> { 173 | type Error = X509Error; 174 | 175 | fn parse_der(input: Input<'i>) -> IResult, Self, Self::Error> { 176 | let orig_input = input.clone(); 177 | 178 | let (rem, mut tbs) = Sequence::parse_der_and_then(input, |header, input| { 179 | Self::from_der_content(&header, input) 180 | })?; 181 | 182 | // update `raw` field to contain full sequence (including header) 183 | // this is safe because `rem` is built from `orig_input` 184 | let raw = orig_input.take(rem.start() - orig_input.start()); 185 | tbs.raw = raw; 186 | Ok((rem, tbs)) 187 | } 188 | 189 | fn from_der_content( 190 | header: &'_ Header<'i>, 191 | input: Input<'i>, 192 | ) -> IResult, Self, Self::Error> { 193 | header 194 | .assert_constructed_input(&input) 195 | .map_err(|e| Err::Error(e.into()))?; 196 | 197 | let raw = input.clone(); 198 | let (rem, version) = X509Version::parse_der(input)?; 199 | let (rem, subject) = X509Name::parse_der(rem)?; 200 | let (rem, subject_pki) = SubjectPublicKeyInfo::parse_der(rem)?; 201 | let (rem, opt_attributes) = 202 | , X509Error, 0>>::parse_der(rem)?; 203 | let attributes = opt_attributes.map(|o| o.into_inner()).unwrap_or_default(); 204 | 205 | let tbs = X509CertificationRequestInfo { 206 | version, 207 | subject, 208 | subject_pki, 209 | attributes, 210 | raw, 211 | }; 212 | Ok((rem, tbs)) 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/cri_attributes.rs: -------------------------------------------------------------------------------- 1 | use crate::{error::X509Error, extensions::X509Extension}; 2 | 3 | use asn1_rs::{Any, AnyIterator, BerError, DerMode, Set}; 4 | use asn1_rs::{DerParser, Header, Input, Oid, Tag, Tagged}; 5 | use nom::Err; 6 | use nom::IResult; 7 | use nom::Parser as _; 8 | use oid_registry::*; 9 | use std::collections::HashMap; 10 | 11 | /// Attributes for Certification Request 12 | /// 13 | ///
 14 | /// Attribute               ::= SEQUENCE {
 15 | ///     type             AttributeType,
 16 | ///     values    SET OF AttributeValue }
 17 | ///           -- at least one value is required
 18 | /// 
19 | #[derive(Clone, Debug, PartialEq)] 20 | pub struct X509CriAttribute<'a> { 21 | /// Attribute identifier 22 | pub oid: Oid<'a>, 23 | /// Unparsed data 24 | pub value: Input<'a>, 25 | pub(crate) parsed_attributes: Vec>, 26 | } 27 | 28 | impl<'a> X509CriAttribute<'a> { 29 | /// Return the parsed attribute values or `UnsupportedAttribute` if the attribute is unknown. 30 | #[inline] 31 | pub fn parsed_attributes(&self) -> &[ParsedCriAttribute<'a>] { 32 | &self.parsed_attributes 33 | } 34 | 35 | /// Iterate over the unparsed values of 'SET OF AttributeValue' 36 | pub fn iter_raw_values( 37 | &self, 38 | ) -> impl Iterator, Any<'a>), BerError>>> { 39 | AnyIterator::::new(self.value.clone()) 40 | } 41 | } 42 | 43 | impl Tagged for X509CriAttribute<'_> { 44 | const CONSTRUCTED: bool = true; 45 | const TAG: Tag = Tag::Sequence; 46 | } 47 | 48 | impl<'i> DerParser<'i> for X509CriAttribute<'i> { 49 | type Error = X509Error; 50 | 51 | fn from_der_content( 52 | header: &'_ Header<'i>, 53 | input: Input<'i>, 54 | ) -> IResult, Self, Self::Error> { 55 | header 56 | .assert_constructed_input(&input) 57 | .map_err(|e| Err::Error(e.into()))?; 58 | 59 | let (rem, oid) = Oid::parse_der(input).map_err(Err::convert)?; 60 | 61 | // `value` is the content of a 'SET OF AttributeValue'. Iterate and parse values 62 | let value = rem.clone(); 63 | // read DER header (ensuring it is a set) and content (as `value`) 64 | let (rem, (_, content)) = ::parse_der_as_input(rem).map_err(Err::convert)?; 65 | let parsed_attributes = AnyIterator::::new(content) 66 | .map(|r| { 67 | let (input, _) = r?; 68 | // parse attribute 69 | let (_, attr) = parser::parse_attribute(&oid, input)?; 70 | Ok::<_, X509Error>(attr) 71 | }) 72 | .collect::>()?; 73 | 74 | let attribute = X509CriAttribute { 75 | oid, 76 | value, 77 | parsed_attributes, 78 | }; 79 | Ok((rem, attribute)) 80 | } 81 | } 82 | 83 | /// Simple PKI Request 84 | /// 85 | /// Section 3.1 of RFC 5272 86 | /// 87 | ///
 88 | /// ExtensionReq ::= SEQUENCE SIZE (1..MAX) OF Extension
 89 | /// 
90 | #[derive(Clone, Debug, PartialEq)] 91 | pub struct ExtensionRequest<'a> { 92 | pub extensions: Vec>, 93 | } 94 | 95 | impl Tagged for ExtensionRequest<'_> { 96 | const CONSTRUCTED: bool = true; 97 | const TAG: Tag = Tag::Sequence; 98 | } 99 | 100 | impl<'i> DerParser<'i> for ExtensionRequest<'i> { 101 | type Error = X509Error; 102 | 103 | fn from_der_content( 104 | _: &'_ Header<'i>, 105 | input: Input<'i>, 106 | ) -> IResult, Self, Self::Error> { 107 | parser::parse_extension_request(input) 108 | } 109 | } 110 | 111 | #[derive(Clone, Debug, Eq, PartialEq)] 112 | pub struct ChallengePassword(pub String); 113 | 114 | /// Attributes for Certification Request 115 | #[derive(Clone, Debug, PartialEq)] 116 | pub enum ParsedCriAttribute<'a> { 117 | ChallengePassword(ChallengePassword), 118 | ExtensionRequest(ExtensionRequest<'a>), 119 | UnsupportedAttribute, 120 | } 121 | 122 | pub(crate) mod parser { 123 | use crate::cri_attributes::*; 124 | use crate::x509::DirectoryString; 125 | use asn1_rs::{DerParser, Input}; 126 | use lazy_static::lazy_static; 127 | use nom::combinator::map; 128 | 129 | type AttrParser = 130 | for<'a> fn(Input<'a>) -> IResult, ParsedCriAttribute<'a>, X509Error>; 131 | 132 | lazy_static! { 133 | static ref ATTRIBUTE_PARSERS: HashMap, AttrParser> = { 134 | macro_rules! add { 135 | ($m:ident, $oid:ident, $p:ident) => { 136 | $m.insert($oid, $p as AttrParser); 137 | }; 138 | } 139 | 140 | let mut m = HashMap::new(); 141 | add!(m, OID_PKCS9_EXTENSION_REQUEST, parse_extension_request_attr); 142 | add!( 143 | m, 144 | OID_PKCS9_CHALLENGE_PASSWORD, 145 | parse_challenge_password_attr 146 | ); 147 | m 148 | }; 149 | } 150 | 151 | /// Look into the parser map if the extension is known, and parse it, 152 | /// otherwise leave it as UnsupportedExtension 153 | /// 154 | /// Note: `Input` points to the start of the SET 155 | pub(crate) fn parse_attribute<'a>( 156 | oid: &Oid, 157 | value: Input<'a>, 158 | ) -> IResult, ParsedCriAttribute<'a>, X509Error> { 159 | if let Some(parser) = ATTRIBUTE_PARSERS.get(oid) { 160 | parser(value) 161 | } else { 162 | Ok((Input::default(), ParsedCriAttribute::UnsupportedAttribute)) 163 | } 164 | } 165 | 166 | /// Simple PKI Request (RFC 5272 Section 3.1) 167 | /// 168 | /// Note: `Input` points to the start of the SET 169 | pub(super) fn parse_extension_request( 170 | input: Input<'_>, 171 | ) -> IResult, ExtensionRequest<'_>, X509Error> { 172 | crate::extensions::parse_extension_sequence(input) 173 | .map(|(i, extensions)| (i, ExtensionRequest { extensions })) 174 | } 175 | 176 | fn parse_extension_request_attr( 177 | value: Input<'_>, 178 | ) -> IResult, ParsedCriAttribute<'_>, X509Error> { 179 | map( 180 | parse_extension_request, 181 | ParsedCriAttribute::ExtensionRequest, 182 | ) 183 | .parse(value) 184 | } 185 | 186 | // RFC 2985, 5.4.1 Challenge password 187 | // challengePassword ATTRIBUTE ::= { 188 | // WITH SYNTAX DirectoryString {pkcs-9-ub-challengePassword} 189 | // EQUALITY MATCHING RULE caseExactMatch 190 | // SINGLE VALUE TRUE 191 | // ID pkcs-9-at-challengePassword 192 | // } 193 | pub(super) fn parse_challenge_password( 194 | input: Input<'_>, 195 | ) -> IResult, ChallengePassword, X509Error> { 196 | let (rem, ds) = DirectoryString::parse_der(input) 197 | .map_err(|_| Err::Error(X509Error::InvalidAttributes))?; 198 | 199 | Ok((rem, ChallengePassword(ds.to_string()))) 200 | } 201 | 202 | fn parse_challenge_password_attr( 203 | value: Input<'_>, 204 | ) -> IResult, ParsedCriAttribute<'_>, X509Error> { 205 | map( 206 | parse_challenge_password, 207 | ParsedCriAttribute::ChallengePassword, 208 | ) 209 | .parse(value) 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! X.509 errors 2 | 3 | use asn1_rs::{BerError, Error, InnerError, Input}; 4 | use nom::error::{ErrorKind, ParseError}; 5 | use nom::IResult; 6 | 7 | /// An error that can occur while converting an OID to a Nid. 8 | #[derive(Debug, PartialEq, Eq)] 9 | pub struct NidError; 10 | 11 | /// Holds the result of parsing functions (X.509) 12 | /// 13 | /// Note that this type is also a `Result`, so usual functions (`map`, `unwrap` etc.) are available. 14 | pub type X509Result<'a, T> = IResult<&'a [u8], T, X509Error>; 15 | 16 | /// An error that can occur while parsing or validating a certificate. 17 | #[derive(Clone, Debug, PartialEq, thiserror::Error)] 18 | pub enum X509Error { 19 | #[error("generic error")] 20 | Generic, 21 | 22 | #[error("invalid version")] 23 | InvalidVersion, 24 | #[error("invalid serial")] 25 | InvalidSerial, 26 | #[error("invalid algorithm identifier")] 27 | InvalidAlgorithmIdentifier, 28 | #[error("invalid X.509 name")] 29 | InvalidX509Name, 30 | #[error("invalid date")] 31 | InvalidDate, 32 | #[error("invalid X.509 Subject Public Key Info")] 33 | InvalidSPKI, 34 | #[error("invalid X.509 Subject Unique ID")] 35 | InvalidSubjectUID, 36 | #[error("invalid X.509 Issuer Unique ID")] 37 | InvalidIssuerUID, 38 | #[error("invalid extensions")] 39 | InvalidExtensions, 40 | #[error("invalid attributes")] 41 | InvalidAttributes, 42 | #[error("duplicate extensions")] 43 | DuplicateExtensions, 44 | #[error("duplicate attributes")] 45 | DuplicateAttributes, 46 | #[error("invalid Signature DER Value")] 47 | InvalidSignatureValue, 48 | #[error("invalid TBS certificate")] 49 | InvalidTbsCertificate, 50 | 51 | // error types from CRL 52 | #[error("invalid User certificate")] 53 | InvalidUserCertificate, 54 | 55 | /// Top-level certificate structure is invalid 56 | #[error("invalid certificate")] 57 | InvalidCertificate, 58 | 59 | #[error("signature verification error")] 60 | SignatureVerificationError, 61 | #[error("signature unsupported algorithm")] 62 | SignatureUnsupportedAlgorithm, 63 | 64 | #[error("invalid number")] 65 | InvalidNumber, 66 | 67 | #[error("BER error: {0}")] 68 | Der(#[from] Error), 69 | #[error("BER error: {0}")] 70 | DerParser(#[from] InnerError), 71 | #[error("nom error: {0:?}")] 72 | NomError(ErrorKind), 73 | } 74 | 75 | impl From> for X509Error { 76 | fn from(e: nom::Err) -> Self { 77 | Self::Der(Error::from(e)) 78 | } 79 | } 80 | 81 | impl From> for X509Error { 82 | fn from(e: nom::Err) -> Self { 83 | match e { 84 | nom::Err::Error(e) | nom::Err::Failure(e) => e, 85 | nom::Err::Incomplete(i) => Self::Der(Error::Incomplete(i)), 86 | } 87 | } 88 | } 89 | 90 | impl From for nom::Err { 91 | fn from(e: X509Error) -> nom::Err { 92 | nom::Err::Error(e) 93 | } 94 | } 95 | 96 | impl From for X509Error { 97 | fn from(e: ErrorKind) -> X509Error { 98 | X509Error::NomError(e) 99 | } 100 | } 101 | 102 | impl<'a> From>> for X509Error { 103 | fn from(e: BerError>) -> X509Error { 104 | X509Error::DerParser(e.inner().clone()) 105 | } 106 | } 107 | 108 | impl ParseError for X509Error { 109 | fn from_error_kind(_input: I, kind: ErrorKind) -> Self { 110 | X509Error::NomError(kind) 111 | } 112 | fn append(_input: I, kind: ErrorKind, _other: Self) -> Self { 113 | X509Error::NomError(kind) 114 | } 115 | } 116 | 117 | /// An error that can occur while parsing or validating a certificate. 118 | #[derive(Debug, thiserror::Error)] 119 | pub enum PEMError { 120 | #[error("base64 decode error")] 121 | Base64DecodeError, 122 | #[error("incomplete PEM")] 123 | IncompletePEM, 124 | #[error("invalid header")] 125 | InvalidHeader, 126 | #[error("missing header")] 127 | MissingHeader, 128 | 129 | #[error("IO error: {0}")] 130 | IOError(#[from] std::io::Error), 131 | } 132 | -------------------------------------------------------------------------------- /src/extensions/authority_info_access.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use asn1_rs::{Alias, Oid, Sequence}; 4 | 5 | use crate::error::X509Error; 6 | 7 | use super::GeneralName; 8 | 9 | ///
10 | /// AuthorityInfoAccessSyntax  ::=
11 | ///         SEQUENCE SIZE (1..MAX) OF AccessDescription
12 | /// 
13 | #[derive(Clone, Debug, PartialEq, Alias)] 14 | #[asn1(parse = "DER", encode = "")] 15 | #[error(X509Error)] 16 | pub struct AuthorityInfoAccess<'a>(pub Vec>); 17 | 18 | impl<'a> AuthorityInfoAccess<'a> { 19 | /// Returns an iterator over the Access Descriptors 20 | pub fn iter(&self) -> impl Iterator> { 21 | self.0.iter() 22 | } 23 | 24 | /// Returns a `HashMap` mapping `Oid` to the list of references to `GeneralNames` 25 | /// 26 | /// If several names match the same `Oid`, they are merged in the same entry. 27 | pub fn as_hashmap(&self) -> HashMap, Vec<&GeneralName<'a>>> { 28 | // create the hashmap and merge entries with same OID 29 | let mut m: HashMap> = HashMap::new(); 30 | for desc in &self.0 { 31 | let AccessDescription { 32 | access_method: oid, 33 | access_location: gn, 34 | } = desc; 35 | if let Some(general_names) = m.get_mut(oid) { 36 | general_names.push(gn); 37 | } else { 38 | m.insert(oid.clone(), vec![gn]); 39 | } 40 | } 41 | m 42 | } 43 | 44 | /// Returns a `HashMap` mapping `Oid` to the list of `GeneralNames` (consuming the input) 45 | /// 46 | /// If several names match the same `Oid`, they are merged in the same entry. 47 | pub fn into_hashmap(self) -> HashMap, Vec>> { 48 | let mut aia_list = self.0; 49 | // create the hashmap and merge entries with same OID 50 | let mut m: HashMap> = HashMap::new(); 51 | for desc in aia_list.drain(..) { 52 | let AccessDescription { 53 | access_method: oid, 54 | access_location: gn, 55 | } = desc; 56 | if let Some(general_names) = m.get_mut(&oid) { 57 | general_names.push(gn); 58 | } else { 59 | m.insert(oid, vec![gn]); 60 | } 61 | } 62 | m 63 | } 64 | } 65 | 66 | ///
67 | /// AccessDescription  ::=  SEQUENCE {
68 | ///         accessMethod          OBJECT IDENTIFIER,
69 | ///         accessLocation        GeneralName  }
70 | /// 
71 | #[derive(Clone, Debug, PartialEq, Sequence)] 72 | #[asn1(parse = "DER", encode = "")] 73 | #[error(X509Error)] 74 | pub struct AccessDescription<'a> { 75 | pub access_method: Oid<'a>, 76 | pub access_location: GeneralName<'a>, 77 | } 78 | 79 | impl<'a> AccessDescription<'a> { 80 | pub const fn new(access_method: Oid<'a>, access_location: GeneralName<'a>) -> Self { 81 | AccessDescription { 82 | access_method, 83 | access_location, 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/extensions/authority_key_identifier.rs: -------------------------------------------------------------------------------- 1 | use asn1_rs::{Integer, Sequence}; 2 | 3 | use crate::error::X509Error; 4 | 5 | use super::{GeneralName, KeyIdentifier}; 6 | 7 | ///
 8 | /// -- IMPLICIT tags
 9 | /// AuthorityKeyIdentifier ::= SEQUENCE {
10 | ///     keyIdentifier             [0] KeyIdentifier            OPTIONAL,
11 | ///     authorityCertIssuer       [1] GeneralNames             OPTIONAL,
12 | ///     authorityCertSerialNumber [2] CertificateSerialNumber  OPTIONAL }
13 | ///     -- authorityCertIssuer and authorityCertSerialNumber MUST both
14 | ///     -- be present or both be absent
15 | ///
16 | /// CertificateSerialNumber  ::=  INTEGER
17 | /// 
18 | #[derive(Clone, Debug, PartialEq, Sequence)] 19 | #[asn1(parse = "DER", encode = "")] 20 | #[error(X509Error)] 21 | pub struct AuthorityKeyIdentifier<'a> { 22 | #[tag_implicit(0)] 23 | #[optional] 24 | pub key_identifier: Option>, 25 | 26 | #[tag_implicit(1)] 27 | #[optional] 28 | pub authority_cert_issuer: Option>>, 29 | 30 | #[tag_implicit(2)] 31 | #[optional] 32 | pub authority_cert_serial: Option>, 33 | } 34 | -------------------------------------------------------------------------------- /src/extensions/basic_constraints.rs: -------------------------------------------------------------------------------- 1 | use asn1_rs::Sequence; 2 | 3 | use crate::error::X509Error; 4 | 5 | /// "Basic Constraints" extension: identifies whether the subject of the certificate 6 | /// is a CA, and the max validation depth. 7 | /// 8 | ///
 9 | ///   id-ce-basicConstraints OBJECT IDENTIFIER ::=  { id-ce 19 }
10 | ///   BasicConstraints ::= SEQUENCE {
11 | ///        cA                      BOOLEAN DEFAULT FALSE,
12 | ///        pathLenConstraint       INTEGER (0..MAX) OPTIONAL }
13 | /// 
14 | /// 15 | /// Note the maximum length of the `pathLenConstraint` field is limited to the size of a 32-bits 16 | /// unsigned integer, and parsing will fail if value if larger. 17 | #[derive(Clone, Debug, PartialEq, Eq, Sequence)] 18 | #[asn1(parse = "DER", encode = "")] 19 | #[error(X509Error)] 20 | pub struct BasicConstraints { 21 | #[default(false)] 22 | pub ca: bool, 23 | pub path_len_constraint: Option, 24 | } 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | use crate::extensions::BasicConstraints; 29 | use asn1_rs::{DerParser, Input}; 30 | use hex_literal::hex; 31 | 32 | #[test] 33 | fn extension_basic_constraints() { 34 | //--- CA=false 35 | let bytes = &hex!("30 00"); 36 | let (rem, res) = BasicConstraints::parse_der(Input::from(bytes)).expect("BasicConstraints"); 37 | assert!(rem.is_empty()); 38 | assert_eq!( 39 | res, 40 | BasicConstraints { 41 | ca: false, 42 | path_len_constraint: None 43 | } 44 | ); 45 | 46 | //--- CA=true, pathlen omitted 47 | let bytes = &hex!("30 03 01 01 FF"); 48 | let (rem, res) = BasicConstraints::parse_der(Input::from(bytes)).expect("BasicConstraints"); 49 | assert!(rem.is_empty()); 50 | assert_eq!( 51 | res, 52 | BasicConstraints { 53 | ca: true, 54 | path_len_constraint: None 55 | } 56 | ); 57 | 58 | //--- CA=true, pathlen=> 59 | let bytes = &hex!("30 06 01 01 FF 02 01 0a"); 60 | let (rem, res) = BasicConstraints::parse_der(Input::from(bytes)).expect("BasicConstraints"); 61 | assert!(rem.is_empty()); 62 | assert_eq!( 63 | res, 64 | BasicConstraints { 65 | ca: true, 66 | path_len_constraint: Some(0xa), 67 | } 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/extensions/certificate_policies.rs: -------------------------------------------------------------------------------- 1 | use asn1_rs::{Any, Oid, Sequence}; 2 | 3 | use crate::error::X509Error; 4 | 5 | ///
 6 | /// CertificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation
 7 | /// 
8 | pub type CertificatePolicies<'a> = Vec>; 9 | 10 | ///
11 | /// PolicyInformation ::= SEQUENCE {
12 | ///      policyIdentifier   CertPolicyId,
13 | ///      policyQualifiers   SEQUENCE SIZE (1..MAX) OF
14 | ///              PolicyQualifierInfo OPTIONAL }
15 | ///
16 | /// CertPolicyId ::= OBJECT IDENTIFIER
17 | /// 
18 | #[derive(Clone, Debug, PartialEq, Eq, Sequence)] 19 | #[asn1(parse = "DER", encode = "")] 20 | #[error(X509Error)] 21 | pub struct PolicyInformation<'a> { 22 | pub policy_id: Oid<'a>, 23 | #[optional] 24 | pub policy_qualifiers: Option>>, 25 | } 26 | 27 | ///
28 | /// PolicyQualifierInfo ::= SEQUENCE {
29 | ///      policyQualifierId  PolicyQualifierId,
30 | ///      qualifier          ANY DEFINED BY policyQualifierId }
31 | /// 
32 | #[derive(Clone, Debug, PartialEq, Eq, Sequence)] 33 | #[asn1(parse = "DER", encode = "")] 34 | #[error(X509Error)] 35 | pub struct PolicyQualifierInfo<'a> { 36 | pub policy_qualifier_id: Oid<'a>, 37 | pub qualifier: Any<'a>, 38 | } 39 | 40 | //
41 | // -- Implementations that recognize additional policy qualifiers MUST
42 | // -- augment the following definition for PolicyQualifierId
43 | //
44 | // PolicyQualifierId ::= OBJECT IDENTIFIER ( id-qt-cps | id-qt-unotice )
45 | // 
46 | -------------------------------------------------------------------------------- /src/extensions/distribution_point.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | use asn1_rs::bitvec::field::BitField; 4 | use asn1_rs::{Alias, BitString, Choice, DerParser, Sequence, Tag, Tagged}; 5 | use nom::Err; 6 | 7 | use crate::error::X509Error; 8 | use crate::x509::RelativeDistinguishedName; 9 | 10 | use super::GeneralName; 11 | 12 | ///
 13 | /// -- IMPLICIT tags
 14 | /// DistributionPointName ::= CHOICE {
 15 | ///     fullName                [0]     GeneralNames,
 16 | ///     nameRelativeToCRLIssuer [1]     RelativeDistinguishedName }
 17 | /// 
18 | #[derive(Clone, Debug, PartialEq, Choice)] 19 | #[asn1(parse = "DER", encode = "")] 20 | #[error(X509Error)] 21 | #[tagged_implicit] 22 | pub enum DistributionPointName<'a> { 23 | FullName(Vec>), 24 | NameRelativeToCRLIssuer(RelativeDistinguishedName<'a>), 25 | } 26 | 27 | ///
 28 | /// ReasonFlags ::= BIT STRING {
 29 | /// unused                  (0),
 30 | /// keyCompromise           (1),
 31 | /// cACompromise            (2),
 32 | /// affiliationChanged      (3),
 33 | /// superseded              (4),
 34 | /// cessationOfOperation    (5),
 35 | /// certificateHold         (6),
 36 | /// privilegeWithdrawn      (7),
 37 | /// aACompromise            (8) }
 38 | /// 
39 | #[derive(Clone, Debug, PartialEq, Eq)] 40 | pub struct ReasonFlags { 41 | pub flags: u16, 42 | } 43 | 44 | impl ReasonFlags { 45 | pub fn key_compromise(&self) -> bool { 46 | (self.flags >> 1) & 1 == 1 47 | } 48 | pub fn ca_compromise(&self) -> bool { 49 | (self.flags >> 2) & 1 == 1 50 | } 51 | pub fn affiliation_changed(&self) -> bool { 52 | (self.flags >> 3) & 1 == 1 53 | } 54 | pub fn superseded(&self) -> bool { 55 | (self.flags >> 4) & 1 == 1 56 | } 57 | pub fn cessation_of_operation(&self) -> bool { 58 | (self.flags >> 5) & 1 == 1 59 | } 60 | pub fn certificate_hold(&self) -> bool { 61 | (self.flags >> 6) & 1 == 1 62 | } 63 | pub fn privilege_withdrawn(&self) -> bool { 64 | (self.flags >> 7) & 1 == 1 65 | } 66 | pub fn aa_compromise(&self) -> bool { 67 | (self.flags >> 8) & 1 == 1 68 | } 69 | } 70 | 71 | const REASON_FLAGS: &[&str] = &[ 72 | "Unused", 73 | "Key Compromise", 74 | "CA Compromise", 75 | "Affiliation Changed", 76 | "Superseded", 77 | "Cessation Of Operation", 78 | "Certificate Hold", 79 | "Privilege Withdrawn", 80 | "AA Compromise", 81 | ]; 82 | 83 | impl fmt::Display for ReasonFlags { 84 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 85 | let mut s = String::new(); 86 | let mut acc = self.flags; 87 | for flag_text in REASON_FLAGS { 88 | if acc & 1 != 0 { 89 | s = s + flag_text + ", "; 90 | } 91 | acc >>= 1; 92 | } 93 | s.pop(); 94 | s.pop(); 95 | f.write_str(&s) 96 | } 97 | } 98 | 99 | impl Tagged for ReasonFlags { 100 | const TAG: Tag = Tag::BitString; 101 | } 102 | 103 | impl<'i> DerParser<'i> for ReasonFlags { 104 | type Error = X509Error; 105 | 106 | fn from_der_content( 107 | header: &'_ asn1_rs::Header<'i>, 108 | input: asn1_rs::Input<'i>, 109 | ) -> nom::IResult, Self, Self::Error> { 110 | let (rem, mut obj) = BitString::from_der_content(header, input).map_err(Err::convert)?; 111 | let bitslice = obj.as_mut_bitslice(); 112 | if bitslice.len() > 9 { 113 | return Err(Err::Error(X509Error::InvalidAttributes)); 114 | } 115 | if bitslice.is_empty() { 116 | return Ok((rem, Self { flags: 0 })); 117 | } 118 | bitslice.reverse(); 119 | let flags = bitslice.load_be::(); 120 | Ok((rem, Self { flags })) 121 | } 122 | } 123 | 124 | ///
125 | /// -- IMPLICIT tags
126 | /// DistributionPoint ::= SEQUENCE {
127 | ///     distributionPoint       [0]     DistributionPointName OPTIONAL,
128 | ///     reasons                 [1]     ReasonFlags OPTIONAL,
129 | ///     cRLIssuer               [2]     GeneralNames OPTIONAL }
130 | /// 
131 | /// 132 | /// Note: this object has implicit tags, however ASN.1 specifications (X.680) contain the following note: 133 | /// 134 | ///
135 | /// The tagging construction specifies explicit tagging if any of the following holds:
136 | ///
137 | /// c) the "Tag Type" alternative is used and the value of "TagDefault" for the module is
138 | /// IMPLICIT TAGS or AUTOMATIC TAGS, but the type defined by "Type" is an untagged choice type,
139 | /// an untagged open type, or an untagged "DummyReference"
140 | /// (see Rec. ITU-T X.683 | ISO/IEC 8824-4, 8.3).
141 | /// 
142 | /// 143 | /// Thus, the fields using tags (like `DistributionPointName`) use *explicit* tags here. 144 | #[derive(Clone, Debug, PartialEq, Sequence)] 145 | #[asn1(parse = "DER", encode = "")] 146 | #[error(X509Error)] 147 | // #[debug_derive] 148 | pub struct CRLDistributionPoint<'a> { 149 | #[tag_explicit(0)] 150 | #[optional] 151 | pub distribution_point: Option>, 152 | #[tag_implicit(1)] 153 | #[optional] 154 | pub reasons: Option, 155 | #[tag_implicit(2)] 156 | #[optional] 157 | pub crl_issuer: Option>>, 158 | } 159 | 160 | ///
161 | /// CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint
162 | /// 
163 | #[derive(Clone, Debug, PartialEq, Alias)] 164 | #[asn1(parse = "DER", encode = "")] 165 | #[error(X509Error)] 166 | pub struct CRLDistributionPoints<'a>(pub Vec>); 167 | 168 | impl<'a> std::ops::Deref for CRLDistributionPoints<'a> { 169 | type Target = Vec>; 170 | 171 | fn deref(&self) -> &Self::Target { 172 | &self.0 173 | } 174 | } 175 | 176 | #[cfg(test)] 177 | mod tests { 178 | use asn1_rs::{DerParser, Input}; 179 | use hex_literal::hex; 180 | 181 | use crate::prelude::CRLDistributionPoint; 182 | 183 | use super::{CRLDistributionPoints, DistributionPointName}; 184 | 185 | #[test] 186 | fn parse_crl_distribution_points() { 187 | let bytes = &hex!( 188 | "30 23 30 21 189 | A0 1F A0 1D 86 1B 68 74 74 70 3A 2F 2F 65 78 61 190 | 6D 70 6C 65 2E 63 6F 6D 2F 6D 79 63 61 2E 63 72 191 | 6C" 192 | ); 193 | 194 | let (rem, crl_distribution_points) = 195 | CRLDistributionPoints::parse_der(Input::from(bytes)).expect("Parsing failed"); 196 | assert!(rem.is_empty()); 197 | assert_eq!(crl_distribution_points.0.len(), 1); 198 | } 199 | 200 | #[test] 201 | fn parse_distribution_point() { 202 | let bytes: &[u8; 35] = &hex!( 203 | "30 21 204 | A0 1F A0 1D 86 1B 68 74 74 70 3A 2F 2F 65 78 61 205 | 6D 70 6C 65 2E 63 6F 6D 2F 6D 79 63 61 2E 63 72 206 | 6C" 207 | ); 208 | 209 | let (rem, obj) = 210 | CRLDistributionPoint::parse_der(Input::from(bytes)).expect("Parsing failed"); 211 | assert!(rem.is_empty()); 212 | assert!(obj.distribution_point.is_some()); 213 | } 214 | 215 | #[test] 216 | fn parse_distribution_point_name() { 217 | let bytes = &hex!( 218 | "A0 1D 86 1B 68 74 74 70 3A 2F 2F 65 78 61 219 | 6D 70 6C 65 2E 63 6F 6D 2F 6D 79 63 61 2E 63 72 220 | 6C" 221 | ); 222 | 223 | let (rem, obj) = 224 | DistributionPointName::parse_der(Input::from(bytes)).expect("Parsing failed"); 225 | assert!(rem.is_empty()); 226 | assert!(matches!(obj, DistributionPointName::FullName(_))); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/extensions/extended_key_usage.rs: -------------------------------------------------------------------------------- 1 | use asn1_rs::{oid, DerParser, Input, Oid, Tag, Tagged}; 2 | use nom::{Err, IResult}; 3 | 4 | use crate::error::X509Error; 5 | 6 | ///
 7 | /// ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId
 8 | ///
 9 | /// KeyPurposeId ::= OBJECT IDENTIFIER
10 | /// 
11 | #[derive(Clone, Debug, PartialEq, Eq)] 12 | pub struct ExtendedKeyUsage<'a> { 13 | pub any: bool, 14 | pub server_auth: bool, 15 | pub client_auth: bool, 16 | pub code_signing: bool, 17 | pub email_protection: bool, 18 | pub time_stamping: bool, 19 | pub ocsp_signing: bool, 20 | pub other: Vec>, 21 | } 22 | 23 | impl Tagged for ExtendedKeyUsage<'_> { 24 | const CONSTRUCTED: bool = true; 25 | const TAG: Tag = Tag::Sequence; 26 | } 27 | 28 | impl<'a> DerParser<'a> for ExtendedKeyUsage<'a> { 29 | type Error = X509Error; 30 | 31 | fn from_der_content( 32 | header: &'_ asn1_rs::Header<'a>, 33 | input: Input<'a>, 34 | ) -> IResult, Self, Self::Error> { 35 | let (rem, seq) = >::from_der_content(header, input).map_err(Err::convert)?; 36 | let mut seen = std::collections::HashSet::new(); 37 | let mut eku = ExtendedKeyUsage { 38 | any: false, 39 | server_auth: false, 40 | client_auth: false, 41 | code_signing: false, 42 | email_protection: false, 43 | time_stamping: false, 44 | ocsp_signing: false, 45 | other: Vec::new(), 46 | }; 47 | for oid in &seq { 48 | if !seen.insert(oid.clone()) { 49 | continue; 50 | } 51 | let asn1 = oid.as_bytes(); 52 | if asn1 == oid!(raw 2.5.29.37.0) { 53 | eku.any = true; 54 | } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.1) { 55 | eku.server_auth = true; 56 | } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.2) { 57 | eku.client_auth = true; 58 | } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.3) { 59 | eku.code_signing = true; 60 | } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.4) { 61 | eku.email_protection = true; 62 | } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.8) { 63 | eku.time_stamping = true; 64 | } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.9) { 65 | eku.ocsp_signing = true; 66 | } else { 67 | eku.other.push(oid.clone()); 68 | } 69 | } 70 | Ok((rem, eku)) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/extensions/generalname.rs: -------------------------------------------------------------------------------- 1 | use crate::error::X509Error; 2 | use crate::prelude::format_serial; 3 | use crate::x509::X509Name; 4 | use asn1_rs::{Any, DerParser, DynTagged, Header, InnerError, Input, Oid, Tag}; 5 | use nom::{Err, IResult, Input as _}; 6 | use std::fmt; 7 | 8 | /// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName 9 | pub type GeneralNames<'a> = Vec>; 10 | 11 | /// Represents a GeneralName as defined in RFC5280. There 12 | /// is no support X.400 addresses and EDIPartyName. 13 | /// 14 | /// String formats are not validated (except for valid UTF-8). 15 | /// 16 | ///
 17 | /// -- Note: IMPLICIT Tags
 18 | /// GeneralName ::= CHOICE {
 19 | ///     otherName                 [0]  AnotherName,
 20 | ///     rfc822Name                [1]  IA5String,
 21 | ///     dNSName                   [2]  IA5String,
 22 | ///     x400Address               [3]  ORAddress,
 23 | ///     directoryName             [4]  Name,
 24 | ///     ediPartyName              [5]  EDIPartyName,
 25 | ///     uniformResourceIdentifier [6]  IA5String,
 26 | ///     iPAddress                 [7]  OCTET STRING,
 27 | ///     registeredID              [8]  OBJECT IDENTIFIER }
 28 | /// 
29 | #[derive(Clone, Debug, PartialEq)] 30 | pub enum GeneralName<'a> { 31 | OtherName(Oid<'a>, Any<'a>), 32 | /// More or less an e-mail, the format is not checked. 33 | RFC822Name(&'a str), 34 | /// A hostname, format is not checked. 35 | DNSName(&'a str), 36 | /// X400Address, 37 | X400Address(Any<'a>), 38 | /// RFC5280 defines several string types, we always try to parse as utf-8 39 | /// which is more or less a superset of the string types. 40 | DirectoryName(X509Name<'a>), 41 | /// EDIPartyName 42 | EDIPartyName(Any<'a>), 43 | /// An uniform resource identifier. The format is not checked. 44 | URI(&'a str), 45 | /// An ip address, provided as encoded. 46 | IPAddress(&'a [u8]), 47 | RegisteredID(Oid<'a>), 48 | /// Invalid data (for ex. invalid UTF-8 data in DNSName entry) 49 | Invalid(Any<'a>), 50 | } 51 | 52 | impl DynTagged for GeneralName<'_> { 53 | fn constructed(&self) -> bool { 54 | matches!( 55 | self, 56 | GeneralName::OtherName(_, _) 57 | | GeneralName::X400Address(_) 58 | | GeneralName::DirectoryName(_) 59 | | GeneralName::EDIPartyName(_) 60 | ) 61 | } 62 | 63 | fn tag(&self) -> Tag { 64 | match self { 65 | GeneralName::OtherName(_, _) => Tag(0), 66 | GeneralName::RFC822Name(_) => Tag(1), 67 | GeneralName::DNSName(_) => Tag(2), 68 | GeneralName::X400Address(_) => Tag(3), 69 | GeneralName::DirectoryName(_) => Tag(4), 70 | GeneralName::EDIPartyName(_) => Tag(5), 71 | GeneralName::URI(_) => Tag(6), 72 | GeneralName::IPAddress(_) => Tag(7), 73 | GeneralName::RegisteredID(_) => Tag(8), 74 | GeneralName::Invalid(any) => any.tag(), 75 | } 76 | } 77 | 78 | fn accept_tag(tag: Tag) -> bool { 79 | (0..9).contains(&tag.0) 80 | } 81 | } 82 | 83 | impl<'a> DerParser<'a> for GeneralName<'a> { 84 | type Error = X509Error; 85 | 86 | fn from_der_content( 87 | header: &'_ Header<'a>, 88 | input: Input<'a>, 89 | ) -> IResult, Self, Self::Error> { 90 | let (rem, gn) = match header.tag().0 { 91 | 0 => { 92 | // AnotherName ::= SEQUENCE { 93 | // type-id OBJECT IDENTIFIER, 94 | // value [0] EXPLICIT ANY DEFINED BY type-id } 95 | let (rem, (type_id, value)) = 96 | <(Oid, Any)>::from_der_content(header, input).map_err(Err::convert)?; 97 | (rem, GeneralName::OtherName(type_id, value)) 98 | } 99 | 1 => { 100 | let (rem, s) = ia5str_relaxed(input)?; 101 | (rem, GeneralName::RFC822Name(s)) 102 | } 103 | 2 => { 104 | let (rem, s) = ia5str_relaxed(input)?; 105 | (rem, GeneralName::DNSName(s)) 106 | } 107 | 3 => { 108 | // XXX Not yet implemented 109 | let rem = input.take_from(input.input_len()); 110 | let any = Any::new(header.clone(), input); 111 | (rem, GeneralName::X400Address(any)) 112 | } 113 | 4 => { 114 | // Field is 'IMPLICIT [4] Name', but name is a CHOICE { RDNSequence } 115 | // so tags are EXPLICIT 116 | let (rem, name) = X509Name::parse_der(input)?; 117 | (rem, GeneralName::DirectoryName(name)) 118 | } 119 | 5 => { 120 | // Deep parsing not yet implemented, so just read Any 121 | let rem = input.take_from(input.input_len()); 122 | let any = Any::new(header.clone(), input); 123 | (rem, GeneralName::EDIPartyName(any)) 124 | } 125 | 6 => { 126 | let (rem, s) = ia5str_relaxed(input)?; 127 | (rem, GeneralName::URI(s)) 128 | } 129 | 7 => { 130 | let (rem, b) = <&[u8]>::from_der_content(header, input).map_err(Err::convert)?; 131 | (rem, GeneralName::IPAddress(b)) 132 | } 133 | 8 => { 134 | let (rem, oid) = Oid::from_der_content(header, input).map_err(Err::convert)?; 135 | (rem, GeneralName::RegisteredID(oid)) 136 | } 137 | _ => { 138 | let rem = input.take_from(input.input_len()); 139 | let any = Any::new(header.clone(), input); 140 | (rem, GeneralName::Invalid(any)) 141 | } 142 | }; 143 | Ok((rem, gn)) 144 | } 145 | } 146 | 147 | impl fmt::Display for GeneralName<'_> { 148 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 149 | match self { 150 | GeneralName::OtherName(oid, _) => write!(f, "OtherName({oid}, [...])"), 151 | GeneralName::RFC822Name(s) => write!(f, "RFC822Name({s})"), 152 | GeneralName::DNSName(s) => write!(f, "DNSName({s})"), 153 | GeneralName::X400Address(_) => write!(f, "X400Address()"), 154 | GeneralName::DirectoryName(dn) => write!(f, "DirectoryName({dn})"), 155 | GeneralName::EDIPartyName(_) => write!(f, "EDIPartyName()"), 156 | GeneralName::URI(s) => write!(f, "URI({s})"), 157 | GeneralName::IPAddress(b) => write!(f, "IPAddress({})", format_serial(b)), 158 | GeneralName::RegisteredID(oid) => write!(f, "RegisteredID({oid})"), 159 | GeneralName::Invalid(any) => { 160 | write!( 161 | f, 162 | "Invalid(tag={}, data={})", 163 | any.tag(), 164 | format_serial(any.data.as_bytes2()) 165 | ) 166 | } 167 | } 168 | } 169 | } 170 | 171 | fn ia5str_relaxed(input: Input) -> Result<(Input, &str), Err> { 172 | let (rem, input) = input.take_split(input.input_len()); 173 | // Relax constraints from RFC here: we are expecting an IA5String, but many certificates 174 | // are using unicode characters 175 | let s = std::str::from_utf8(input.as_bytes2()) 176 | .map_err(|_| Err::Failure(X509Error::DerParser(InnerError::StringInvalidCharset)))?; 177 | Ok((rem, s)) 178 | } 179 | -------------------------------------------------------------------------------- /src/extensions/inhibitant_policy.rs: -------------------------------------------------------------------------------- 1 | use asn1_rs::Alias; 2 | 3 | use crate::error::X509Error; 4 | 5 | ///
 6 | /// SkipCerts ::= INTEGER (0..MAX)
 7 | /// 
8 | pub type SkipCerts = u32; 9 | 10 | ///
11 | /// InhibitAnyPolicy ::= SkipCerts
12 | /// 
13 | #[derive(Clone, Debug, PartialEq, Eq, Alias)] 14 | #[asn1(parse = "DER", encode = "")] 15 | #[error(X509Error)] 16 | pub struct InhibitAnyPolicy(pub SkipCerts); 17 | -------------------------------------------------------------------------------- /src/extensions/issuer_alt_name.rs: -------------------------------------------------------------------------------- 1 | use asn1_rs::Alias; 2 | 3 | use crate::error::X509Error; 4 | 5 | use super::GeneralNames; 6 | 7 | /// Issuer Alternative Name 8 | /// 9 | /// Note: empty sequences are accepted 10 | /// 11 | ///
12 | /// IssuerAltName ::= GeneralNames
13 | ///
14 | /// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
15 | /// 
16 | #[derive(Clone, Debug, PartialEq, Alias)] 17 | #[asn1(parse = "DER", encode = "")] 18 | #[error(X509Error)] 19 | pub struct IssuerAlternativeName<'a>(pub GeneralNames<'a>); 20 | -------------------------------------------------------------------------------- /src/extensions/issuing_distribution_point.rs: -------------------------------------------------------------------------------- 1 | use asn1_rs::Sequence; 2 | 3 | use crate::error::X509Error; 4 | 5 | use super::{DistributionPointName, ReasonFlags}; 6 | 7 | ///
 8 | /// -- IMPLICIT tags
 9 | /// IssuingDistributionPoint ::= SEQUENCE {
10 | ///     distributionPoint          [0] DistributionPointName OPTIONAL,
11 | ///     onlyContainsUserCerts      [1] BOOLEAN DEFAULT FALSE,
12 | ///     onlyContainsCACerts        [2] BOOLEAN DEFAULT FALSE,
13 | ///     onlySomeReasons            [3] ReasonFlags OPTIONAL,
14 | ///     indirectCRL                [4] BOOLEAN DEFAULT FALSE,
15 | ///     onlyContainsAttributeCerts [5] BOOLEAN DEFAULT FALSE }
16 | ///     -- at most one of onlyContainsUserCerts, onlyContainsCACerts,
17 | ///     -- and onlyContainsAttributeCerts may be set to TRUE.
18 | /// 
19 | #[derive(Clone, Debug, PartialEq, Sequence)] 20 | #[asn1(parse = "DER", encode = "")] 21 | #[error(X509Error)] 22 | pub struct IssuingDistributionPoint<'a> { 23 | // `DistributionPointName` is a CHOICE, so this tag is converted to EXPLICIT 24 | // See `CRLDistributionPoint` documentation for details 25 | #[tag_explicit(0)] 26 | #[optional] 27 | pub distribution_point: Option>, 28 | 29 | #[tag_implicit(1)] 30 | #[default(false)] 31 | pub only_contains_user_certs: bool, 32 | 33 | #[tag_implicit(2)] 34 | #[default(false)] 35 | pub only_contains_ca_certs: bool, 36 | 37 | #[tag_implicit(3)] 38 | #[optional] 39 | pub only_some_reasons: Option, 40 | 41 | #[tag_implicit(4)] 42 | #[default(false)] 43 | pub indirect_crl: bool, 44 | 45 | #[tag_implicit(5)] 46 | #[default(false)] 47 | pub only_contains_attribute_certs: bool, 48 | } 49 | -------------------------------------------------------------------------------- /src/extensions/key_usage.rs: -------------------------------------------------------------------------------- 1 | use crate::error::X509Error; 2 | use asn1_rs::{bitvec::field::BitField, BitString, DerParser, Header, Input, Tag, Tagged}; 3 | use nom::{Err, IResult}; 4 | use std::fmt; 5 | 6 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 7 | pub struct KeyUsage { 8 | pub flags: u16, 9 | } 10 | 11 | impl KeyUsage { 12 | pub fn digital_signature(&self) -> bool { 13 | self.flags & 1 == 1 14 | } 15 | pub fn non_repudiation(&self) -> bool { 16 | (self.flags >> 1) & 1u16 == 1 17 | } 18 | pub fn key_encipherment(&self) -> bool { 19 | (self.flags >> 2) & 1u16 == 1 20 | } 21 | pub fn data_encipherment(&self) -> bool { 22 | (self.flags >> 3) & 1u16 == 1 23 | } 24 | pub fn key_agreement(&self) -> bool { 25 | (self.flags >> 4) & 1u16 == 1 26 | } 27 | pub fn key_cert_sign(&self) -> bool { 28 | (self.flags >> 5) & 1u16 == 1 29 | } 30 | pub fn crl_sign(&self) -> bool { 31 | (self.flags >> 6) & 1u16 == 1 32 | } 33 | pub fn encipher_only(&self) -> bool { 34 | (self.flags >> 7) & 1u16 == 1 35 | } 36 | pub fn decipher_only(&self) -> bool { 37 | (self.flags >> 8) & 1u16 == 1 38 | } 39 | } 40 | 41 | // This list must have the same order as KeyUsage flags declaration (4.2.1.3) 42 | const KEY_USAGE_FLAGS: &[&str] = &[ 43 | "Digital Signature", 44 | "Non Repudiation", 45 | "Key Encipherment", 46 | "Data Encipherment", 47 | "Key Agreement", 48 | "Key Cert Sign", 49 | "CRL Sign", 50 | "Encipher Only", 51 | "Decipher Only", 52 | ]; 53 | 54 | impl fmt::Display for KeyUsage { 55 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 56 | let mut s = KEY_USAGE_FLAGS 57 | .iter() 58 | .enumerate() 59 | .fold(String::new(), |acc, (idx, s)| { 60 | if (self.flags >> idx) & 1 != 0 { 61 | acc + s + ", " 62 | } else { 63 | acc 64 | } 65 | }); 66 | s.pop(); 67 | s.pop(); 68 | f.write_str(&s) 69 | } 70 | } 71 | 72 | impl Tagged for KeyUsage { 73 | const TAG: Tag = Tag::BitString; 74 | } 75 | 76 | impl<'i> DerParser<'i> for KeyUsage { 77 | type Error = X509Error; 78 | 79 | fn from_der_content( 80 | header: &'_ Header<'i>, 81 | input: Input<'i>, 82 | ) -> IResult, Self, Self::Error> { 83 | let (rem, mut obj) = BitString::from_der_content(header, input).map_err(Err::convert)?; 84 | let bitslice = obj.as_mut_bitslice(); 85 | if bitslice.len() > 16 { 86 | return Err(Err::Error(X509Error::InvalidAttributes)); 87 | } 88 | if bitslice.is_empty() { 89 | return Ok((rem, Self { flags: 0 })); 90 | } 91 | bitslice.reverse(); 92 | let flags = bitslice.load_be::(); 93 | Ok((rem, Self { flags })) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/extensions/name_constraints.rs: -------------------------------------------------------------------------------- 1 | use asn1_rs::Sequence; 2 | 3 | use super::GeneralName; 4 | use crate::error::X509Error; 5 | 6 | ///
 7 | /// -- IMPLICIT tags
 8 | /// NameConstraints ::= SEQUENCE {
 9 | ///     permittedSubtrees       [0]     GeneralSubtrees OPTIONAL,
10 | ///     excludedSubtrees        [1]     GeneralSubtrees OPTIONAL }
11 | /// 
12 | #[derive(Clone, Debug, PartialEq, Sequence)] 13 | #[asn1(parse = "DER", encode = "")] 14 | #[error(X509Error)] 15 | pub struct NameConstraints<'a> { 16 | #[tag_implicit(0)] 17 | #[optional] 18 | pub permitted_subtrees: Option>>, 19 | 20 | #[tag_implicit(1)] 21 | #[optional] 22 | pub excluded_subtrees: Option>>, 23 | } 24 | 25 | /// Represents the structure used in the name constraints extensions. 26 | /// The fields minimum and maximum are not supported (openssl also has no support). 27 | /// 28 | ///
29 | /// -- IMPLICIT tags
30 | /// GeneralSubtree ::= SEQUENCE {
31 | ///     base                    GeneralName,
32 | ///     minimum         [0]     BaseDistance DEFAULT 0,
33 | ///     maximum         [1]     BaseDistance OPTIONAL }
34 | ///
35 | /// BaseDistance ::= INTEGER (0..MAX)
36 | /// 
37 | #[derive(Clone, Debug, PartialEq, Sequence)] 38 | #[asn1(parse = "DER", encode = "")] 39 | #[error(X509Error)] 40 | pub struct GeneralSubtree<'a> { 41 | pub base: GeneralName<'a>, 42 | 43 | #[tag_implicit(0)] 44 | #[default(0)] 45 | pub minimum: u32, 46 | 47 | #[tag_implicit(1)] 48 | #[optional] 49 | pub maximum: Option, 50 | } 51 | 52 | #[cfg(test)] 53 | mod tests { 54 | use asn1_rs::{DerParser, Input}; 55 | use hex_literal::hex; 56 | 57 | use crate::prelude::GeneralName; 58 | 59 | use super::NameConstraints; 60 | 61 | #[test] 62 | fn extension_name_constraints() { 63 | // permitted subtree with 1 item DNS:".example.com" 64 | let bytes = &hex!("30 12 A0 10 30 0E 82 0C 2E 65 78 61 6D 70 6C 65 2E 63 6F 6D"); 65 | 66 | let (rem, res) = NameConstraints::parse_der(Input::from(bytes)).expect("NameConstraints"); 67 | assert!(rem.is_empty()); 68 | assert!(res.permitted_subtrees.is_some()); 69 | let permitted0 = &res.permitted_subtrees.as_ref().unwrap()[0]; 70 | assert_eq!(permitted0.base, GeneralName::DNSName(".example.com")); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/extensions/ns_cert_type.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | use asn1_rs::{bitvec::field::BitField, BitString, DerParser, Tag, Tagged}; 4 | use nom::Err; 5 | 6 | use crate::error::X509Error; 7 | 8 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 9 | pub struct NSCertType(u8); 10 | 11 | // The value is a bit-string, where the individual bit positions are defined as: 12 | // 13 | // bit-0 SSL client - this cert is certified for SSL client authentication use 14 | // bit-1 SSL server - this cert is certified for SSL server authentication use 15 | // bit-2 S/MIME - this cert is certified for use by clients (New in PR3) 16 | // bit-3 Object Signing - this cert is certified for signing objects such as Java applets and plugins(New in PR3) 17 | // bit-4 Reserved - this bit is reserved for future use 18 | // bit-5 SSL CA - this cert is certified for issuing certs for SSL use 19 | // bit-6 S/MIME CA - this cert is certified for issuing certs for S/MIME use (New in PR3) 20 | // bit-7 Object Signing CA - this cert is certified for issuing certs for Object Signing (New in PR3) 21 | impl NSCertType { 22 | pub fn ssl_client(&self) -> bool { 23 | self.0 & 0x1 == 1 24 | } 25 | pub fn ssl_server(&self) -> bool { 26 | (self.0 >> 1) & 1 == 1 27 | } 28 | pub fn smime(&self) -> bool { 29 | (self.0 >> 2) & 1 == 1 30 | } 31 | pub fn object_signing(&self) -> bool { 32 | (self.0 >> 3) & 1 == 1 33 | } 34 | pub fn ssl_ca(&self) -> bool { 35 | (self.0 >> 5) & 1 == 1 36 | } 37 | pub fn smime_ca(&self) -> bool { 38 | (self.0 >> 6) & 1 == 1 39 | } 40 | pub fn object_signing_ca(&self) -> bool { 41 | (self.0 >> 7) & 1 == 1 42 | } 43 | } 44 | 45 | const NS_CERT_TYPE_FLAGS: &[&str] = &[ 46 | "SSL CLient", 47 | "SSL Server", 48 | "S/MIME", 49 | "Object Signing", 50 | "Reserved", 51 | "SSL CA", 52 | "S/MIME CA", 53 | "Object Signing CA", 54 | ]; 55 | 56 | impl fmt::Display for NSCertType { 57 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 58 | let mut s = String::new(); 59 | let mut acc = self.0; 60 | for flag_text in NS_CERT_TYPE_FLAGS { 61 | if acc & 1 != 0 { 62 | s = s + flag_text + ", "; 63 | } 64 | acc >>= 1; 65 | } 66 | s.pop(); 67 | s.pop(); 68 | f.write_str(&s) 69 | } 70 | } 71 | 72 | impl Tagged for NSCertType { 73 | const TAG: Tag = Tag::BitString; 74 | } 75 | 76 | impl<'i> DerParser<'i> for NSCertType { 77 | type Error = X509Error; 78 | 79 | fn from_der_content( 80 | header: &'_ asn1_rs::Header<'i>, 81 | input: asn1_rs::Input<'i>, 82 | ) -> nom::IResult, Self, Self::Error> { 83 | let (rem, mut obj) = BitString::from_der_content(header, input).map_err(Err::convert)?; 84 | let bitslice = obj.as_mut_bitslice(); 85 | if bitslice.len() > 8 { 86 | return Err(Err::Error(X509Error::InvalidAttributes)); 87 | } 88 | if bitslice.is_empty() { 89 | return Ok((rem, Self(0))); 90 | } 91 | bitslice.reverse(); 92 | let flags = bitslice.load::(); 93 | Ok((rem, Self(flags))) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/extensions/ns_comment.rs: -------------------------------------------------------------------------------- 1 | use asn1_rs::{DerParser, Ia5String, InnerError, Input}; 2 | use nom::{Err, IResult, Input as _}; 3 | 4 | use crate::error::X509Error; 5 | 6 | /// The value is an IA5String representing a comment 7 | /// that may be displayed to the user when the certificate is viewed 8 | pub fn parse_der_nscomment(input: Input) -> IResult { 9 | match Ia5String::parse_der(input.clone()) { 10 | Ok((rem, obj)) => { 11 | let s = obj 12 | .as_raw_str() 13 | .ok_or(Err::Error(X509Error::DerParser(InnerError::LifetimeError)))?; 14 | Ok((rem, s)) 15 | } 16 | Err(_) => { 17 | // Some implementations encode the comment directly, without 18 | // wrapping it in an IA5String 19 | if let Ok(s) = std::str::from_utf8(input.as_bytes2()) { 20 | Ok((input.take(input.input_len()), s)) 21 | } else { 22 | Err(Err::Error(X509Error::DerParser( 23 | InnerError::StringInvalidCharset, 24 | ))) 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/extensions/policy_constraints.rs: -------------------------------------------------------------------------------- 1 | use asn1_rs::Sequence; 2 | 3 | use crate::error::X509Error; 4 | 5 | use super::SkipCerts; 6 | 7 | ///
 8 | /// -- IMPLICIT tags
 9 | /// PolicyConstraints ::= SEQUENCE {
10 | ///     requireExplicitPolicy   [0]     SkipCerts OPTIONAL,
11 | ///     inhibitPolicyMapping    [1]     SkipCerts OPTIONAL }
12 | ///
13 | /// SkipCerts ::= INTEGER (0..MAX)
14 | /// 
15 | #[derive(Clone, Debug, PartialEq, Eq, Sequence)] 16 | #[asn1(parse = "DER", encode = "")] 17 | #[error(X509Error)] 18 | pub struct PolicyConstraints { 19 | #[tag_implicit(0)] 20 | #[optional] 21 | pub require_explicit_policy: Option, 22 | #[tag_implicit(1)] 23 | #[optional] 24 | pub inhibit_policy_mapping: Option, 25 | } 26 | -------------------------------------------------------------------------------- /src/extensions/policy_mappings.rs: -------------------------------------------------------------------------------- 1 | use crate::error::X509Error; 2 | use asn1_rs::{Alias, Oid, Sequence}; 3 | use std::collections::HashMap; 4 | 5 | ///
 6 | /// PolicyMappings ::= SEQUENCE SIZE (1..MAX) OF SEQUENCE {
 7 | ///  issuerDomainPolicy      CertPolicyId,
 8 | ///  subjectDomainPolicy     CertPolicyId }
 9 | /// 
10 | #[derive(Clone, Debug, PartialEq, Eq, Alias)] 11 | #[asn1(parse = "DER", encode = "")] 12 | #[error(X509Error)] 13 | pub struct PolicyMappings<'a>(pub Vec>); 14 | 15 | impl<'a> PolicyMappings<'a> { 16 | /// Returns a `HashMap` mapping `Oid` to the list of references to `Oid` 17 | /// 18 | /// If several names match the same `Oid`, they are merged in the same entry. 19 | pub fn as_hashmap(&self) -> HashMap, Vec<&Oid<'a>>> { 20 | // create the hashmap and merge entries with same OID 21 | let mut m: HashMap> = HashMap::new(); 22 | for desc in &self.0 { 23 | let PolicyMapping { 24 | issuer_domain_policy: left, 25 | subject_domain_policy: right, 26 | } = desc; 27 | if let Some(l) = m.get_mut(left) { 28 | l.push(right); 29 | } else { 30 | m.insert(left.clone(), vec![right]); 31 | } 32 | } 33 | m 34 | } 35 | 36 | /// Returns a `HashMap` mapping `Oid` to the list of `Oid` (consuming the input) 37 | /// 38 | /// If several names match the same `Oid`, they are merged in the same entry. 39 | pub fn into_hashmap(self) -> HashMap, Vec>> { 40 | let mut l = self.0; 41 | // create the hashmap and merge entries with same OID 42 | let mut m: HashMap> = HashMap::new(); 43 | for mapping in l.drain(..) { 44 | let PolicyMapping { 45 | issuer_domain_policy: left, 46 | subject_domain_policy: right, 47 | } = mapping; 48 | if let Some(general_names) = m.get_mut(&left) { 49 | general_names.push(right); 50 | } else { 51 | m.insert(left, vec![right]); 52 | } 53 | } 54 | m 55 | } 56 | } 57 | 58 | ///
59 | /// SEQUENCE {
60 | ///  issuerDomainPolicy      CertPolicyId,
61 | ///  subjectDomainPolicy     CertPolicyId }
62 | /// 
63 | #[derive(Clone, Debug, PartialEq, Eq, Sequence)] 64 | #[asn1(parse = "DER", encode = "")] 65 | #[error(X509Error)] 66 | pub struct PolicyMapping<'a> { 67 | pub issuer_domain_policy: Oid<'a>, 68 | pub subject_domain_policy: Oid<'a>, 69 | } 70 | 71 | impl<'a> PolicyMapping<'a> { 72 | pub const fn new(issuer_domain_policy: Oid<'a>, subject_domain_policy: Oid<'a>) -> Self { 73 | PolicyMapping { 74 | issuer_domain_policy, 75 | subject_domain_policy, 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/extensions/sct.rs: -------------------------------------------------------------------------------- 1 | //! Certificate transparency [RFC6962](https://datatracker.ietf.org/doc/html/rfc6962) 2 | //! 3 | //! Code borrowed from tls-parser crate (file ) 4 | 5 | use std::convert::TryInto; 6 | 7 | use asn1_rs::{DerParser, Error, Input}; 8 | use nom::bytes::streaming::take; 9 | use nom::combinator::{complete, map_parser}; 10 | use nom::multi::{length_data, many1}; 11 | use nom::number::streaming::{be_u16, be_u64, be_u8}; 12 | use nom::{Err, IResult, Parser as _}; 13 | 14 | use crate::error::X509Error; 15 | 16 | #[derive(Clone, Debug, PartialEq, Eq)] 17 | pub struct SignedCertificateTimestamp<'a> { 18 | pub version: CtVersion, 19 | pub id: CtLogID<'a>, 20 | pub timestamp: u64, 21 | pub extensions: CtExtensions<'a>, 22 | pub signature: DigitallySigned<'a>, 23 | } 24 | 25 | /// Certificate Transparency Version as defined in 26 | /// [RFC6962 Section 3.2](https://datatracker.ietf.org/doc/html/rfc6962#section-3.2) 27 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 28 | pub struct CtVersion(pub u8); 29 | 30 | impl CtVersion { 31 | pub const V1: CtVersion = CtVersion(0); 32 | } 33 | 34 | /// LogID as defined in 35 | /// [RFC6962 Section 3.2](https://datatracker.ietf.org/doc/html/rfc6962#section-3.2) 36 | #[derive(Clone, Debug, PartialEq, Eq)] 37 | pub struct CtLogID<'a> { 38 | pub key_id: &'a [u8; 32], 39 | } 40 | 41 | /// CtExtensions as defined in 42 | /// [RFC6962 Section 3.2](https://datatracker.ietf.org/doc/html/rfc6962#section-3.2) 43 | #[derive(Clone, Debug, PartialEq, Eq)] 44 | pub struct CtExtensions<'a>(pub &'a [u8]); 45 | 46 | #[derive(Clone, Debug, PartialEq, Eq)] 47 | pub struct DigitallySigned<'a> { 48 | pub hash_alg_id: u8, 49 | pub sign_alg_id: u8, 50 | pub data: &'a [u8], 51 | } 52 | 53 | /// Parses a list of Signed Certificate Timestamp entries 54 | pub fn parse_ct_signed_certificate_timestamp_list( 55 | input: Input, 56 | ) -> IResult, X509Error> { 57 | let (rem, b) = <&[u8]>::parse_der(input).map_err(Err::convert)?; 58 | 59 | let (b, sct_len) = be_u16(b)?; 60 | let (_, sct_list) = map_parser( 61 | take(sct_len as usize), 62 | many1(complete(parse_ct_signed_certificate_timestamp)), 63 | ) 64 | .parse(b) 65 | .map_err(Err::convert)?; 66 | 67 | Ok((rem, sct_list)) 68 | } 69 | 70 | /// Parses as single Signed Certificate Timestamp entry 71 | pub fn parse_ct_signed_certificate_timestamp( 72 | i: &[u8], 73 | ) -> IResult<&[u8], SignedCertificateTimestamp, Error> { 74 | map_parser( 75 | length_data(be_u16), 76 | parse_ct_signed_certificate_timestamp_content, 77 | ) 78 | .parse(i) 79 | } 80 | 81 | pub(crate) fn parse_ct_signed_certificate_timestamp_content( 82 | i: &[u8], 83 | ) -> IResult<&[u8], SignedCertificateTimestamp, Error> { 84 | let (i, version) = be_u8(i)?; 85 | let (i, id) = parse_log_id(i)?; 86 | let (i, timestamp) = be_u64(i)?; 87 | let (i, extensions) = parse_ct_extensions(i)?; 88 | let (i, signature) = parse_digitally_signed(i)?; 89 | let sct = SignedCertificateTimestamp { 90 | version: CtVersion(version), 91 | id, 92 | timestamp, 93 | extensions, 94 | signature, 95 | }; 96 | Ok((i, sct)) 97 | } 98 | 99 | // Safety: cannot fail, take() returns exactly 32 bytes 100 | fn parse_log_id(i: &[u8]) -> IResult<&[u8], CtLogID, Error> { 101 | let (i, key_id) = take(32usize)(i)?; 102 | Ok(( 103 | i, 104 | CtLogID { 105 | key_id: key_id 106 | .try_into() 107 | .expect("take(32) is in sync with key_id size"), 108 | }, 109 | )) 110 | } 111 | 112 | fn parse_ct_extensions(i: &[u8]) -> IResult<&[u8], CtExtensions, Error> { 113 | let (i, ext_len) = be_u16(i)?; 114 | let (i, ext_data) = take(ext_len as usize)(i)?; 115 | Ok((i, CtExtensions(ext_data))) 116 | } 117 | 118 | fn parse_digitally_signed(i: &[u8]) -> IResult<&[u8], DigitallySigned, Error> { 119 | let (i, hash_alg_id) = be_u8(i)?; 120 | let (i, sign_alg_id) = be_u8(i)?; 121 | let (i, data) = length_data(be_u16).parse(i)?; 122 | let signed = DigitallySigned { 123 | hash_alg_id, 124 | sign_alg_id, 125 | data, 126 | }; 127 | Ok((i, signed)) 128 | } 129 | -------------------------------------------------------------------------------- /src/extensions/subject_alt_name.rs: -------------------------------------------------------------------------------- 1 | use asn1_rs::Alias; 2 | 3 | use crate::error::X509Error; 4 | 5 | use super::{GeneralName, GeneralNames}; 6 | 7 | /// Subject Alternative Name 8 | /// 9 | /// Note: empty sequences are accepted 10 | /// 11 | ///
12 | /// SubjectAltName ::= GeneralNames
13 | ///
14 | /// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
15 | /// 
16 | #[derive(Clone, Debug, PartialEq, Alias)] 17 | #[asn1(parse = "DER", encode = "")] 18 | #[error(X509Error)] 19 | pub struct SubjectAlternativeName<'a>(pub GeneralNames<'a>); 20 | 21 | impl SubjectAlternativeName<'_> { 22 | pub fn general_names(&self) -> impl Iterator { 23 | self.0.iter() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/extensions/subject_info_access.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use asn1_rs::{Alias, Oid}; 4 | 5 | use crate::error::X509Error; 6 | 7 | use super::{AccessDescription, GeneralName}; 8 | 9 | ///
10 | /// SubjectInfoAccessSyntax  ::=
11 | ///         SEQUENCE SIZE (1..MAX) OF AccessDescription
12 | /// 
13 | #[derive(Clone, Debug, PartialEq, Alias)] 14 | #[asn1(parse = "DER", encode = "")] 15 | #[error(X509Error)] 16 | pub struct SubjectInfoAccess<'a>(pub Vec>); 17 | 18 | impl<'a> SubjectInfoAccess<'a> { 19 | /// Returns an iterator over the Access Descriptors 20 | pub fn iter(&self) -> impl Iterator> { 21 | self.0.iter() 22 | } 23 | 24 | /// Returns a `HashMap` mapping `Oid` to the list of references to `GeneralNames` 25 | /// 26 | /// If several names match the same `Oid`, they are merged in the same entry. 27 | pub fn as_hashmap(&self) -> HashMap, Vec<&GeneralName<'a>>> { 28 | // create the hashmap and merge entries with same OID 29 | let mut m: HashMap> = HashMap::new(); 30 | for desc in &self.0 { 31 | let AccessDescription { 32 | access_method: oid, 33 | access_location: gn, 34 | } = desc; 35 | if let Some(general_names) = m.get_mut(oid) { 36 | general_names.push(gn); 37 | } else { 38 | m.insert(oid.clone(), vec![gn]); 39 | } 40 | } 41 | m 42 | } 43 | 44 | /// Returns a `HashMap` mapping `Oid` to the list of `GeneralNames` (consuming the input) 45 | /// 46 | /// If several names match the same `Oid`, they are merged in the same entry. 47 | pub fn into_hashmap(self) -> HashMap, Vec>> { 48 | let mut aia_list = self.0; 49 | // create the hashmap and merge entries with same OID 50 | let mut m: HashMap> = HashMap::new(); 51 | for desc in aia_list.drain(..) { 52 | let AccessDescription { 53 | access_method: oid, 54 | access_location: gn, 55 | } = desc; 56 | if let Some(general_names) = m.get_mut(&oid) { 57 | general_names.push(gn); 58 | } else { 59 | m.insert(oid, vec![gn]); 60 | } 61 | } 62 | m 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/extensions/subject_key_identifier.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use std::ops::Deref; 3 | 4 | use asn1_rs::Alias; 5 | 6 | use crate::error::X509Error; 7 | use crate::x509::format_serial; 8 | 9 | ///
10 | /// SubjectKeyIdentifier ::= KeyIdentifier
11 | /// 
12 | #[derive(Clone, Debug, PartialEq, Alias)] 13 | #[asn1(parse = "DER", encode = "")] 14 | #[error(X509Error)] 15 | pub struct SubjectKeyIdentifier<'a>(pub KeyIdentifier<'a>); 16 | 17 | impl<'a> Deref for SubjectKeyIdentifier<'a> { 18 | type Target = KeyIdentifier<'a>; 19 | 20 | fn deref(&self) -> &Self::Target { 21 | &self.0 22 | } 23 | } 24 | 25 | //
26 | // KeyIdentifier ::= OCTET STRING
27 | // 
28 | #[derive(Clone, Debug, PartialEq, Eq, Alias)] 29 | #[asn1(parse = "DER", encode = "")] 30 | #[error(X509Error)] 31 | pub struct KeyIdentifier<'a>(pub &'a [u8]); 32 | 33 | impl KeyIdentifier<'_> { 34 | pub const fn is_empty(&self) -> bool { 35 | self.0.is_empty() 36 | } 37 | 38 | pub const fn len(&self) -> usize { 39 | self.0.len() 40 | } 41 | } 42 | 43 | impl<'a> Deref for KeyIdentifier<'a> { 44 | type Target = &'a [u8]; 45 | 46 | fn deref(&self) -> &Self::Target { 47 | &self.0 48 | } 49 | } 50 | 51 | impl<'a> AsRef<[u8]> for KeyIdentifier<'a> { 52 | fn as_ref(&self) -> &'a [u8] { 53 | self.0 54 | } 55 | } 56 | 57 | impl fmt::LowerHex for KeyIdentifier<'_> { 58 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 59 | let s = format_serial(self.0); 60 | f.write_str(&s) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE-MIT) 2 | //! [![Apache License 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](./LICENSE-APACHE) 3 | //! [![docs.rs](https://docs.rs/x509-parser/badge.svg)](https://docs.rs/x509-parser) 4 | //! [![crates.io](https://img.shields.io/crates/v/x509-parser.svg)](https://crates.io/crates/x509-parser) 5 | //! [![Download numbers](https://img.shields.io/crates/d/x509-parser.svg)](https://crates.io/crates/x509-parser) 6 | //! [![Github CI](https://github.com/rusticata/x509-parser/workflows/Continuous%20integration/badge.svg)](https://github.com/rusticata/x509-parser/actions) 7 | //! [![Minimum rustc version](https://img.shields.io/badge/rustc-1.67.1+-lightgray.svg)](#rust-version-requirements) 8 | //! 9 | //! # X.509 Parser 10 | //! 11 | //! A X.509 v3 ([RFC5280]) parser, implemented with the [nom](https://github.com/Geal/nom) 12 | //! parser combinator framework. 13 | //! 14 | //! It is written in pure Rust, fast, and makes extensive use of zero-copy. A lot of care is taken 15 | //! to ensure security and safety of this crate, including design (recursion limit, defensive 16 | //! programming), tests, and fuzzing. It also aims to be panic-free. 17 | //! 18 | //! The code is available on [Github](https://github.com/rusticata/x509-parser) 19 | //! and is part of the [Rusticata](https://github.com/rusticata) project. 20 | //! 21 | //! Certificates are usually encoded in two main formats: PEM (usually the most common format) or 22 | //! DER. A PEM-encoded certificate is a container, storing a DER object. See the 23 | //! [`pem`](pem/index.html) module for more documentation. 24 | //! 25 | //! To decode a DER-encoded certificate, the main parsing method is 26 | //! `X509Certificate::from_der` ( 27 | //! part of the [`FromDer`](prelude/trait.FromDer.html) trait 28 | //! ), which builds a 29 | //! [`X509Certificate`](certificate/struct.X509Certificate.html) object. 30 | //! 31 | //! An alternative method is to use [`X509CertificateParser`](certificate/struct.X509CertificateParser.html), 32 | //! which allows specifying parsing options (for example, not automatically parsing option contents). 33 | //! 34 | //! The returned objects for parsers follow the definitions of the RFC. This means that accessing 35 | //! fields is done by accessing struct members recursively. Some helper functions are provided, for 36 | //! example [`X509Certificate::issuer()`](certificate/struct.X509Certificate.html#method.issuer) returns the 37 | //! same as accessing `.tbs_certificate.issuer`. 38 | //! 39 | //! For PEM-encoded certificates, use the [`pem`](pem/index.html) module. 40 | //! 41 | //! This crate also provides visitor traits: [`X509CertificateVisitor`](crate::visitor::X509CertificateVisitor). 42 | //! 43 | //! # Examples 44 | //! 45 | //! Parsing a certificate in DER format: 46 | //! 47 | //! ```rust 48 | //! use x509_parser::prelude::*; 49 | //! 50 | //! static IGCA_DER: &[u8] = include_bytes!("../assets/IGC_A.der"); 51 | //! 52 | //! # fn main() { 53 | //! let res = X509Certificate::from_der(IGCA_DER); 54 | //! match res { 55 | //! Ok((rem, cert)) => { 56 | //! assert!(rem.is_empty()); 57 | //! // 58 | //! assert_eq!(cert.version(), X509Version::V3); 59 | //! }, 60 | //! _ => panic!("x509 parsing failed: {:?}", res), 61 | //! } 62 | //! # } 63 | //! ``` 64 | //! 65 | //! To parse a CRL and print information about revoked certificates: 66 | //! 67 | //! ```rust 68 | //! # use x509_parser::prelude::*; 69 | //! # 70 | //! # static DER: &[u8] = include_bytes!("../assets/example.crl"); 71 | //! # 72 | //! # fn main() { 73 | //! let res = CertificateRevocationList::from_der(DER); 74 | //! match res { 75 | //! Ok((_rem, crl)) => { 76 | //! for revoked in crl.iter_revoked_certificates() { 77 | //! println!("Revoked certificate serial: {}", revoked.raw_serial_as_string()); 78 | //! println!(" Reason: {}", revoked.reason_code().unwrap_or_default().1); 79 | //! } 80 | //! }, 81 | //! _ => panic!("CRL parsing failed: {:?}", res), 82 | //! } 83 | //! # } 84 | //! ``` 85 | //! 86 | //! See also `examples/print-cert.rs`. 87 | //! 88 | //! # Features 89 | //! 90 | //! - The `verify` feature adds support for (cryptographic) signature verification, based on `ring`. 91 | //! It adds the 92 | //! [`X509Certificate::verify_signature()`](certificate/struct.X509Certificate.html#method.verify_signature) 93 | //! to `X509Certificate`. 94 | //! 95 | //! ```rust 96 | //! # #[cfg(feature = "verify")] 97 | //! # use x509_parser::certificate::X509Certificate; 98 | //! /// Cryptographic signature verification: returns true if certificate was signed by issuer 99 | //! #[cfg(feature = "verify")] 100 | //! pub fn check_signature(cert: &X509Certificate<'_>, issuer: &X509Certificate<'_>) -> bool { 101 | //! let issuer_public_key = issuer.public_key(); 102 | //! cert 103 | //! .verify_signature(Some(issuer_public_key)) 104 | //! .is_ok() 105 | //! } 106 | //! ``` 107 | //! 108 | //! - The `validate` features add methods to run more validation functions on the certificate structure 109 | //! and values using the [`Validate`](validate/trait.Validate.html) trait. 110 | //! It does not validate any cryptographic parameter (see `verify` above). 111 | //! 112 | //! ## Rust version requirements 113 | //! 114 | //! `x509-parser` requires **Rustc version 1.67.1 or greater**, based on der-parser 115 | //! dependencies and for proc-macro attributes support. 116 | //! 117 | //! [RFC5280]: https://tools.ietf.org/html/rfc5280 118 | 119 | #![deny(/*missing_docs,*/ 120 | unstable_features, 121 | unused_import_braces, unused_qualifications)] 122 | #![warn( 123 | missing_debug_implementations, 124 | /* missing_docs, 125 | rust_2018_idioms,*/ 126 | unreachable_pub 127 | )] 128 | #![forbid(unsafe_code)] 129 | #![deny(rustdoc::broken_intra_doc_links)] 130 | #![doc(test( 131 | no_crate_inject, 132 | attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) 133 | ))] 134 | #![cfg_attr(docsrs, feature(doc_cfg))] 135 | 136 | pub mod certificate; 137 | pub mod certification_request; 138 | pub mod cri_attributes; 139 | pub mod error; 140 | pub mod extensions; 141 | pub mod objects; 142 | pub mod parser_utils; 143 | pub mod pem; 144 | pub mod prelude; 145 | pub mod public_key; 146 | pub mod revocation_list; 147 | pub mod signature_algorithm; 148 | pub mod signature_value; 149 | pub mod time; 150 | #[cfg(feature = "validate")] 151 | #[cfg_attr(docsrs, doc(cfg(feature = "validate")))] 152 | pub mod validate; 153 | #[cfg(feature = "verify")] 154 | #[cfg_attr(docsrs, doc(cfg(feature = "verify")))] 155 | pub mod verify; 156 | pub mod visitor; 157 | pub mod x509; 158 | 159 | // reexports 160 | pub use asn1_rs; 161 | pub use asn1_rs::num_bigint; 162 | pub use nom; 163 | pub use oid_registry; 164 | 165 | use asn1_rs::FromDer; 166 | use certificate::X509Certificate; 167 | use error::X509Result; 168 | use revocation_list::CertificateRevocationList; 169 | 170 | /// Parse a **DER-encoded** X.509 Certificate, and return the remaining of the input and the built 171 | /// object. 172 | /// 173 | /// 174 | /// This function is an alias to [X509Certificate::from_der](certificate::X509Certificate::from_der). See this function 175 | /// for more information. 176 | /// 177 | /// For PEM-encoded certificates, use the [`pem`](pem/index.html) module. 178 | #[inline] 179 | pub fn parse_x509_certificate(i: &[u8]) -> X509Result { 180 | X509Certificate::from_der(i) 181 | } 182 | 183 | /// Parse a DER-encoded X.509 v2 CRL, and return the remaining of the input and the built 184 | /// object. 185 | /// 186 | /// This function is an alias to [CertificateRevocationList::from_der](revocation_list::CertificateRevocationList::from_der). See this function 187 | /// for more information. 188 | #[inline] 189 | pub fn parse_x509_crl(i: &[u8]) -> X509Result { 190 | CertificateRevocationList::from_der(i) 191 | } 192 | 193 | /// Parse a DER-encoded X.509 Certificate, and return the remaining of the input and the built 194 | #[deprecated( 195 | since = "0.9.0", 196 | note = "please use `parse_x509_certificate` or `X509Certificate::from_der` instead" 197 | )] 198 | #[inline] 199 | pub fn parse_x509_der(i: &[u8]) -> X509Result { 200 | X509Certificate::from_der(i) 201 | } 202 | 203 | /// Parse a DER-encoded X.509 v2 CRL, and return the remaining of the input and the built 204 | /// object. 205 | #[deprecated( 206 | since = "0.9.0", 207 | note = "please use `parse_x509_crl` or `CertificateRevocationList::from_der` instead" 208 | )] 209 | #[inline] 210 | pub fn parse_crl_der(i: &[u8]) -> X509Result { 211 | CertificateRevocationList::from_der(i) 212 | } 213 | -------------------------------------------------------------------------------- /src/objects.rs: -------------------------------------------------------------------------------- 1 | //! X.509 helper objects definitions and registry 2 | //! 3 | //! All OID objects and definitions are now stored in the [oid-registry](https://crates.io/crates/oid-registry) crate. 4 | //! 5 | //! This crate is re-exporting `oid-registry`, so to access the OID constants the 6 | //! `x509_parser::oid_oid_registry` namespace can be used (see example below). 7 | //! 8 | //! ## Example 9 | //! 10 | //! To get the short name for a given OID: 11 | //! 12 | //! ```rust 13 | //! use x509_parser::objects::*; 14 | //! use x509_parser::oid_registry::*; 15 | //! 16 | //! let oid = &OID_X509_COMMON_NAME; 17 | //! let sn = oid2sn(oid, oid_registry()); 18 | //! assert_eq!(sn, Ok("commonName")); 19 | //! ``` 20 | 21 | use crate::error::NidError; 22 | use asn1_rs::oid; 23 | use lazy_static::lazy_static; 24 | use oid_registry::*; 25 | use std::collections::HashMap; 26 | 27 | lazy_static! { 28 | static ref OID_REGISTRY: OidRegistry<'static> = { 29 | let mut reg = OidRegistry::default().with_all_crypto().with_x509(); 30 | // OIDs not in the default registry can be added here 31 | let entry = OidEntry::new("id-mgf1", "Mask Generator Function 1 (MGF1)"); 32 | reg.insert(oid! {1.2.840.113549.1.1.8}, entry); 33 | reg 34 | }; 35 | static ref ABBREV_MAP: HashMap, &'static str> = { 36 | let mut m = HashMap::new(); 37 | m.insert(OID_X509_COMMON_NAME, "CN"); 38 | m.insert(OID_X509_COUNTRY_NAME, "C"); 39 | m.insert(OID_X509_LOCALITY_NAME, "L"); 40 | m.insert(OID_X509_STATE_OR_PROVINCE_NAME, "ST"); 41 | m.insert(OID_X509_ORGANIZATION_NAME, "O"); 42 | m.insert(OID_X509_ORGANIZATIONAL_UNIT, "OU"); 43 | m.insert(OID_DOMAIN_COMPONENT, "DC"); 44 | m.insert(OID_PKCS9_EMAIL_ADDRESS, "Email"); 45 | m 46 | }; 47 | } 48 | 49 | /// Return the abbreviation (for ex. CN for Common Name), or if not found, the OID short name 50 | pub fn oid2abbrev<'a>(oid: &'a Oid, registry: &'a OidRegistry) -> Result<&'a str, NidError> { 51 | if let Some(abbrev) = ABBREV_MAP.get(oid) { 52 | return Ok(abbrev); 53 | } 54 | registry.get(oid).map(|entry| entry.sn()).ok_or(NidError) 55 | } 56 | 57 | /// Returns the short name corresponding to the OID 58 | pub fn oid2sn<'a>(oid: &'a Oid, registry: &'a OidRegistry) -> Result<&'a str, NidError> { 59 | registry.get(oid).map(|o| o.sn()).ok_or(NidError) 60 | } 61 | 62 | /// Returns the description corresponding to the OID 63 | pub fn oid2description<'a>(oid: &'a Oid, registry: &'a OidRegistry) -> Result<&'a str, NidError> { 64 | registry.get(oid).map(|o| o.description()).ok_or(NidError) 65 | } 66 | 67 | /// Return a reference to the default registry of known OIDs 68 | pub fn oid_registry() -> &'static OidRegistry<'static> { 69 | &OID_REGISTRY 70 | } 71 | 72 | #[cfg(test)] 73 | mod tests { 74 | use super::*; 75 | use asn1_rs::oid; 76 | 77 | // This test is meant to check syntax of pattern matching with OID objects 78 | #[test] 79 | fn test_oid_match() { 80 | let oid = oid!(1.2.840 .113549 .1 .1 .5); 81 | if oid == OID_PKCS1_SHA1WITHRSA { 82 | // ok 83 | } 84 | // matching is not possible with Cow constants in pattern, 85 | // see https://rust-lang.github.io/rfcs/1445-restrict-constants-in-patterns.html 86 | // 87 | // match oid { 88 | // OID_PKCS1_SHA1WITHRSA => (), 89 | // _ => (), 90 | // } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/parser_utils.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | 3 | use asn1_rs::{BerError, Header, Input}; 4 | use nom::IResult; 5 | 6 | pub fn get_span<'a>( 7 | header: &Header<'a>, 8 | input: Input<'a>, 9 | ) -> IResult, Range, BerError>> { 10 | let start = header.raw_header().map(|h| h.start()).unwrap_or(0); 11 | let end = input.end(); 12 | Ok((input, Range { start, end })) 13 | } 14 | -------------------------------------------------------------------------------- /src/pem.rs: -------------------------------------------------------------------------------- 1 | //! Decoding functions for PEM-encoded data 2 | //! 3 | //! A PEM object is a container, which can store (amongst other formats) a public X.509 4 | //! Certificate, or a CRL, etc. It contains only printable characters. 5 | //! PEM-encoded binary data is essentially a beginning and matching end tag that encloses 6 | //! base64-encoded binary data (see: 7 | //! ). 8 | //! 9 | //! # Examples 10 | //! 11 | //! To parse a certificate in PEM format, first create the `Pem` object, then decode 12 | //! contents: 13 | //! 14 | //! ```rust,no_run 15 | //! use x509_parser::pem::Pem; 16 | //! use x509_parser::x509::X509Version; 17 | //! 18 | //! static IGCA_PEM: &str = "../assets/IGC_A.pem"; 19 | //! 20 | //! # fn main() { 21 | //! let data = std::fs::read(IGCA_PEM).expect("Could not read file"); 22 | //! for pem in Pem::iter_from_buffer(&data) { 23 | //! let pem = pem.expect("Reading next PEM block failed"); 24 | //! let x509 = pem.parse_x509().expect("X.509: decoding DER failed"); 25 | //! assert_eq!(x509.tbs_certificate.version, X509Version::V3); 26 | //! } 27 | //! # } 28 | //! ``` 29 | //! 30 | //! This is the most direct method to parse PEM data. 31 | //! 32 | //! Another method to parse the certificate is to use `parse_x509_pem`: 33 | //! 34 | //! ```rust,no_run 35 | //! use x509_parser::pem::parse_x509_pem; 36 | //! use x509_parser::parse_x509_certificate; 37 | //! 38 | //! static IGCA_PEM: &[u8] = include_bytes!("../assets/IGC_A.pem"); 39 | //! 40 | //! # fn main() { 41 | //! let res = parse_x509_pem(IGCA_PEM); 42 | //! match res { 43 | //! Ok((rem, pem)) => { 44 | //! assert!(rem.is_empty()); 45 | //! // 46 | //! assert_eq!(pem.label, String::from("CERTIFICATE")); 47 | //! // 48 | //! let res_x509 = parse_x509_certificate(&pem.contents); 49 | //! assert!(res_x509.is_ok()); 50 | //! }, 51 | //! _ => panic!("PEM parsing failed: {:?}", res), 52 | //! } 53 | //! # } 54 | //! ``` 55 | //! 56 | //! Note that all methods require to store the `Pem` object in a variable, mainly because decoding 57 | //! the PEM object requires allocation of buffers, and that the lifetime of X.509 certificates will 58 | //! be bound to these buffers. 59 | 60 | use crate::certificate::X509Certificate; 61 | use crate::error::{PEMError, X509Error}; 62 | use crate::parse_x509_certificate; 63 | use nom::{Err, IResult}; 64 | use std::io::{BufRead, Cursor, ErrorKind, Seek}; 65 | 66 | /// Representation of PEM data 67 | #[derive(Clone, PartialEq, Eq, Debug)] 68 | pub struct Pem { 69 | /// The PEM label 70 | pub label: String, 71 | /// The PEM decoded data 72 | pub contents: Vec, 73 | } 74 | 75 | #[deprecated(since = "0.8.3", note = "please use `parse_x509_pem` instead")] 76 | pub fn pem_to_der(i: &[u8]) -> IResult<&[u8], Pem, PEMError> { 77 | parse_x509_pem(i) 78 | } 79 | 80 | /// Read a PEM-encoded structure, and decode the base64 data 81 | /// 82 | /// Return a structure describing the PEM object: the enclosing tag, and the data. 83 | /// Allocates a new buffer for the decoded data. 84 | /// 85 | /// Note that only the *first* PEM block is decoded. To iterate all blocks from PEM data, 86 | /// use [`Pem::iter_from_buffer`]. 87 | /// 88 | /// For X.509 (`CERTIFICATE` tag), the data is a certificate, encoded in DER. To parse the 89 | /// certificate content, use `Pem::parse_x509` or `parse_x509_certificate`. 90 | pub fn parse_x509_pem(i: &[u8]) -> IResult<&'_ [u8], Pem, PEMError> { 91 | let reader = Cursor::new(i); 92 | let res = Pem::read(reader); 93 | match res { 94 | Ok((pem, bytes_read)) => Ok((&i[bytes_read..], pem)), 95 | Err(e) => Err(Err::Error(e)), 96 | } 97 | } 98 | 99 | impl Pem { 100 | /// Read the next PEM-encoded structure, and decode the base64 data 101 | /// 102 | /// Returns the certificate (encoded in DER) and the number of bytes read. 103 | /// Allocates a new buffer for the decoded data. 104 | /// 105 | /// Note that a PEM file can contain multiple PEM blocks. This function returns the 106 | /// *first* decoded object, starting from the current reader position. 107 | /// To get all objects, call this function repeatedly until `PEMError::MissingHeader` 108 | /// is returned. 109 | /// 110 | /// # Examples 111 | /// ``` 112 | /// let file = std::fs::File::open("assets/certificate.pem").unwrap(); 113 | /// let subject = x509_parser::pem::Pem::read(std::io::BufReader::new(file)) 114 | /// .unwrap().0 115 | /// .parse_x509().unwrap() 116 | /// .tbs_certificate.subject.to_string(); 117 | /// assert_eq!(subject, "CN=lists.for-our.info"); 118 | /// ``` 119 | pub fn read(mut r: impl BufRead + Seek) -> Result<(Pem, usize), PEMError> { 120 | let mut line = String::new(); 121 | let label = loop { 122 | let num_bytes = match r.read_line(&mut line) { 123 | Ok(line) => line, 124 | Err(e) if e.kind() == ErrorKind::InvalidData => { 125 | // some tools put invalid UTF-8 data in PEM comment section. Ignore line 126 | continue; 127 | } 128 | Err(e) => { 129 | return Err(e.into()); 130 | } 131 | }; 132 | if num_bytes == 0 { 133 | // EOF 134 | return Err(PEMError::MissingHeader); 135 | } 136 | if !line.starts_with("-----BEGIN ") { 137 | line.clear(); 138 | continue; 139 | } 140 | let v: Vec<&str> = line.split("-----").collect(); 141 | if v.len() < 3 || !v[0].is_empty() { 142 | return Err(PEMError::InvalidHeader); 143 | } 144 | let label = v[1].strip_prefix("BEGIN ").ok_or(PEMError::InvalidHeader)?; 145 | break label; 146 | }; 147 | let label = label.split('-').next().ok_or(PEMError::InvalidHeader)?; 148 | let mut s = String::new(); 149 | loop { 150 | let mut l = String::new(); 151 | let num_bytes = r.read_line(&mut l)?; 152 | if num_bytes == 0 { 153 | return Err(PEMError::IncompletePEM); 154 | } 155 | if l.starts_with("-----END ") { 156 | // finished reading 157 | break; 158 | } 159 | s.push_str(l.trim_end()); 160 | } 161 | 162 | let contents = data_encoding::BASE64 163 | .decode(s.as_bytes()) 164 | .or(Err(PEMError::Base64DecodeError))?; 165 | let pem = Pem { 166 | label: label.to_string(), 167 | contents, 168 | }; 169 | Ok((pem, r.stream_position()? as usize)) 170 | } 171 | 172 | /// Decode the PEM contents into a X.509 object 173 | pub fn parse_x509(&self) -> Result> { 174 | parse_x509_certificate(&self.contents).map(|(_, x509)| x509) 175 | } 176 | 177 | /// Returns an iterator over the PEM-encapsulated parts of a buffer 178 | /// 179 | /// Only the sections enclosed in blocks starting with `-----BEGIN xxx-----` 180 | /// and ending with `-----END xxx-----` will be considered. 181 | /// Lines before, between or after such blocks will be ignored. 182 | /// 183 | /// The iterator is fallible: `next()` returns a `Result` object. 184 | /// An error indicates a block is present but invalid. 185 | /// 186 | /// If the buffer does not contain any block, iterator will be empty. 187 | pub fn iter_from_buffer(i: &[u8]) -> PemIterator> { 188 | let reader = Cursor::new(i); 189 | PemIterator { reader } 190 | } 191 | 192 | /// Returns an iterator over the PEM-encapsulated parts of a reader 193 | /// 194 | /// Only the sections enclosed in blocks starting with `-----BEGIN xxx-----` 195 | /// and ending with `-----END xxx-----` will be considered. 196 | /// Lines before, between or after such blocks will be ignored. 197 | /// 198 | /// The iterator is fallible: `next()` returns a `Result` object. 199 | /// An error indicates a block is present but invalid. 200 | /// 201 | /// If the reader does not contain any block, iterator will be empty. 202 | pub fn iter_from_reader(reader: R) -> PemIterator { 203 | PemIterator { reader } 204 | } 205 | } 206 | 207 | /// Iterator over PEM-encapsulated blocks 208 | /// 209 | /// Only the sections enclosed in blocks starting with `-----BEGIN xxx-----` 210 | /// and ending with `-----END xxx-----` will be considered. 211 | /// Lines before, between or after such blocks will be ignored. 212 | /// 213 | /// The iterator is fallible: `next()` returns a `Result` object. 214 | /// An error indicates a block is present but invalid. 215 | /// 216 | /// If the buffer does not contain any block, iterator will be empty. 217 | #[allow(missing_debug_implementations)] 218 | pub struct PemIterator { 219 | reader: Reader, 220 | } 221 | 222 | impl Iterator for PemIterator { 223 | type Item = Result; 224 | 225 | fn next(&mut self) -> Option { 226 | if let Ok(&[]) = self.reader.fill_buf() { 227 | return None; 228 | } 229 | let reader = self.reader.by_ref(); 230 | let r = Pem::read(reader).map(|(pem, _)| pem); 231 | if let Err(PEMError::MissingHeader) = r { 232 | None 233 | } else { 234 | Some(r) 235 | } 236 | } 237 | } 238 | 239 | #[cfg(test)] 240 | mod tests { 241 | use super::*; 242 | 243 | #[test] 244 | fn read_pem_from_file() { 245 | let file = std::io::BufReader::new(std::fs::File::open("assets/certificate.pem").unwrap()); 246 | let subject = Pem::read(file) 247 | .unwrap() 248 | .0 249 | .parse_x509() 250 | .unwrap() 251 | .tbs_certificate 252 | .subject 253 | .to_string(); 254 | assert_eq!(subject, "CN=lists.for-our.info"); 255 | } 256 | 257 | #[test] 258 | fn pem_multi_word_label() { 259 | const PEM_BYTES: &[u8] = 260 | b"-----BEGIN MULTI WORD LABEL-----\n-----END MULTI WORD LABEL-----"; 261 | let (_, pem) = parse_x509_pem(PEM_BYTES).expect("should parse pem"); 262 | assert_eq!(pem.label, "MULTI WORD LABEL"); 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! A "prelude" for users of the x509-parser crate. 2 | 3 | pub use crate::certificate::*; 4 | pub use crate::certification_request::*; 5 | pub use crate::cri_attributes::*; 6 | pub use crate::error::*; 7 | pub use crate::extensions::*; 8 | pub use crate::objects::*; 9 | pub use crate::pem::*; 10 | pub use crate::revocation_list::*; 11 | pub use crate::time::*; 12 | #[cfg(feature = "validate")] 13 | pub use crate::validate::*; 14 | pub use crate::x509::*; 15 | pub use crate::*; 16 | 17 | pub use asn1_rs::{DerParser, FromDer}; 18 | -------------------------------------------------------------------------------- /src/public_key.rs: -------------------------------------------------------------------------------- 1 | use crate::error::*; 2 | use asn1_rs::{DerParser, FromDer, InnerError, Input, Integer}; 3 | use nom::{Err, IResult}; 4 | 5 | /// Public Key value 6 | #[derive(Debug, PartialEq, Eq)] 7 | pub enum PublicKey<'a> { 8 | RSA(RSAPublicKey<'a>), 9 | EC(ECPoint<'a>), 10 | /// DSAPublicKey ::= INTEGER -- public key, Y (RFC 3279) 11 | DSA(&'a [u8]), 12 | /// GostR3410-94-PublicKey ::= OCTET STRING -- public key, Y (RFC 4491) 13 | GostR3410(&'a [u8]), 14 | /// GostR3410-2012-256-PublicKey ::= OCTET STRING (64), 15 | /// GostR3410-2012-512-PublicKey ::= OCTET STRING (128). (RFC 4491-bis) 16 | GostR3410_2012(&'a [u8]), 17 | 18 | Unknown(&'a [u8]), 19 | } 20 | 21 | impl PublicKey<'_> { 22 | /// Return the key size (in bits) or 0 23 | pub fn key_size(&self) -> usize { 24 | match self { 25 | Self::EC(ec) => ec.key_size(), 26 | Self::RSA(rsa) => rsa.key_size(), 27 | Self::DSA(y) | Self::GostR3410(y) => y.len() * 8, 28 | _ => 0, 29 | } 30 | } 31 | } 32 | 33 | /// RSA public Key, defined in rfc3279 34 | #[derive(Debug, PartialEq, Eq)] 35 | pub struct RSAPublicKey<'a> { 36 | /// Raw bytes of the modulus 37 | /// 38 | /// This possibly includes a leading 0 if the MSB is 1 39 | pub modulus: &'a [u8], 40 | /// Raw bytes of the exponent 41 | /// 42 | /// This possibly includes a leading 0 if the MSB is 1 43 | pub exponent: &'a [u8], 44 | } 45 | 46 | impl RSAPublicKey<'_> { 47 | /// Attempt to convert exponent to u64 48 | /// 49 | /// Returns an error if integer is too large, empty, or negative 50 | pub fn try_exponent(&self) -> Result { 51 | let mut buf = [0u8; 8]; 52 | if self.exponent.is_empty() || self.exponent[0] & 0x80 != 0 || self.exponent.len() > 8 { 53 | return Err(X509Error::InvalidNumber); 54 | } 55 | buf[8_usize.saturating_sub(self.exponent.len())..].copy_from_slice(self.exponent); 56 | let int = ::from_be_bytes(buf); 57 | Ok(int) 58 | } 59 | 60 | /// Return the key size (in bits) or 0 61 | pub fn key_size(&self) -> usize { 62 | if !self.modulus.is_empty() && self.modulus[0] & 0x80 == 0 { 63 | // XXX len must substract leading zeroes 64 | let modulus = &self.modulus[1..]; 65 | 8 * modulus.len() 66 | } else { 67 | 0 68 | } 69 | } 70 | } 71 | 72 | fn parse_rsa_key(bytes: &[u8]) -> IResult<&[u8], RSAPublicKey, X509Error> { 73 | let input = Input::from(bytes); 74 | let (rem, (o1, o2)) = <(Integer, Integer)>::parse_der(input).map_err(Err::convert)?; 75 | let modulus = o1 76 | .as_raw_slice() 77 | .ok_or(Err::Error(InnerError::LifetimeError.into()))?; 78 | let exponent = o2 79 | .as_raw_slice() 80 | .ok_or(Err::Error(InnerError::LifetimeError.into()))?; 81 | let key = RSAPublicKey { modulus, exponent }; 82 | Ok((rem.as_bytes2(), key)) 83 | } 84 | 85 | impl<'a> FromDer<'a, X509Error> for RSAPublicKey<'a> { 86 | fn from_der(bytes: &'a [u8]) -> X509Result<'a, Self> { 87 | parse_rsa_key(bytes).map_err(|_| Err::Error(X509Error::InvalidSPKI)) 88 | } 89 | } 90 | 91 | /// Elliptic Curve point, as defined in [RFC5480](https://datatracker.ietf.org/doc/html/rfc5480) 92 | #[derive(Debug, PartialEq, Eq)] 93 | pub struct ECPoint<'a> { 94 | data: &'a [u8], 95 | } 96 | 97 | impl<'a> ECPoint<'a> { 98 | /// EC Point content (See Standards for Efficient Cryptography Group (SECG), "SEC1: Elliptic Curve Cryptography") 99 | pub fn data(&'a self) -> &'a [u8] { 100 | self.data 101 | } 102 | 103 | /// Return the key size (in bits) or 0 104 | pub fn key_size(&self) -> usize { 105 | match self.data { 106 | [] => { 107 | // empty 108 | 0 109 | } 110 | [4, rem @ ..] => { 111 | // uncompressed 112 | rem.len() * 8 / 2 113 | } 114 | [2..=3, rem @ ..] => { 115 | // compressed 116 | rem.len() * 8 117 | } 118 | _ => { 119 | // invalid 120 | 0 121 | } 122 | } 123 | } 124 | } 125 | 126 | impl<'a> From<&'a [u8]> for ECPoint<'a> { 127 | fn from(data: &'a [u8]) -> Self { 128 | ECPoint { data } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/signature_algorithm.rs: -------------------------------------------------------------------------------- 1 | use crate::error::X509Error; 2 | use crate::x509::AlgorithmIdentifier; 3 | use asn1_rs::{oid, Any, CheckDerConstraints, DerAutoDerive, DerParser, FromDer, Sequence}; 4 | use core::convert::TryFrom; 5 | use oid_registry::*; 6 | 7 | #[allow(non_camel_case_types)] 8 | #[derive(Debug, PartialEq)] 9 | pub enum SignatureAlgorithm<'a> { 10 | RSA, 11 | RSASSA_PSS(Box>), 12 | RSAAES_OAEP(Box>), 13 | DSA, 14 | ECDSA, 15 | ED25519, 16 | } 17 | 18 | impl<'a, 'b> TryFrom<&'b AlgorithmIdentifier<'a>> for SignatureAlgorithm<'a> { 19 | type Error = X509Error; 20 | 21 | fn try_from(value: &'b AlgorithmIdentifier<'a>) -> Result { 22 | if value.algorithm.starts_with(&oid! {1.2.840.113549.1.1}) { 23 | // children of PKCS1 are all RSA 24 | // test if RSASSA-PSS 25 | if value.algorithm == OID_PKCS1_RSASSAPSS { 26 | let params = match value.parameters.as_ref() { 27 | Some(any) => any, 28 | None => return Err(X509Error::InvalidSignatureValue), 29 | }; 30 | let (_, params) = 31 | RsaSsaPssParams::from_der_content(¶ms.header, params.data.clone()) 32 | .map_err(|_| X509Error::InvalidSignatureValue)?; 33 | Ok(SignatureAlgorithm::RSASSA_PSS(Box::new(params))) 34 | } else { 35 | // rfc3279#section-2.2.1: the parameters component of that type SHALL be 36 | // the ASN.1 type NULL 37 | // We could enforce presence of NULL, but that would make a strict parser 38 | // so it would best go to a verifier. 39 | Ok(SignatureAlgorithm::RSA) 40 | } 41 | } else if test_ecdsa_oid(&value.algorithm) { 42 | // parameter should be NULL - see above 43 | Ok(SignatureAlgorithm::ECDSA) 44 | } else if value.algorithm.starts_with(&oid! {1.2.840.10040.4}) { 45 | // parameter should be NULL - see above 46 | Ok(SignatureAlgorithm::DSA) 47 | } else if value.algorithm == OID_SIG_ED25519 { 48 | Ok(SignatureAlgorithm::ED25519) 49 | } else if value.algorithm == oid! {1.2.840.113549.1.1.7} { 50 | let params = match value.parameters.as_ref() { 51 | Some(any) => any, 52 | None => return Err(X509Error::InvalidSignatureValue), 53 | }; 54 | let (_, params) = 55 | RsaAesOaepParams::from_der_content(¶ms.header, params.data.clone()) 56 | .map_err(|_| X509Error::InvalidSignatureValue)?; 57 | Ok(SignatureAlgorithm::RSAAES_OAEP(Box::new(params))) 58 | } else { 59 | if cfg!(debug_assertions) { 60 | // TODO: remove debug 61 | eprintln!("bad Signature AlgorithmIdentifier: {}", value.algorithm); 62 | } 63 | Err(X509Error::InvalidSignatureValue) 64 | } 65 | } 66 | } 67 | 68 | #[inline] 69 | fn test_ecdsa_oid(oid: &Oid) -> bool { 70 | // test if oid is a child from {ansi-x962 signatures} 71 | oid.starts_with(&oid! {1.2.840.10045.4}) 72 | } 73 | 74 | /// RSASSA-PSS public keys [RFC4055](https://www.rfc-editor.org/rfc/rfc4055.html) 75 | /// 76 | ///
 77 | /// -- EXPLICIT tags
 78 | /// RSASSA-PSS-params  ::=  SEQUENCE  {
 79 | ///     hashAlgorithm      [0] HashAlgorithm DEFAULT
 80 | ///                               sha1Identifier,
 81 | ///     maskGenAlgorithm   [1] MaskGenAlgorithm DEFAULT
 82 | ///                               mgf1SHA1Identifier,
 83 | ///     saltLength         [2] INTEGER DEFAULT 20,
 84 | ///     trailerField       [3] INTEGER DEFAULT 1  }
 85 | /// 
86 | #[derive(Debug, PartialEq, Sequence)] 87 | #[asn1(parse = "DER", encode = "")] 88 | #[error(X509Error)] 89 | pub struct RsaSsaPssParams<'a> { 90 | #[tag_explicit(0)] 91 | #[optional] 92 | hash_alg: Option>, 93 | #[tag_explicit(1)] 94 | #[optional] 95 | mask_gen_algorithm: Option>, 96 | #[tag_explicit(2)] 97 | #[optional] 98 | salt_length: Option, 99 | #[tag_explicit(3)] 100 | #[optional] 101 | trailer_field: Option, 102 | } 103 | 104 | impl<'a> RsaSsaPssParams<'a> { 105 | /// Get a reference to the rsa ssa pss params's hash algorithm. 106 | pub fn hash_algorithm(&self) -> Option<&AlgorithmIdentifier> { 107 | self.hash_alg.as_ref() 108 | } 109 | 110 | /// Return the hash algorithm OID, or SHA1 if absent (RFC4055) 111 | pub fn hash_algorithm_oid(&self) -> &'a Oid { 112 | const SHA1: &Oid = &OID_HASH_SHA1; 113 | self.hash_alg 114 | .as_ref() 115 | .map(|alg| &alg.algorithm) 116 | .unwrap_or(SHA1) 117 | } 118 | 119 | /// Get a reference to the rsa ssa pss params's mask generation algorithm. 120 | pub fn mask_gen_algorithm_raw(&self) -> Option<&AlgorithmIdentifier> { 121 | self.mask_gen_algorithm.as_ref() 122 | } 123 | 124 | /// Get the rsa ssa pss params's mask generation algorithm. 125 | /// 126 | /// If the algorithm encoding is invalid, raise an error `InvalidAlgorithmIdentifier` 127 | pub fn mask_gen_algorithm(&self) -> Result { 128 | match self.mask_gen_algorithm.as_ref() { 129 | Some(alg) => { 130 | let (_, hash) = alg 131 | .parameters() 132 | .and_then(|any| Oid::from_der(any.data.as_bytes2()).ok()) 133 | .ok_or(X509Error::InvalidAlgorithmIdentifier)?; 134 | Ok(MaskGenAlgorithm::new(alg.algorithm.clone(), hash)) 135 | } 136 | _ => { 137 | Ok(MaskGenAlgorithm::new( 138 | oid! {1.2.840.113549.1.1.8}, // id-mgf1 139 | OID_HASH_SHA1, 140 | )) 141 | } 142 | } 143 | } 144 | 145 | /// Return the salt length 146 | pub fn salt_length(&self) -> u32 { 147 | self.salt_length.unwrap_or(20) 148 | } 149 | 150 | /// Return the trailer field (value must be `1` according to RFC4055) 151 | pub fn trailer_field(&self) -> u32 { 152 | self.trailer_field.unwrap_or(1) 153 | } 154 | } 155 | 156 | impl CheckDerConstraints for RsaSsaPssParams<'_> { 157 | fn check_constraints(any: &Any) -> asn1_rs::Result<()> { 158 | any.header.assert_constructed()?; 159 | Ok(()) 160 | } 161 | } 162 | 163 | impl DerAutoDerive for RsaSsaPssParams<'_> {} 164 | 165 | #[derive(Debug, PartialEq, Eq)] 166 | pub struct MaskGenAlgorithm<'a, 'b> { 167 | pub mgf: Oid<'a>, 168 | pub hash: Oid<'b>, 169 | } 170 | 171 | impl<'a, 'b> MaskGenAlgorithm<'a, 'b> { 172 | pub const fn new(mgf: Oid<'a>, hash: Oid<'b>) -> Self { 173 | Self { mgf, hash } 174 | } 175 | } 176 | 177 | /// RSAAES-OAEP public keys [RFC8017](https://www.rfc-editor.org/rfc/rfc8017.html) 178 | /// 179 | ///
180 | /// RSAES-OAEP-params  ::=  SEQUENCE  {
181 | ///     hashFunc          [0] AlgorithmIdentifier DEFAULT
182 | ///                              sha1Identifier,
183 | ///     maskGenFunc       [1] AlgorithmIdentifier DEFAULT
184 | ///                              mgf1SHA1Identifier,
185 | ///     pSourceFunc       [2] AlgorithmIdentifier DEFAULT
186 | ///                              pSpecifiedEmptyIdentifier  }
187 | ///
188 | ///  pSpecifiedEmptyIdentifier  AlgorithmIdentifier  ::=
189 | ///                       { id-pSpecified, nullOctetString }
190 | ///
191 | ///  nullOctetString  OCTET STRING (SIZE (0))  ::=  { ''H }
192 | /// 
193 | #[derive(Debug, PartialEq, Sequence)] 194 | #[asn1(parse = "DER", encode = "")] 195 | #[error(X509Error)] 196 | pub struct RsaAesOaepParams<'a> { 197 | #[tag_explicit(0)] 198 | hash_alg: Option>, 199 | #[tag_explicit(1)] 200 | mask_gen_alg: Option>, 201 | #[tag_explicit(2)] 202 | p_source_alg: Option>, 203 | } 204 | 205 | impl<'a> RsaAesOaepParams<'a> { 206 | pub const EMPTY: &'static AlgorithmIdentifier<'static> = &AlgorithmIdentifier::new( 207 | oid! {1.2.840.113549.1.1.9}, // id-pSpecified 208 | None, 209 | ); 210 | 211 | /// Get a reference to the rsa aes oaep params's hash algorithm. 212 | pub fn hash_algorithm(&self) -> Option<&AlgorithmIdentifier> { 213 | self.hash_alg.as_ref() 214 | } 215 | 216 | /// Return the hash algorithm OID, or SHA1 if absent (RFC4055) 217 | pub fn hash_algorithm_oid(&self) -> &'a Oid { 218 | const SHA1: &Oid = &OID_HASH_SHA1; 219 | self.hash_alg 220 | .as_ref() 221 | .map(|alg| &alg.algorithm) 222 | .unwrap_or(SHA1) 223 | } 224 | 225 | /// Get a reference to the rsa ssa pss params's mask generation algorithm. 226 | pub fn mask_gen_algorithm_raw(&self) -> Option<&AlgorithmIdentifier> { 227 | self.mask_gen_alg.as_ref() 228 | } 229 | 230 | /// Get the rsa ssa pss params's mask generation algorithm. 231 | /// 232 | /// If the algorithm encoding is invalid, raise an error `InvalidAlgorithmIdentifier` 233 | pub fn mask_gen_algorithm(&self) -> Result { 234 | match self.mask_gen_alg.as_ref() { 235 | Some(alg) => { 236 | let hash = alg 237 | .parameters() 238 | .and_then(|any| any.as_oid().ok()) 239 | .ok_or(X509Error::InvalidAlgorithmIdentifier)?; 240 | Ok(MaskGenAlgorithm::new(alg.algorithm.clone(), hash)) 241 | } 242 | _ => { 243 | Ok(MaskGenAlgorithm::new( 244 | oid! {1.2.840.113549.1.1.8}, // id-mgf1 245 | OID_HASH_SHA1, 246 | )) 247 | } 248 | } 249 | } 250 | 251 | /// Return the pSourceFunc algorithm 252 | pub fn p_source_alg(&'a self) -> &'a AlgorithmIdentifier<'a> { 253 | self.p_source_alg.as_ref().unwrap_or(Self::EMPTY) 254 | } 255 | } 256 | 257 | impl CheckDerConstraints for RsaAesOaepParams<'_> { 258 | fn check_constraints(any: &Any) -> asn1_rs::Result<()> { 259 | any.header.assert_constructed()?; 260 | Ok(()) 261 | } 262 | } 263 | 264 | impl DerAutoDerive for RsaAesOaepParams<'_> {} 265 | 266 | // ECC subject public key information [RFC5480](https://datatracker.ietf.org/doc/rfc5480/) 267 | 268 | // ECParameters ::= CHOICE { 269 | // namedCurve OBJECT IDENTIFIER 270 | // -- implicitCurve NULL 271 | // -- specifiedCurve SpecifiedECDomain 272 | // } 273 | // -- implicitCurve and specifiedCurve MUST NOT be used in PKIX. 274 | // -- Details for SpecifiedECDomain can be found in [X9.62]. 275 | // -- Any future additions to this CHOICE should be coordinated 276 | // -- with ANSI X9. 277 | -------------------------------------------------------------------------------- /src/signature_value.rs: -------------------------------------------------------------------------------- 1 | use asn1_rs::{DerSequence, Integer}; 2 | 3 | /// ECDSA Signature Value (RFC3279) 4 | // Ecdsa-Sig-Value ::= SEQUENCE { 5 | // r INTEGER, 6 | // s INTEGER } 7 | #[derive(Debug, PartialEq, Eq, DerSequence)] 8 | pub struct EcdsaSigValue<'a> { 9 | pub r: Integer<'a>, 10 | pub s: Integer<'a>, 11 | } 12 | -------------------------------------------------------------------------------- /src/time.rs: -------------------------------------------------------------------------------- 1 | use asn1_rs::nom::Err; 2 | use asn1_rs::{ 3 | BerError, DerParser, DynTagged, GeneralizedTime, Header, InnerError, Input, Tag, UtcTime, 4 | }; 5 | use nom::IResult; 6 | use std::fmt; 7 | use std::ops::{Add, Sub}; 8 | use time::macros::format_description; 9 | use time::{Duration, OffsetDateTime}; 10 | 11 | use crate::error::X509Error; 12 | 13 | /// An ASN.1 timestamp. 14 | /// 15 | ///
 16 | /// Time ::= CHOICE {
 17 | ///     utcTime        UTCTime,
 18 | ///     generalTime    GeneralizedTime }
 19 | /// 
20 | #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] 21 | pub struct ASN1Time { 22 | time: OffsetDateTime, 23 | generalized: bool, 24 | } 25 | 26 | impl ASN1Time { 27 | #[inline] 28 | pub const fn new(dt: OffsetDateTime) -> Self { 29 | let generalized = dt.year() > 2049; 30 | Self { 31 | time: dt, 32 | generalized, 33 | } 34 | } 35 | 36 | #[inline] 37 | pub const fn new_generalized(dt: OffsetDateTime) -> Self { 38 | Self { 39 | time: dt, 40 | generalized: true, 41 | } 42 | } 43 | 44 | #[inline] 45 | pub const fn new_utc(dt: OffsetDateTime) -> Self { 46 | Self { 47 | time: dt, 48 | generalized: false, 49 | } 50 | } 51 | 52 | #[inline] 53 | pub const fn to_datetime(&self) -> OffsetDateTime { 54 | self.time 55 | } 56 | 57 | /// Makes a new `ASN1Time` from the number of non-leap seconds since Epoch 58 | pub fn from_timestamp(secs: i64) -> Result { 59 | let dt = OffsetDateTime::from_unix_timestamp(secs).map_err(|_| X509Error::InvalidDate)?; 60 | Ok(ASN1Time::new(dt)) 61 | } 62 | 63 | /// Returns the number of non-leap seconds since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp"). 64 | #[inline] 65 | pub fn timestamp(&self) -> i64 { 66 | self.time.unix_timestamp() 67 | } 68 | 69 | /// Returns a `ASN1Time` which corresponds to the current date. 70 | #[inline] 71 | pub fn now() -> Self { 72 | ASN1Time::new(OffsetDateTime::now_utc()) 73 | } 74 | 75 | /// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`. 76 | /// 77 | /// Conversion to RFC2822 date can fail if date cannot be represented in this format, 78 | /// for example if year < 1900. 79 | /// 80 | /// For an infallible conversion to string, use `.to_string()`. 81 | #[inline] 82 | pub fn to_rfc2822(self) -> Result { 83 | self.time 84 | .format(&time::format_description::well_known::Rfc2822) 85 | .map_err(|e| e.to_string()) 86 | } 87 | 88 | /// Return `true` if date is encoded as UTCTime 89 | /// 90 | /// According to RFC 5280, dates though year 2049 should be encoded as UTCTime, and 91 | /// GeneralizedTime after 2029. 92 | #[inline] 93 | pub const fn is_utctime(&self) -> bool { 94 | !self.generalized 95 | } 96 | 97 | /// Return `true` if date is encoded as GeneralizedTime 98 | /// 99 | /// According to RFC 5280, dates though year 2049 should be encoded as UTCTime, and 100 | /// GeneralizedTime after 2029. 101 | #[inline] 102 | pub const fn is_generalizedtime(&self) -> bool { 103 | self.generalized 104 | } 105 | } 106 | 107 | impl DynTagged for ASN1Time { 108 | fn tag(&self) -> Tag { 109 | if self.is_generalizedtime() { 110 | Tag::GeneralizedTime 111 | } else { 112 | Tag::UtcTime 113 | } 114 | } 115 | 116 | fn accept_tag(tag: Tag) -> bool { 117 | tag == Tag::GeneralizedTime || tag == Tag::UtcTime 118 | } 119 | } 120 | 121 | impl<'a> DerParser<'a> for ASN1Time { 122 | type Error = X509Error; 123 | 124 | fn from_der_content( 125 | header: &'_ Header<'a>, 126 | input: Input<'a>, 127 | ) -> IResult, Self, Self::Error> { 128 | match header.tag() { 129 | Tag::GeneralizedTime => { 130 | let (rem, t) = GeneralizedTime::from_der_content(header, input) 131 | .map_err(|_| X509Error::InvalidDate)?; 132 | let dt = t.utc_datetime().map_err(|e| Err::Error(e.into()))?; 133 | Ok((rem, ASN1Time::new_utc(dt))) 134 | } 135 | Tag::UtcTime => { 136 | if let Ok((rem, t)) = UtcTime::from_der_content(header, input.clone()) { 137 | let dt = t 138 | .utc_adjusted_datetime() 139 | .map_err(|e| Err::Error(e.into()))?; 140 | Ok((rem, ASN1Time::new_utc(dt))) 141 | } else { 142 | parse_malformed_date(input).map_err(Err::convert) 143 | } 144 | } 145 | _ => Err(Err::Error(X509Error::InvalidDate)), 146 | } 147 | } 148 | } 149 | 150 | // allow relaxed parsing of UTCTime (ex: 370116130016+0000) 151 | fn parse_malformed_date(input: Input<'_>) -> IResult, ASN1Time, BerError>> { 152 | #[allow(clippy::trivially_copy_pass_by_ref)] 153 | // fn check_char(b: &u8) -> bool { 154 | // (0x20 <= *b && *b <= 0x7f) || (*b == b'+') 155 | // } 156 | let (_rem, hdr) = Header::parse_der(input.clone())?; 157 | // let len = hdr.length().definite()?; 158 | // if len > MAX_OBJECT_SIZE { 159 | // return Err(Err::Error(Error::InvalidLength)); 160 | // } 161 | match hdr.tag() { 162 | Tag::UtcTime => { 163 | // // if we are in this function, the PrintableString could not be validated. 164 | // // Accept it without validating charset, because some tools do not respect the charset 165 | // // restrictions (for ex. they use '*' while explicingly disallowed) 166 | // let (rem, data) = take(len as usize)(rem)?; 167 | // if !data.iter().all(check_char) { 168 | // return Err(nom::Err::Error(BerError::BerValueError)); 169 | // } 170 | // let s = std::str::from_utf8(data).map_err(|_| BerError::BerValueError)?; 171 | // let content = BerObjectContent::UTCTime(s); 172 | // let obj = DerObject::from_header_and_content(hdr, content); 173 | // Ok((rem, obj)) 174 | Err(BerError::nom_err_input(&input, InnerError::BerValueError)) 175 | } 176 | _ => Err(Err::Error(BerError::unexpected_tag( 177 | input, 178 | Some(Tag::UtcTime), 179 | hdr.tag(), 180 | ))), 181 | } 182 | } 183 | 184 | impl fmt::Display for ASN1Time { 185 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 186 | let format = format_description!("[month repr:short] [day padding:space] [hour]:[minute]:[second] [year padding:none] [offset_hour sign:mandatory]:[offset_minute]"); 187 | let s = self 188 | .time 189 | .format(format) 190 | .unwrap_or_else(|e| format!("Invalid date: {e}")); 191 | f.write_str(&s) 192 | } 193 | } 194 | 195 | impl Add for ASN1Time { 196 | type Output = Option; 197 | 198 | #[inline] 199 | fn add(self, rhs: Duration) -> Option { 200 | Some(ASN1Time::new(self.time + rhs)) 201 | } 202 | } 203 | 204 | impl Sub for ASN1Time { 205 | type Output = Option; 206 | 207 | #[inline] 208 | fn sub(self, rhs: ASN1Time) -> Option { 209 | if self.time > rhs.time { 210 | Some(self.time - rhs.time) 211 | } else { 212 | None 213 | } 214 | } 215 | } 216 | 217 | impl From for ASN1Time { 218 | fn from(dt: OffsetDateTime) -> Self { 219 | ASN1Time::new(dt) 220 | } 221 | } 222 | 223 | #[cfg(test)] 224 | mod tests { 225 | use time::macros::datetime; 226 | 227 | use super::ASN1Time; 228 | 229 | #[test] 230 | fn test_time_to_string() { 231 | let d = datetime!(1 - 1 - 1 12:34:56 UTC); 232 | let t = ASN1Time::from(d); 233 | assert_eq!(t.to_string(), "Jan 1 12:34:56 1 +00:00".to_string()); 234 | } 235 | 236 | #[test] 237 | fn test_nonrfc2822_date() { 238 | // test year < 1900 239 | let d = datetime!(1 - 1 - 1 00:00:00 UTC); 240 | let t = ASN1Time::from(d); 241 | assert!(t.to_rfc2822().is_err()); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/validate/certificate.rs: -------------------------------------------------------------------------------- 1 | use crate::certificate::*; 2 | use crate::validate::*; 3 | 4 | #[derive(Debug)] 5 | pub struct X509CertificateValidator; 6 | 7 | impl<'a> Validator<'a> for X509CertificateValidator { 8 | type Item = X509Certificate<'a>; 9 | 10 | fn validate(&self, item: &'a Self::Item, l: &'_ mut L) -> bool { 11 | let mut res = true; 12 | res &= X509ExtensionsValidator.validate(&item.extensions(), l); 13 | res 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/validate/extensions.rs: -------------------------------------------------------------------------------- 1 | use crate::extensions::*; 2 | use crate::validate::*; 3 | use std::collections::HashSet; 4 | 5 | // extra-pedantic checks 6 | 7 | const WARN_SHOULD_BE_CRITICAL: bool = false; 8 | 9 | macro_rules! test_critical { 10 | (MUST $ext:ident, $l:ident, $name:expr) => { 11 | if !$ext.critical { 12 | $l.err(&format!("Extension {} MUST be critical, but is not", $name)); 13 | } 14 | }; 15 | (MUST NOT $ext:ident, $l:ident, $name:expr) => { 16 | if $ext.critical { 17 | $l.err(&format!("Extension {} MUST NOT be critical, but is", $name)); 18 | } 19 | }; 20 | (SHOULD $ext:ident, $l:ident, $name:expr) => { 21 | if WARN_SHOULD_BE_CRITICAL && !$ext.critical { 22 | $l.warn(&format!( 23 | "Extension {} SHOULD be critical, but is not", 24 | $name 25 | )); 26 | } 27 | }; 28 | (SHOULD NOT $ext:ident, $l:ident, $name:expr) => { 29 | if WARN_SHOULD_BE_CRITICAL && $ext.critical { 30 | $l.warn(&format!( 31 | "Extension {} SHOULD NOT be critical, but is", 32 | $name 33 | )); 34 | } 35 | }; 36 | } 37 | 38 | #[derive(Debug)] 39 | pub struct X509ExtensionsValidator; 40 | 41 | impl<'a> Validator<'a> for X509ExtensionsValidator { 42 | type Item = &'a [X509Extension<'a>]; 43 | 44 | fn validate(&self, item: &'a Self::Item, l: &'_ mut L) -> bool { 45 | let mut res = true; 46 | // check for duplicate extensions 47 | { 48 | let mut m = HashSet::new(); 49 | for ext in item.iter() { 50 | if m.contains(&ext.oid) { 51 | l.err(&format!("Duplicate extension {}", ext.oid)); 52 | res = false; 53 | } else { 54 | m.insert(ext.oid.clone()); 55 | } 56 | } 57 | } 58 | 59 | for ext in item.iter() { 60 | // specific extension checks 61 | match ext.parsed_extension() { 62 | ParsedExtension::AuthorityKeyIdentifier(aki) => { 63 | // Conforming CAs MUST mark this extension as non-critical 64 | test_critical!(MUST NOT ext, l, "AKI"); 65 | // issuer or serial is present must be either both present or both absent 66 | if aki.authority_cert_issuer.is_some() ^ aki.authority_cert_serial.is_some() { 67 | l.warn("AKI: only one of Issuer and Serial is present"); 68 | } 69 | } 70 | ParsedExtension::CertificatePolicies(policies) => { 71 | // A certificate policy OID MUST NOT appear more than once in a 72 | // certificate policies extension. 73 | let mut policy_oids = HashSet::new(); 74 | for policy_info in policies { 75 | if policy_oids.contains(&policy_info.policy_id) { 76 | l.err(&format!( 77 | "Certificate Policies: duplicate policy {}", 78 | policy_info.policy_id 79 | )); 80 | res = false; 81 | } else { 82 | policy_oids.insert(policy_info.policy_id.clone()); 83 | } 84 | } 85 | } 86 | ParsedExtension::KeyUsage(ku) => { 87 | // SHOULD be critical 88 | test_critical!(SHOULD ext, l, "KeyUsage"); 89 | // When the keyUsage extension appears in a certificate, at least one of the bits 90 | // MUST be set to 1. 91 | if ku.flags == 0 { 92 | l.err("KeyUsage: all flags are set to 0"); 93 | } 94 | } 95 | ParsedExtension::SubjectAlternativeName(san) => { 96 | // SHOULD be non-critical 97 | test_critical!(SHOULD NOT ext, l, "SubjectAltName"); 98 | for name in san.general_names() { 99 | match name { 100 | GeneralName::DNSName(ref s) | GeneralName::RFC822Name(ref s) => { 101 | // should be an ia5string 102 | if !s.as_bytes().iter().all(u8::is_ascii) { 103 | l.warn(&format!("Invalid charset in 'SAN' entry '{s}'")); 104 | } 105 | } 106 | _ => (), 107 | } 108 | } 109 | } 110 | _ => (), 111 | } 112 | } 113 | res 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/validate/loggers.rs: -------------------------------------------------------------------------------- 1 | pub trait Logger { 2 | fn warn(&mut self, message: &str); 3 | 4 | fn err(&mut self, message: &str); 5 | } 6 | 7 | /// Simple Logger for [`Validator`](crate::validate::Validator) trait, storing messages in `Vec` 8 | #[derive(Debug, Default)] 9 | pub struct VecLogger { 10 | warnings: Vec, 11 | errors: Vec, 12 | } 13 | 14 | impl VecLogger { 15 | /// Get stored warnings 16 | pub fn warnings(&self) -> &[String] { 17 | &self.warnings 18 | } 19 | 20 | /// Get stored errors 21 | pub fn errors(&self) -> &[String] { 22 | &self.errors 23 | } 24 | } 25 | 26 | impl Logger for VecLogger { 27 | fn warn(&mut self, message: &str) { 28 | self.warnings.push(message.to_owned()) 29 | } 30 | 31 | fn err(&mut self, message: &str) { 32 | self.errors.push(message.to_owned()) 33 | } 34 | } 35 | 36 | /// Simple Logger for [`Validator`](crate::validate::Validator) trait, printing messages to `stderr` 37 | #[derive(Debug, Default)] 38 | pub struct StderrLogger; 39 | 40 | impl Logger for StderrLogger { 41 | fn warn(&mut self, message: &str) { 42 | eprintln!("[W] {message}"); 43 | } 44 | 45 | fn err(&mut self, message: &str) { 46 | eprintln!("[E] {message}"); 47 | } 48 | } 49 | 50 | /// Simple Logger for [`Validator`](crate::validate::Validator) trait, using closures for `warn`/`err`. 51 | #[derive(Debug, Default)] 52 | pub struct CallbackLogger 53 | where 54 | W: FnMut(&str), 55 | E: FnMut(&str), 56 | { 57 | warn: W, 58 | err: E, 59 | } 60 | 61 | impl CallbackLogger 62 | where 63 | W: FnMut(&str), 64 | E: FnMut(&str), 65 | { 66 | pub fn new(warn: W, err: E) -> Self { 67 | CallbackLogger { warn, err } 68 | } 69 | } 70 | 71 | impl Logger for CallbackLogger 72 | where 73 | W: FnMut(&str), 74 | E: FnMut(&str), 75 | { 76 | fn warn(&mut self, message: &str) { 77 | (self.warn)(message); 78 | } 79 | 80 | fn err(&mut self, message: &str) { 81 | (self.err)(message); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/validate/mod.rs: -------------------------------------------------------------------------------- 1 | mod certificate; 2 | mod extensions; 3 | mod loggers; 4 | mod name; 5 | mod structure; 6 | use std::marker::PhantomData; 7 | 8 | pub use certificate::*; 9 | pub use extensions::*; 10 | pub use loggers::*; 11 | pub use name::*; 12 | pub use structure::*; 13 | 14 | /// Trait for validating item (for ex. validate X.509 structure) 15 | /// 16 | /// # Examples 17 | /// 18 | /// Using callbacks: 19 | /// 20 | /// ``` 21 | /// use x509_parser::certificate::X509Certificate; 22 | /// # #[allow(deprecated)] 23 | /// use x509_parser::validate::Validate; 24 | /// # #[allow(deprecated)] 25 | /// #[cfg(feature = "validate")] 26 | /// fn validate_certificate(x509: &X509Certificate<'_>) -> Result<(), &'static str> { 27 | /// println!(" Subject: {}", x509.subject()); 28 | /// // validate and print warnings and errors to stderr 29 | /// let ok = x509.validate( 30 | /// |msg| { 31 | /// eprintln!(" [W] {}", msg); 32 | /// }, 33 | /// |msg| { 34 | /// eprintln!(" [E] {}", msg); 35 | /// }, 36 | /// ); 37 | /// print!("Structure validation status: "); 38 | /// if ok { 39 | /// println!("Ok"); 40 | /// Ok(()) 41 | /// } else { 42 | /// println!("FAIL"); 43 | /// Err("validation failed") 44 | /// } 45 | /// } 46 | /// ``` 47 | /// 48 | /// Collecting warnings and errors to `Vec`: 49 | /// 50 | /// ``` 51 | /// use x509_parser::certificate::X509Certificate; 52 | /// # #[allow(deprecated)] 53 | /// use x509_parser::validate::Validate; 54 | /// 55 | /// # #[allow(deprecated)] 56 | /// #[cfg(feature = "validate")] 57 | /// fn validate_certificate(x509: &X509Certificate<'_>) -> Result<(), &'static str> { 58 | /// println!(" Subject: {}", x509.subject()); 59 | /// // validate and print warnings and errors to stderr 60 | /// let (ok, warnings, errors) = x509.validate_to_vec(); 61 | /// print!("Structure validation status: "); 62 | /// if ok { 63 | /// println!("Ok"); 64 | /// } else { 65 | /// println!("FAIL"); 66 | /// } 67 | /// for warning in &warnings { 68 | /// eprintln!(" [W] {}", warning); 69 | /// } 70 | /// for error in &errors { 71 | /// eprintln!(" [E] {}", error); 72 | /// } 73 | /// println!(); 74 | /// if !errors.is_empty() { 75 | /// return Err("validation failed"); 76 | /// } 77 | /// Ok(()) 78 | /// } 79 | /// ``` 80 | #[deprecated(since = "0.13.0", note = "please use `X509StructureValidator` instead")] 81 | pub trait Validate { 82 | /// Attempts to validate current item. 83 | /// 84 | /// Returns `true` if item was validated. 85 | /// 86 | /// Call `warn()` if a non-fatal error was encountered, and `err()` 87 | /// if the error is fatal. These fucntions receive a description of the error. 88 | fn validate(&self, warn: W, err: E) -> bool 89 | where 90 | W: FnMut(&str), 91 | E: FnMut(&str); 92 | 93 | /// Attempts to validate current item, storing warning and errors in `Vec`. 94 | /// 95 | /// Returns the validation result (`true` if validated), the list of warnings, 96 | /// and the list of errors. 97 | fn validate_to_vec(&self) -> (bool, Vec, Vec) { 98 | let mut warn_list = Vec::new(); 99 | let mut err_list = Vec::new(); 100 | let res = self.validate( 101 | |s| warn_list.push(s.to_owned()), 102 | |s| err_list.push(s.to_owned()), 103 | ); 104 | (res, warn_list, err_list) 105 | } 106 | } 107 | 108 | /// Trait for build item validators (for ex. validate X.509 structure) 109 | /// 110 | /// See [`X509StructureValidator`] for a default implementation, validating the 111 | /// DER structure of a X.509 Certificate. 112 | /// 113 | /// See implementors of the [`Logger`] trait for methods to collect or handle warnings and errors. 114 | /// 115 | /// # Examples 116 | /// 117 | /// Collecting warnings and errors to `Vec`: 118 | /// 119 | /// ``` 120 | /// use x509_parser::certificate::X509Certificate; 121 | /// use x509_parser::validate::*; 122 | /// 123 | /// # #[allow(deprecated)] 124 | /// #[cfg(feature = "validate")] 125 | /// fn validate_certificate(x509: &X509Certificate<'_>) -> Result<(), &'static str> { 126 | /// let mut logger = VecLogger::default(); 127 | /// println!(" Subject: {}", x509.subject()); 128 | /// // validate and print warnings and errors to stderr 129 | /// let ok = X509StructureValidator.validate(&x509, &mut logger); 130 | /// print!("Structure validation status: "); 131 | /// if ok { 132 | /// println!("Ok"); 133 | /// } else { 134 | /// println!("FAIL"); 135 | /// } 136 | /// for warning in logger.warnings() { 137 | /// eprintln!(" [W] {}", warning); 138 | /// } 139 | /// for error in logger.errors() { 140 | /// eprintln!(" [E] {}", error); 141 | /// } 142 | /// println!(); 143 | /// if !logger.errors().is_empty() { 144 | /// return Err("validation failed"); 145 | /// } 146 | /// Ok(()) 147 | /// } 148 | /// ``` 149 | pub trait Validator<'a> { 150 | /// The item to validate 151 | type Item; 152 | 153 | /// Attempts to validate current item. 154 | /// 155 | /// Returns `true` if item was validated. 156 | /// 157 | /// Call `l.warn()` if a non-fatal error was encountered, and `l.err()` 158 | /// if the error is fatal. These functions receive a description of the error. 159 | fn validate(&self, item: &'a Self::Item, l: &'_ mut L) -> bool; 160 | 161 | fn chain(self, v2: V2) -> ChainValidator<'a, Self, V2, Self::Item> 162 | where 163 | Self: Sized, 164 | V2: Validator<'a, Item = Self::Item>, 165 | { 166 | ChainValidator { 167 | v1: self, 168 | v2, 169 | _p: PhantomData, 170 | } 171 | } 172 | } 173 | 174 | #[derive(Debug)] 175 | pub struct ChainValidator<'a, A, B, I> 176 | where 177 | A: Validator<'a, Item = I>, 178 | B: Validator<'a, Item = I>, 179 | { 180 | v1: A, 181 | v2: B, 182 | _p: PhantomData<&'a ()>, 183 | } 184 | 185 | impl<'a, A, B, I> Validator<'a> for ChainValidator<'a, A, B, I> 186 | where 187 | A: Validator<'a, Item = I>, 188 | B: Validator<'a, Item = I>, 189 | { 190 | type Item = I; 191 | 192 | fn validate(&'_ self, item: &'a Self::Item, l: &'_ mut L) -> bool { 193 | self.v1.validate(item, l) & self.v2.validate(item, l) 194 | } 195 | } 196 | 197 | #[allow(deprecated)] 198 | #[cfg(test)] 199 | mod tests { 200 | use crate::validate::*; 201 | 202 | struct V1 { 203 | a: u32, 204 | } 205 | 206 | impl Validate for V1 { 207 | fn validate(&self, mut warn: W, _err: E) -> bool 208 | where 209 | W: FnMut(&str), 210 | E: FnMut(&str), 211 | { 212 | if self.a > 10 { 213 | warn("a is greater than 10"); 214 | } 215 | true 216 | } 217 | } 218 | 219 | struct V1Validator; 220 | 221 | impl<'a> Validator<'a> for V1Validator { 222 | type Item = V1; 223 | 224 | fn validate(&self, item: &'a Self::Item, l: &'_ mut L) -> bool { 225 | if item.a > 10 { 226 | l.warn("a is greater than 10"); 227 | } 228 | true 229 | } 230 | } 231 | 232 | #[test] 233 | fn validate_warn() { 234 | let v1 = V1 { a: 1 }; 235 | let (res, warn, err) = v1.validate_to_vec(); 236 | assert!(res); 237 | assert!(warn.is_empty()); 238 | assert!(err.is_empty()); 239 | // same, with one warning 240 | let v20 = V1 { a: 20 }; 241 | let (res, warn, err) = v20.validate_to_vec(); 242 | assert!(res); 243 | assert_eq!(warn, vec!["a is greater than 10".to_string()]); 244 | assert!(err.is_empty()); 245 | } 246 | 247 | #[test] 248 | fn validator_warn() { 249 | let mut logger = VecLogger::default(); 250 | let v1 = V1 { a: 1 }; 251 | let res = V1Validator.validate(&v1, &mut logger); 252 | assert!(res); 253 | assert!(logger.warnings().is_empty()); 254 | assert!(logger.errors().is_empty()); 255 | // same, with one warning 256 | let v20 = V1 { a: 20 }; 257 | let res = V1Validator.validate(&v20, &mut logger); 258 | assert!(res); 259 | assert_eq!(logger.warnings(), &["a is greater than 10".to_string()]); 260 | assert!(logger.errors().is_empty()); 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/validate/name.rs: -------------------------------------------------------------------------------- 1 | use crate::validate::*; 2 | use crate::x509::*; 3 | use asn1_rs::Tag; 4 | 5 | #[derive(Debug)] 6 | pub struct X509NameStructureValidator; 7 | 8 | impl<'a> Validator<'a> for X509NameStructureValidator { 9 | type Item = X509Name<'a>; 10 | 11 | fn validate(&self, item: &'a Self::Item, l: &'_ mut L) -> bool { 12 | let res = true; 13 | // subject/issuer: verify charsets 14 | // - wildcards in PrintableString 15 | // - non-IA5 in IA5String 16 | for attr in item.iter_attributes() { 17 | match attr.attr_value().tag() { 18 | Tag::PrintableString | Tag::Ia5String => { 19 | let b = attr.attr_value().as_bytes(); 20 | if !b.iter().all(u8::is_ascii) { 21 | l.warn(&format!( 22 | "Invalid charset in X.509 Name, component {}", 23 | attr.attr_type() 24 | )); 25 | } 26 | } 27 | _ => (), 28 | } 29 | } 30 | res 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/validate/structure.rs: -------------------------------------------------------------------------------- 1 | use super::{Logger, Validator, X509NameStructureValidator}; 2 | use crate::certificate::*; 3 | use crate::extensions::{GeneralName, ParsedExtension}; 4 | use crate::public_key::PublicKey; 5 | use crate::x509::{SubjectPublicKeyInfo, X509Version}; 6 | 7 | /// Default X.509 structure validator for `X509Certificate` 8 | /// 9 | /// This [`Validator`] iterates the X.509 Certificate fields, and verifies the 10 | /// DER encoding and structure: 11 | /// - numbers with wrong encoding/sign (for ex. serial number) 12 | /// - strings with characters not allowed in DER type (for ex. '*' in `PrintableString`) 13 | /// 14 | /// # Examples 15 | /// 16 | /// Validate structure, collect warnings and errors to a `Vec`: 17 | /// 18 | /// ``` 19 | /// use x509_parser::certificate::X509Certificate; 20 | /// use x509_parser::validate::*; 21 | /// 22 | /// # #[allow(deprecated)] 23 | /// #[cfg(feature = "validate")] 24 | /// fn validate_certificate(x509: &X509Certificate<'_>) -> Result<(), &'static str> { 25 | /// let mut logger = VecLogger::default(); 26 | /// println!(" Subject: {}", x509.subject()); 27 | /// // validate and print warnings and errors to stderr 28 | /// let ok = X509StructureValidator.validate(&x509, &mut logger); 29 | /// print!("Structure validation status: "); 30 | /// if ok { 31 | /// println!("Ok"); 32 | /// } else { 33 | /// println!("FAIL"); 34 | /// } 35 | /// for warning in logger.warnings() { 36 | /// eprintln!(" [W] {}", warning); 37 | /// } 38 | /// for error in logger.errors() { 39 | /// eprintln!(" [E] {}", error); 40 | /// } 41 | /// println!(); 42 | /// if !logger.errors().is_empty() { 43 | /// return Err("validation failed"); 44 | /// } 45 | /// Ok(()) 46 | /// } 47 | /// ``` 48 | #[derive(Debug, Default)] 49 | pub struct X509StructureValidator; 50 | 51 | impl<'a> Validator<'a> for X509StructureValidator { 52 | type Item = X509Certificate<'a>; 53 | 54 | fn validate(&self, item: &'a Self::Item, l: &'_ mut L) -> bool { 55 | let mut res = true; 56 | res &= TbsCertificateStructureValidator.validate(&item.tbs_certificate, l); 57 | res 58 | } 59 | } 60 | 61 | /// Default X.509 structure validator for `TbsCertificate` 62 | #[derive(Debug, Default)] 63 | pub struct TbsCertificateStructureValidator; 64 | 65 | impl<'a> Validator<'a> for TbsCertificateStructureValidator { 66 | type Item = TbsCertificate<'a>; 67 | 68 | fn validate(&self, item: &'a Self::Item, l: &'_ mut L) -> bool { 69 | let mut res = true; 70 | // version must be 0, 1 or 2 71 | if item.version.0 >= 3 { 72 | l.err("Invalid version"); 73 | res = false; 74 | } 75 | let b = item.raw_serial(); 76 | if b.is_empty() { 77 | l.err("Serial is empty"); 78 | res = false; 79 | } else { 80 | // check MSB of serial 81 | if b[0] & 0x80 != 0 { 82 | l.warn("Serial number is negative"); 83 | } 84 | // check leading zeroes in serial 85 | if b.len() > 1 && b[0] == 0 && b[1] & 0x80 == 0 { 86 | l.warn("Leading zeroes in serial number"); 87 | } 88 | } 89 | // subject/issuer: verify charsets 90 | res &= X509NameStructureValidator.validate(&item.subject, l); 91 | res &= X509NameStructureValidator.validate(&item.issuer, l); 92 | // subject public key 93 | res &= X509PublicKeyValidator.validate(&item.subject_pki, l); 94 | // validity: dates <= 2049 must use UTCTime, >= 2050 must use GeneralizedTime 95 | let validity = item.validity(); 96 | let year_notbefore = validity.not_before.to_datetime().year(); 97 | if year_notbefore <= 2049 { 98 | if !validity.not_before.is_utctime() { 99 | l.warn("year <= 2049 should use UTCTime (notBefore)"); 100 | } 101 | } else if !validity.not_before.is_generalizedtime() { 102 | l.warn("year >= 2050 should use GeneralizedTime (notBefore)"); 103 | } 104 | let year_notafter = validity.not_after.to_datetime().year(); 105 | if year_notafter <= 2049 { 106 | if !validity.not_after.is_utctime() { 107 | l.warn("year <= 2049 should use UTCTime (notAfter)"); 108 | } 109 | } else if !validity.not_after.is_generalizedtime() { 110 | l.warn("year >= 2050 should use GeneralizedTime (notAfter)"); 111 | } 112 | if item.version == X509Version::V1 { 113 | // unique identifiers: version must 2 or 3 114 | if item.issuer_uid.is_some() { 115 | l.warn("issuerUniqueID present but version 1"); 116 | } 117 | if item.subject_uid.is_some() { 118 | l.warn("subjectUniqueID present but version 1"); 119 | } 120 | } 121 | // extensions require v3 122 | if !item.extensions().is_empty() && item.version != X509Version::V3 { 123 | l.err("Extensions present but version is not 3"); 124 | res = false; 125 | } 126 | // check for parse errors or unsupported extensions 127 | for ext in item.extensions() { 128 | if let ParsedExtension::UnsupportedExtension { .. } = &ext.parsed_extension { 129 | l.warn(&format!("Unsupported extension {}", ext.oid)); 130 | } 131 | if let ParsedExtension::ParseError { error } = &ext.parsed_extension { 132 | l.err(&format!("Parse error in extension {}: {}", ext.oid, error)); 133 | res = false; 134 | } 135 | } 136 | // check extensions 137 | for ext in item.extensions() { 138 | // specific extension checks 139 | // SAN 140 | if let ParsedExtension::SubjectAlternativeName(san) = ext.parsed_extension() { 141 | for name in san.general_names() { 142 | match name { 143 | GeneralName::DNSName(ref s) | GeneralName::RFC822Name(ref s) => { 144 | // should be an ia5string 145 | if !s.as_bytes().iter().all(u8::is_ascii) { 146 | l.warn(&format!("Invalid charset in 'SAN' entry '{s}'")); 147 | } 148 | } 149 | _ => (), 150 | } 151 | } 152 | } 153 | } 154 | res 155 | } 156 | } 157 | 158 | #[derive(Debug, Default)] 159 | pub struct X509PublicKeyValidator; 160 | 161 | impl<'a> Validator<'a> for X509PublicKeyValidator { 162 | type Item = SubjectPublicKeyInfo<'a>; 163 | 164 | fn validate(&self, item: &'a Self::Item, l: &'_ mut L) -> bool { 165 | let mut res = true; 166 | // res &= TbsCertificateStructureValidator.validate(&item.tbs_certificate, l); 167 | match item.parsed() { 168 | Ok(PublicKey::RSA(rsa)) => { 169 | if rsa.modulus[0] & 0x80 != 0 { 170 | l.warn("Public key: (RSA) modulus is negative"); 171 | } 172 | if rsa.exponent[0] & 0x80 != 0 { 173 | l.warn("Public key: (RSA) exponent is negative"); 174 | } 175 | } 176 | Ok(PublicKey::Unknown(_b)) => { 177 | l.warn("Unknown public key type"); 178 | } 179 | Ok(_) => {} 180 | Err(_) => { 181 | l.err("Invalid public key"); 182 | res = false; 183 | } 184 | } 185 | res 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/verify.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | use crate::signature_algorithm::RsaSsaPssParams; 3 | use asn1_rs::{Any, BitString, DerParser}; 4 | use oid_registry::{ 5 | OID_EC_P256, OID_NIST_EC_P384, OID_NIST_HASH_SHA256, OID_NIST_HASH_SHA384, 6 | OID_NIST_HASH_SHA512, OID_PKCS1_RSASSAPSS, OID_PKCS1_SHA1WITHRSA, OID_PKCS1_SHA256WITHRSA, 7 | OID_PKCS1_SHA384WITHRSA, OID_PKCS1_SHA512WITHRSA, OID_SHA1_WITH_RSA, OID_SIG_ECDSA_WITH_SHA256, 8 | OID_SIG_ECDSA_WITH_SHA384, OID_SIG_ED25519, 9 | }; 10 | 11 | /// Verify the cryptographic signature of the raw data (can be a certificate, a CRL or a CSR). 12 | /// 13 | /// `public_key` is the public key of the **signer**. 14 | /// 15 | /// Not all algorithms are supported, this function is limited to what `ring` supports. 16 | pub fn verify_signature( 17 | public_key: &SubjectPublicKeyInfo, 18 | signature_algorithm: &AlgorithmIdentifier, 19 | signature_value: &BitString, 20 | raw_data: &[u8], 21 | ) -> Result<(), X509Error> { 22 | use ring::signature; 23 | 24 | let AlgorithmIdentifier { 25 | algorithm: signature_algorithm, 26 | parameters: signature_algorithm_parameters, 27 | } = &signature_algorithm; 28 | 29 | // identify verification algorithm 30 | let verification_alg: &dyn signature::VerificationAlgorithm = if *signature_algorithm 31 | == OID_PKCS1_SHA1WITHRSA 32 | || *signature_algorithm == OID_SHA1_WITH_RSA 33 | { 34 | &signature::RSA_PKCS1_1024_8192_SHA1_FOR_LEGACY_USE_ONLY 35 | } else if *signature_algorithm == OID_PKCS1_SHA256WITHRSA { 36 | &signature::RSA_PKCS1_2048_8192_SHA256 37 | } else if *signature_algorithm == OID_PKCS1_SHA384WITHRSA { 38 | &signature::RSA_PKCS1_2048_8192_SHA384 39 | } else if *signature_algorithm == OID_PKCS1_SHA512WITHRSA { 40 | &signature::RSA_PKCS1_2048_8192_SHA512 41 | } else if *signature_algorithm == OID_PKCS1_RSASSAPSS { 42 | get_rsa_pss_verification_algo(signature_algorithm_parameters) 43 | .ok_or(X509Error::SignatureUnsupportedAlgorithm)? 44 | } else if *signature_algorithm == OID_SIG_ECDSA_WITH_SHA256 { 45 | get_ec_curve_sha(&public_key.algorithm, 256) 46 | .ok_or(X509Error::SignatureUnsupportedAlgorithm)? 47 | } else if *signature_algorithm == OID_SIG_ECDSA_WITH_SHA384 { 48 | get_ec_curve_sha(&public_key.algorithm, 384) 49 | .ok_or(X509Error::SignatureUnsupportedAlgorithm)? 50 | } else if *signature_algorithm == OID_SIG_ED25519 { 51 | &signature::ED25519 52 | } else { 53 | return Err(X509Error::SignatureUnsupportedAlgorithm); 54 | }; 55 | // get public key 56 | let key = signature::UnparsedPublicKey::new( 57 | verification_alg, 58 | public_key.subject_public_key.as_raw_slice(), 59 | ); 60 | // verify signature 61 | key.verify(raw_data, signature_value.as_raw_slice()) 62 | .or(Err(X509Error::SignatureVerificationError)) 63 | } 64 | 65 | /// Find the verification algorithm for the given EC curve and SHA digest size 66 | /// 67 | /// Not all algorithms are supported, we are limited to what `ring` supports. 68 | fn get_ec_curve_sha( 69 | pubkey_alg: &AlgorithmIdentifier, 70 | sha_len: usize, 71 | ) -> Option<&'static dyn ring::signature::VerificationAlgorithm> { 72 | use ring::signature; 73 | let curve_oid = pubkey_alg.parameters.as_ref()?.as_oid().ok()?; 74 | // let curve_oid = pubkey_alg.parameters.as_ref()?.as_oid().ok()?; 75 | if curve_oid == OID_EC_P256 { 76 | match sha_len { 77 | 256 => Some(&signature::ECDSA_P256_SHA256_ASN1), 78 | 384 => Some(&signature::ECDSA_P256_SHA384_ASN1), 79 | _ => None, 80 | } 81 | } else if curve_oid == OID_NIST_EC_P384 { 82 | match sha_len { 83 | 256 => Some(&signature::ECDSA_P384_SHA256_ASN1), 84 | 384 => Some(&signature::ECDSA_P384_SHA384_ASN1), 85 | _ => None, 86 | } 87 | } else { 88 | None 89 | } 90 | } 91 | 92 | /// Find the verification algorithm for the given RSA-PSS parameters 93 | /// 94 | /// Not all algorithms are supported, we are limited to what `ring` supports. 95 | /// Notably, the SHA-1 hash algorithm is not supported. 96 | fn get_rsa_pss_verification_algo( 97 | params: &Option, 98 | ) -> Option<&'static dyn ring::signature::VerificationAlgorithm> { 99 | use ring::signature; 100 | 101 | let params = params.as_ref()?; 102 | let (_, params) = 103 | RsaSsaPssParams::from_der_content(¶ms.header, params.data.clone()).ok()?; 104 | let hash_algo = params.hash_algorithm_oid(); 105 | 106 | if *hash_algo == OID_NIST_HASH_SHA256 { 107 | Some(&signature::RSA_PSS_2048_8192_SHA256) 108 | } else if *hash_algo == OID_NIST_HASH_SHA384 { 109 | Some(&signature::RSA_PSS_2048_8192_SHA384) 110 | } else if *hash_algo == OID_NIST_HASH_SHA512 { 111 | Some(&signature::RSA_PSS_2048_8192_SHA512) 112 | } else { 113 | None 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/visitor/crl_visitor.rs: -------------------------------------------------------------------------------- 1 | use asn1_rs::num_bigint::BigUint; 2 | use asn1_rs::BitString; 3 | use oid_registry::*; 4 | 5 | use crate::error::X509Error; 6 | use crate::extensions::*; 7 | use crate::revocation_list::*; 8 | use crate::time::ASN1Time; 9 | use crate::x509::*; 10 | 11 | /// Visitor pattern for [`CertificateRevocationList`] 12 | /// 13 | /// # Extensions 14 | /// 15 | /// Visitor methods are provided for extensions, both in a generic way (receiving a [`X509Extension`] 16 | /// object) and in a specific way for standard extensions (for ex, `visit_extension_aki` receives a 17 | /// [`AuthorityKeyIdentifier`]). 18 | /// 19 | /// For a specific method to be called, the extension OID must be correct and the extension must be 20 | /// successfully parsed as the specific type. 21 | /// 22 | /// A specific method can be called multiple times, if the extension is present multiple times. 23 | /// 24 | /// Extension parsing methods are redundant. This is not a problem because default methods do nothing, 25 | /// but if a trait implementation provides several `visit_extension...` methods it must be aware 26 | /// that it will visit the same extension multiple times. 27 | /// 28 | /// # Example 29 | /// 30 | /// ```rust 31 | /// use asn1_rs::num_bigint::BigUint; 32 | /// use x509_parser::prelude::*; 33 | /// use x509_parser::visitor::CertificateRevocationListVisitor; 34 | /// #[derive(Debug, Default)] 35 | /// struct RevokedCertsVisitor { 36 | /// certificates: Vec, 37 | /// } 38 | /// 39 | /// impl CertificateRevocationListVisitor for RevokedCertsVisitor { 40 | /// fn visit_revoked_certificate(&mut self, certificate: &RevokedCertificate<'_>) { 41 | /// self.certificates.push(certificate.user_certificate.clone()); 42 | /// } 43 | /// } 44 | /// ``` 45 | pub trait CertificateRevocationListVisitor { 46 | /// Run the provided visitor (`self`) over the Certificate Revocation List 47 | fn walk(&mut self, crl: &CertificateRevocationList) 48 | where 49 | Self: Sized, 50 | { 51 | crl.walk(self); 52 | } 53 | 54 | /// Invoked for the "tbsCertList" field of the Certificate Revocation List, before visiting children 55 | fn visit_tbs_cert_list(&mut self, _tbs: &TbsCertList) {} 56 | 57 | /// Invoked for the "signatureAlgorithm" field of the Certificate Revocation List 58 | /// 59 | /// Note: this is the "signatureAlgorithm" in the "CertificateList" sequence. According to the 60 | /// specifications, it should be equal to "signature" field from the "TBSCertificate" sequence. 61 | fn visit_signature_algorithm(&mut self, _algorithm: &AlgorithmIdentifier) {} 62 | 63 | /// Invoked for the "signatureValue" field of the TBSCertList 64 | fn visit_signature_value(&mut self, _signature: &BitString) {} 65 | 66 | /// Invoked for the "version" field of the TBSCertList 67 | fn visit_version(&mut self, _version: Option<&X509Version>) {} 68 | 69 | /// Invoked for the "signature" field of the TBSCertList 70 | /// 71 | /// Note: this is the "signature" field from the "TBSCertList" sequence. According to the 72 | /// specifications, it should be equal to "signatureAlgorithm" in the "CertificateList" sequence. 73 | fn visit_tbs_signature_algorithm(&mut self, _algorithm: &AlgorithmIdentifier) {} 74 | 75 | /// Invoked for the "issuer" field of the TBSCertList 76 | fn visit_issuer(&mut self, _name: &X509Name) {} 77 | 78 | /// Invoked for the "thisUpdate" field of the TBSCertList 79 | fn visit_this_update(&mut self, _time: &ASN1Time) {} 80 | 81 | /// Invoked for the "nextUpdate" field of the TBSCertList 82 | fn visit_next_update(&mut self, _time: Option<&ASN1Time>) {} 83 | 84 | /// Invoked for revoked certificate that appear in the TBSCertList 85 | fn visit_revoked_certificates(&mut self, _certificate: &[RevokedCertificate]) {} 86 | 87 | /// Invoked for any revoked certificates that appear in the TBSCertList 88 | /// 89 | /// Note: this function is redundant with `visit_revoked_certificates` 90 | fn visit_revoked_certificate(&mut self, _certificate: &RevokedCertificate) {} 91 | 92 | /// Invoked for extensions, before visiting children 93 | fn pre_visit_extensions(&mut self, _extensions: &[X509Extension]) {} 94 | 95 | /// Invoked for any extension that appear in the TBSCertList 96 | /// 97 | /// Note: this method may be redundant with any other extension visitor method 98 | fn visit_extension(&mut self, _extension: &X509Extension) {} 99 | 100 | /// Invoked for extensions, after visiting children 101 | fn post_visit_extensions(&mut self, _extensions: &[X509Extension]) {} 102 | 103 | /// Invoked for the "Authority Key Identifier" (if present) 104 | fn visit_extension_aki(&mut self, _aki: &AuthorityKeyIdentifier) {} 105 | 106 | /// Invoked for the "Issuer Alternative Name" (if present) 107 | fn visit_extension_issuer_alternative_name(&mut self, _ian: &IssuerAlternativeName) {} 108 | 109 | /// Invoked for the "CRL Number" (if present) 110 | fn visit_extension_crl_number(&mut self, _number: &BigUint) {} 111 | 112 | /// Invoked for the "Issuing Distribution Point" (if present) 113 | fn visit_extension_issuing_distribution_point(&mut self, _dp: &IssuingDistributionPoint) {} 114 | 115 | /// Invoked for the "Authority Information Access" (if present) 116 | fn visit_extension_authority_information_access(&mut self, _info: &AuthorityInfoAccess) {} 117 | 118 | /// Invoked for the "Reason Code" (if present) 119 | fn visit_extension_reason_code(&mut self, _code: &ReasonCode) {} 120 | 121 | /// Invoked for the "Invalidity Date" (if present) 122 | fn visit_extension_invalidity_date(&mut self, _time: &ASN1Time) {} 123 | 124 | /// Invoked for the "Signed Certificate Timestamp" (SCT) (if present) 125 | fn visit_extension_sct(&mut self, _sct: &[SignedCertificateTimestamp]) {} 126 | 127 | /// Invoked for any other extension than the specific (recognized) types 128 | /// 129 | /// This can happen for several reasons: 130 | /// - the parser did not recognize the extension content 131 | /// - the parser was explicitly asked to not parse extension content 132 | /// - the extension could be correct (for ex in a CRL), but is not supposed to be part of a Certificate 133 | fn visit_extension_unknown(&mut self, _ext: &X509Extension) {} 134 | 135 | /// Invoked for any extension than caused a parse error 136 | /// 137 | /// Normally, this should not match anything except for invalid data. 138 | /// This could match any known extension malformed or wrongly encoded. 139 | fn visit_extension_parse_error( 140 | &mut self, 141 | _extension: &X509Extension, 142 | _error: &asn1_rs::Err, 143 | ) { 144 | } 145 | } 146 | 147 | impl CertificateRevocationList<'_> { 148 | /// Run the provided [`CertificateRevocationListVisitor`] over the Certificate Revocation List (`self`) 149 | pub fn walk(&self, visitor: &mut V) { 150 | visitor.visit_tbs_cert_list(&self.tbs_cert_list); 151 | self.tbs_cert_list.walk(visitor); 152 | visitor.visit_signature_algorithm(&self.signature_algorithm); 153 | visitor.visit_signature_value(&self.signature_value); 154 | } 155 | } 156 | 157 | impl TbsCertList<'_> { 158 | /// Run the provided `visitor` over the [`TbsCertList`] object 159 | pub fn walk(&self, visitor: &mut V) { 160 | // shorten name to reduce line length 161 | let v = visitor; 162 | v.visit_version(self.version.as_ref()); 163 | v.visit_tbs_signature_algorithm(&self.signature); 164 | v.visit_issuer(&self.issuer); 165 | v.visit_this_update(&self.this_update); 166 | v.visit_next_update(self.next_update.as_ref()); 167 | v.visit_revoked_certificates(&self.revoked_certificates); 168 | for certificate in &self.revoked_certificates { 169 | v.visit_revoked_certificate(certificate); 170 | } 171 | v.pre_visit_extensions(self.extensions()); 172 | for extension in self.extensions() { 173 | v.visit_extension(extension); 174 | 175 | match extension.parsed_extension() { 176 | ParsedExtension::AuthorityInfoAccess(info) => { 177 | v.visit_extension_authority_information_access(info) 178 | } 179 | ParsedExtension::AuthorityKeyIdentifier(aki) => v.visit_extension_aki(aki), 180 | ParsedExtension::CRLNumber(number) => v.visit_extension_crl_number(number), 181 | ParsedExtension::InvalidityDate(time) => v.visit_extension_invalidity_date(time), 182 | ParsedExtension::IssuerAlternativeName(ian) => { 183 | v.visit_extension_issuer_alternative_name(ian) 184 | } 185 | ParsedExtension::IssuingDistributionPoint(dp) => { 186 | v.visit_extension_issuing_distribution_point(dp) 187 | } 188 | ParsedExtension::ReasonCode(code) => v.visit_extension_reason_code(code), 189 | ParsedExtension::SCT(sct) => v.visit_extension_sct(sct), 190 | ParsedExtension::ParseError { error } => { 191 | v.visit_extension_parse_error(extension, error) 192 | } 193 | _ => v.visit_extension_unknown(extension), 194 | } 195 | } 196 | v.post_visit_extensions(self.extensions()); 197 | } 198 | } 199 | 200 | #[cfg(test)] 201 | mod tests { 202 | use super::*; 203 | use crate::FromDer; 204 | 205 | static CRL: &[u8] = include_bytes!("../../assets/example.crl"); 206 | 207 | #[test] 208 | fn visitor_crl() { 209 | #[derive(Debug, Default)] 210 | struct RevokedCertsVisitor { 211 | certificates: Vec, 212 | } 213 | 214 | impl CertificateRevocationListVisitor for RevokedCertsVisitor { 215 | fn visit_revoked_certificate(&mut self, certificate: &RevokedCertificate) { 216 | self.certificates.push(certificate.user_certificate.clone()); 217 | } 218 | } 219 | 220 | let mut visitor = RevokedCertsVisitor::default(); 221 | let (_, crl) = CertificateRevocationList::from_der(CRL).unwrap(); 222 | 223 | crl.walk(&mut visitor); 224 | assert_eq!(visitor.certificates.len(), 5); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/visitor/mod.rs: -------------------------------------------------------------------------------- 1 | mod certificate_visitor; 2 | mod crl_visitor; 3 | 4 | pub use certificate_visitor::*; 5 | pub use crl_visitor::*; 6 | -------------------------------------------------------------------------------- /tests/pem.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | use x509_parser::pem::{parse_x509_pem, Pem}; 3 | use x509_parser::{parse_x509_certificate, x509::X509Version}; 4 | 5 | static IGCA_PEM: &[u8] = include_bytes!("../assets/IGC_A.pem"); 6 | 7 | #[test] 8 | fn test_x509_parse_pem() { 9 | let (rem, pem) = parse_x509_pem(IGCA_PEM).expect("PEM parsing failed"); 10 | // println!("{:?}", pem); 11 | assert!(rem.is_empty()); 12 | assert_eq!(pem.label, String::from("CERTIFICATE")); 13 | // 14 | // now check that the content is indeed a certificate 15 | let (rem, crt) = parse_x509_certificate(&pem.contents).expect("X.509 parsing failed"); 16 | // println!("res: {:?}", res); 17 | assert!(rem.is_empty()); 18 | assert_eq!(crt.tbs_certificate.version, X509Version::V3); 19 | } 20 | 21 | #[test] 22 | fn test_pem_read() { 23 | let reader = Cursor::new(IGCA_PEM); 24 | let (pem, bytes_read) = Pem::read(reader).expect("Reading PEM failed"); 25 | // println!("{:?}", pem); 26 | assert_eq!(bytes_read, IGCA_PEM.len()); 27 | assert_eq!(pem.label, String::from("CERTIFICATE")); 28 | // 29 | // now check that the content is indeed a certificate 30 | let x509 = pem.parse_x509().expect("X.509: decoding DER failed"); 31 | assert_eq!(x509.tbs_certificate.version, X509Version::V3); 32 | } 33 | 34 | #[test] 35 | fn test_pem_not_pem() { 36 | let bytes = vec![0x1, 0x2, 0x3, 0x4, 0x5]; 37 | let reader = Cursor::new(bytes); 38 | let res = Pem::read(reader); 39 | assert!(res.is_err()); 40 | } 41 | 42 | static NO_END: &[u8] = include_bytes!("../assets/no_end.pem"); 43 | 44 | #[test] 45 | fn test_pem_no_end() { 46 | let reader = Cursor::new(NO_END); 47 | let res = Pem::read(reader); 48 | assert!(res.is_err()); 49 | } 50 | -------------------------------------------------------------------------------- /tests/readcrl.rs: -------------------------------------------------------------------------------- 1 | use x509_parser::prelude::*; 2 | 3 | #[cfg(feature = "verify")] 4 | #[test] 5 | fn read_crl_verify() { 6 | const CA_DATA: &[u8] = include_bytes!("../assets/ca_minimalcrl.der"); 7 | const CRL_DATA: &[u8] = include_bytes!("../assets/minimal.crl"); 8 | 9 | let (_, x509_ca) = X509Certificate::from_der(CA_DATA).expect("could not parse certificate"); 10 | let (_, crl) = parse_x509_crl(CRL_DATA).expect("could not parse revocation list"); 11 | let res = crl.verify_signature(&x509_ca.tbs_certificate.subject_pki); 12 | eprintln!("Verification: {res:?}"); 13 | assert!(res.is_ok()); 14 | } 15 | 16 | fn crl_idp<'a>(crl: &'a CertificateRevocationList) -> &'a IssuingDistributionPoint<'a> { 17 | let crl_idp = crl 18 | .tbs_cert_list 19 | .find_extension(&oid_registry::OID_X509_EXT_ISSUER_DISTRIBUTION_POINT) 20 | .expect("missing IDP extension"); 21 | match crl_idp.parsed_extension() { 22 | ParsedExtension::IssuingDistributionPoint(crl_idp) => crl_idp, 23 | _ => panic!("wrong extension type"), 24 | } 25 | } 26 | 27 | #[test] 28 | fn read_minimal_crl_idp() { 29 | const CRL_DATA: &[u8] = include_bytes!("../assets/crl-idp/minimal.der"); 30 | let (_, crl) = parse_x509_crl(CRL_DATA).expect("could not parse revocation list"); 31 | let crl_idp = crl_idp(&crl); 32 | 33 | let dp = crl_idp 34 | .distribution_point 35 | .as_ref() 36 | .expect("missing distribution point"); 37 | let full_name = match dp { 38 | DistributionPointName::FullName(full_name) => full_name, 39 | DistributionPointName::NameRelativeToCRLIssuer(_) => { 40 | panic!("wrong distribution point name type") 41 | } 42 | }; 43 | assert_eq!(full_name.len(), 1); 44 | let uri = match full_name.first().unwrap() { 45 | GeneralName::URI(uri) => *uri, 46 | _ => panic!("wrong general name type"), 47 | }; 48 | assert_eq!(uri, "http://crl.trustcor.ca/sub/dv-ssl-rsa-s-0.crl"); 49 | 50 | assert!(!crl_idp.only_contains_user_certs); 51 | assert!(!crl_idp.only_contains_ca_certs); 52 | assert!(crl_idp.only_some_reasons.is_none()); 53 | assert!(!crl_idp.only_contains_attribute_certs); 54 | } 55 | 56 | #[test] 57 | fn test_only_user_crl_idp() { 58 | const CRL_DATA: &[u8] = include_bytes!("../assets/crl-idp/only_user_certs.der"); 59 | let (_, crl) = parse_x509_crl(CRL_DATA).expect("could not parse revocation list"); 60 | let crl_idp = crl_idp(&crl); 61 | 62 | assert!(crl_idp.only_contains_user_certs); 63 | assert!(!crl_idp.only_contains_ca_certs); 64 | assert!(crl_idp.only_some_reasons.is_none()); 65 | assert!(!crl_idp.only_contains_attribute_certs); 66 | } 67 | 68 | #[test] 69 | fn test_only_ca_crl_idp() { 70 | const CRL_DATA: &[u8] = include_bytes!("../assets/crl-idp/only_ca_certs.der"); 71 | let (_, crl) = parse_x509_crl(CRL_DATA).expect("could not parse revocation list"); 72 | let crl_idp = crl_idp(&crl); 73 | 74 | assert!(!crl_idp.only_contains_user_certs); 75 | assert!(crl_idp.only_contains_ca_certs); 76 | assert!(crl_idp.only_some_reasons.is_none()); 77 | assert!(!crl_idp.only_contains_attribute_certs); 78 | } 79 | 80 | #[test] 81 | fn test_only_some_reasons_crl_idp() { 82 | const CRL_DATA: &[u8] = include_bytes!("../assets/crl-idp/only_some_reasons.der"); 83 | let (_, crl) = parse_x509_crl(CRL_DATA).expect("could not parse revocation list"); 84 | let crl_idp = crl_idp(&crl); 85 | 86 | assert!(!crl_idp.only_contains_user_certs); 87 | assert!(!crl_idp.only_contains_ca_certs); 88 | assert!(!crl_idp.only_contains_attribute_certs); 89 | 90 | let reasons = crl_idp 91 | .only_some_reasons 92 | .as_ref() 93 | .expect("missing only_some_reasons"); 94 | assert!(reasons.key_compromise()); 95 | assert!(reasons.affiliation_changed()); 96 | } 97 | 98 | #[test] 99 | fn test_only_attribute_cers_crl_idp() { 100 | const CRL_DATA: &[u8] = include_bytes!("../assets/crl-idp/only_attribute_certs.der"); 101 | let (_, crl) = parse_x509_crl(CRL_DATA).expect("could not parse revocation list"); 102 | let crl_idp = crl_idp(&crl); 103 | 104 | assert!(!crl_idp.only_contains_user_certs); 105 | assert!(!crl_idp.only_contains_ca_certs); 106 | assert!(crl_idp.only_some_reasons.is_none()); 107 | assert!(crl_idp.only_contains_attribute_certs); 108 | } 109 | -------------------------------------------------------------------------------- /tests/readcsr.rs: -------------------------------------------------------------------------------- 1 | use asn1_rs::{oid, Oid, Set}; 2 | use oid_registry::{ 3 | OID_PKCS1_SHA256WITHRSA, OID_PKCS9_CHALLENGE_PASSWORD, OID_PKCS9_EXTENSION_REQUEST, 4 | OID_SIG_ECDSA_WITH_SHA256, OID_X509_COMMON_NAME, 5 | }; 6 | use x509_parser::prelude::*; 7 | 8 | const CSR_DATA_EMPTY_ATTRIB: &[u8] = include_bytes!("../assets/csr-empty-attributes.csr"); 9 | const CSR_DATA: &[u8] = include_bytes!("../assets/test.csr"); 10 | const CSR_CHALLENGE_PASSWORD: &[u8] = include_bytes!("../assets/csr-challenge-password.pem"); 11 | const CSR_CUSTOM_EXTENSION: &[u8] = include_bytes!("../assets/csr-custom-extension.pem"); 12 | const OID_CUSTOM_EXTENSION: Oid<'static> = oid!(1.2.3); 13 | const VALUE_CUSTOM_EXTENSION: &[u8] = &[1, 2, 3]; 14 | #[test] 15 | fn read_csr_empty_attrib() { 16 | let (rem, csr) = 17 | X509CertificationRequest::from_der(CSR_DATA_EMPTY_ATTRIB).expect("could not parse CSR"); 18 | 19 | assert!(rem.is_empty()); 20 | let cri = &csr.certification_request_info; 21 | assert_eq!(cri.version, X509Version(0)); 22 | assert_eq!(cri.attributes().len(), 0); 23 | assert_eq!(csr.signature_algorithm.algorithm, OID_PKCS1_SHA256WITHRSA); 24 | } 25 | 26 | #[test] 27 | fn read_csr_with_san() { 28 | let der = pem::parse_x509_pem(CSR_DATA).unwrap().1; 29 | let (rem, csr) = 30 | X509CertificationRequest::from_der(&der.contents).expect("could not parse CSR"); 31 | 32 | assert!(rem.is_empty()); 33 | let cri = &csr.certification_request_info; 34 | assert_eq!(cri.version, X509Version(0)); 35 | assert_eq!(cri.attributes().len(), 1); 36 | assert_eq!(csr.signature_algorithm.algorithm, OID_SIG_ECDSA_WITH_SHA256); 37 | 38 | let mut rdns = cri.subject.iter(); 39 | let rdn = rdns.next().unwrap(); 40 | let first = rdn.iter().next().unwrap(); 41 | assert_eq!(first.attr_type(), &OID_X509_COMMON_NAME); 42 | assert_eq!(first.as_str().unwrap(), "test.rusticata.fr"); 43 | 44 | let expected: &[u8] = &[ 45 | 4, 195, 245, 126, 177, 113, 192, 146, 215, 136, 181, 58, 82, 138, 142, 61, 253, 245, 185, 46 | 192, 166, 216, 218, 145, 219, 42, 169, 112, 122, 58, 91, 184, 150, 37, 237, 245, 59, 54, 47 | 44, 210, 44, 207, 218, 167, 148, 189, 210, 159, 207, 103, 233, 1, 187, 134, 137, 24, 240, 48 | 188, 223, 135, 215, 71, 80, 64, 65, 49 | ]; 50 | assert_eq!(cri.subject_pki.subject_public_key.as_raw_slice(), expected); 51 | 52 | let extensions: Vec<_> = csr.requested_extensions().collect(); 53 | assert_eq!(extensions.len(), 1); 54 | 55 | match &extensions[0] { 56 | ParsedExtension::SubjectAlternativeName(san) => { 57 | let names: Vec<_> = san.general_names().collect(); 58 | assert_eq!(names.len(), 1); 59 | assert!(matches!( 60 | &names[0], 61 | GeneralName::DNSName("test.rusticata.fr") 62 | )); 63 | } 64 | _ => unreachable!(), 65 | } 66 | } 67 | 68 | #[test] 69 | fn read_csr_with_challenge_password() { 70 | let der = pem::parse_x509_pem(CSR_CHALLENGE_PASSWORD).unwrap().1; 71 | let (rem, csr) = X509CertificationRequest::from_der(&der.contents) 72 | .expect("Could not parse CSR with challenge password"); 73 | 74 | assert!(rem.is_empty()); 75 | let cri = &csr.certification_request_info; 76 | assert_eq!(cri.version, X509Version(0)); 77 | assert_eq!(cri.attributes().len(), 2); 78 | 79 | let challenge_password_attr = csr 80 | .certification_request_info 81 | .find_attribute(&OID_PKCS9_CHALLENGE_PASSWORD) 82 | .expect("Challenge password not found in CSR"); 83 | 84 | // 1. Check: Parse value 85 | let (rem, challenge_password_from_value) = 86 | Set::from_der_and_then(challenge_password_attr.value.as_bytes2(), String::from_der) 87 | .expect("Error parsing challenge password attribute"); 88 | assert_eq!(challenge_password_from_value, "A challenge password"); 89 | assert!(rem.is_empty()); 90 | 91 | // 2. Check: Get value directly from parsed attribute 92 | let parsed_attributes = challenge_password_attr.parsed_attributes(); 93 | assert_eq!(parsed_attributes.len(), 1); 94 | if let ParsedCriAttribute::ChallengePassword(challenge_password_from_parsed_attribute) = 95 | &parsed_attributes[0] 96 | { 97 | assert_eq!( 98 | challenge_password_from_parsed_attribute.0, 99 | "A challenge password" 100 | ); 101 | } else { 102 | panic!("Parsed attribute is not a challenge password"); 103 | } 104 | 105 | // Make sure we can read requested extensions 106 | assert_eq!(csr.requested_extensions().count(), 4); 107 | let mut found_san = false; 108 | for extension in csr.requested_extensions() { 109 | if let ParsedExtension::SubjectAlternativeName(san) = extension { 110 | let name = san.general_names().nth(2).unwrap(); 111 | assert!(matches!(name, GeneralName::DNSName("localhost"))); 112 | found_san = true; 113 | } 114 | } 115 | assert!(found_san); 116 | } 117 | 118 | #[cfg(feature = "verify")] 119 | #[test] 120 | fn read_csr_verify() { 121 | let der = pem::parse_x509_pem(CSR_DATA).unwrap().1; 122 | let (_, csr) = X509CertificationRequest::from_der(&der.contents).expect("could not parse CSR"); 123 | csr.verify_signature().unwrap(); 124 | 125 | let mut der = pem::parse_x509_pem(CSR_DATA).unwrap().1; 126 | assert_eq!(&der.contents[28..37], b"rusticata"); 127 | for (i, b) in b"foobarbaz".iter().enumerate() { 128 | der.contents[28 + i] = *b; 129 | } 130 | assert_eq!(&der.contents[28..37], b"foobarbaz"); 131 | 132 | let (_, csr) = X509CertificationRequest::from_der(&der.contents).expect("could not parse CSR"); 133 | csr.verify_signature().unwrap_err(); 134 | } 135 | 136 | #[test] 137 | fn read_csr_with_custom_extension() { 138 | let der = pem::parse_x509_pem(CSR_CUSTOM_EXTENSION).unwrap().1; 139 | let (rem, csr) = X509CertificationRequest::from_der(&der.contents) 140 | .expect("Could not parse CSR with custom extension"); 141 | 142 | assert!(rem.is_empty()); 143 | dbg!(csr.certification_request_info.attributes()); 144 | let cri = &csr.certification_request_info; 145 | assert_eq!(cri.version, X509Version(0)); 146 | assert_eq!(cri.attributes().len(), 1); 147 | 148 | let custom_attr = csr 149 | .certification_request_info 150 | .find_attribute(&OID_PKCS9_EXTENSION_REQUEST) 151 | .expect("Custom extension not found in CSR"); 152 | for attr in custom_attr.parsed_attributes() { 153 | match attr { 154 | ParsedCriAttribute::ExtensionRequest(req) => { 155 | assert_eq!(req.extensions.len(), 1); 156 | let extension = req.extensions.first().unwrap(); 157 | assert_eq!(extension.oid, OID_CUSTOM_EXTENSION); 158 | assert_eq!(extension.critical, false); 159 | assert_eq!(extension.value.as_bytes2(), VALUE_CUSTOM_EXTENSION); 160 | } 161 | _ => unreachable!(), 162 | } 163 | } 164 | 165 | let extensions = csr.requested_extensions(); 166 | for extension in extensions { 167 | if let ParsedExtension::UnsupportedExtension(ext) = extension { 168 | assert_eq!(ext.oid, OID_CUSTOM_EXTENSION); 169 | assert_eq!(ext.value, VALUE_CUSTOM_EXTENSION); 170 | assert_eq!(ext.critical, false); 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /tests/run_all_fuzz_files.rs: -------------------------------------------------------------------------------- 1 | use std::fs::{self, DirEntry}; 2 | use x509_parser::parse_x509_certificate; 3 | 4 | const ARTIFACTS_DIR: &str = "fuzz/artifacts/fuzzer_script_1"; 5 | const CORPUS_DIR: &str = "fuzz/corpus/fuzzer_script_1"; 6 | 7 | #[test] 8 | fn run_all_fuzz_files() { 9 | parse_dir(ARTIFACTS_DIR); 10 | parse_dir(CORPUS_DIR); 11 | } 12 | 13 | fn parse_dir(name: &str) { 14 | match fs::read_dir(name) { 15 | Ok(dir_entries) => { 16 | dir_entries.for_each(|entry| { 17 | let _ = entry.as_ref().map(parse_file); 18 | }); 19 | } 20 | Err(_) => eprintln!("fuzzer corpus/artifacts not found - ignoring test"), 21 | } 22 | } 23 | 24 | fn parse_file(entry: &DirEntry) -> std::io::Result<()> { 25 | let path = entry.path(); 26 | // println!("{:?}", entry.path()); 27 | let data = fs::read(path).unwrap(); 28 | let _ = parse_x509_certificate(&data); 29 | Ok(()) 30 | } 31 | 32 | #[test] 33 | #[ignore = "placeholder for specific tests"] 34 | fn run_fuzz_candidate() { 35 | const CANDIDATE: &str = "fuzz/corpus/fuzzer_script_1/bd0096a63b9979d64763915a342a59af9dc281fb"; 36 | 37 | let data = fs::read(CANDIDATE).unwrap(); 38 | let _ = parse_x509_certificate(&data); 39 | } 40 | -------------------------------------------------------------------------------- /tests/test01.rs: -------------------------------------------------------------------------------- 1 | use nom::bytes::complete::take; 2 | 3 | #[test] 4 | fn test01() { 5 | let data = b"0\x88\xff\xff\xff\xff\xff\xff\xff\xff00\x0f\x02\x000\x00\x00\x00\x00\x00\x0000\x0f\x00\xff\x0a\xbb\xff"; 6 | let _ = x509_parser::parse_x509_certificate(data); 7 | } 8 | 9 | fn parser02(input: &[u8]) -> nom::IResult<&[u8], ()> { 10 | let (_hdr, input) = take(1_usize)(input)?; 11 | let (_data, input) = take(18_446_744_073_709_551_615_usize)(input)?; 12 | Ok((input, ())) 13 | } 14 | 15 | #[test] 16 | fn test02() { 17 | let data = b"0\x88\xff\xff\xff\xff\xff\xff\xff\xff00\x0f\x02\x000\x00\x00\x00\x00\x00\x0000\x0f\x00\xff\x0a\xbb\xff"; 18 | let _ = parser02(data); 19 | } 20 | -------------------------------------------------------------------------------- /tests/verify.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "verify")] 2 | 3 | use x509_parser::parse_x509_certificate; 4 | 5 | static CA_DER: &[u8] = include_bytes!("../assets/IGC_A.der"); 6 | static CA_LETSENCRYPT_X3: &[u8] = include_bytes!("../assets/lets-encrypt-x3-cross-signed.der"); 7 | static CERT_DER: &[u8] = include_bytes!("../assets/certificate.der"); 8 | 9 | #[test] 10 | fn test_signature_verification() { 11 | // for a root CA, verify self-signature 12 | let (_, x509_ca) = parse_x509_certificate(CA_DER).expect("could not parse certificate"); 13 | let res = x509_ca.verify_signature(None); 14 | eprintln!("Verification: {res:?}"); 15 | assert!(res.is_ok()); 16 | 17 | // for a standard certificate, first load the authority, then the certificate, and verify it 18 | let (_, x509_ca) = 19 | parse_x509_certificate(CA_LETSENCRYPT_X3).expect("could not parse certificate"); 20 | let (_, x509_cert) = parse_x509_certificate(CERT_DER).expect("could not parse certificate"); 21 | let res = x509_cert.verify_signature(Some(&x509_ca.tbs_certificate.subject_pki)); 22 | eprintln!("Verification: {res:?}"); 23 | assert!(res.is_ok()); 24 | } 25 | 26 | static ED25519_DER: &[u8] = include_bytes!("../assets/ed25519.der"); 27 | 28 | #[test] 29 | fn test_signature_verification_ed25519() { 30 | // this certificate is self-signed 31 | let (_, x509_ca) = parse_x509_certificate(ED25519_DER).expect("could not parse certificate"); 32 | let res = x509_ca.verify_signature(None); 33 | eprintln!("Verification: {res:?}"); 34 | assert!(res.is_ok()); 35 | } 36 | 37 | static RSA_PSS_SELF_SIGNED_SHA256: &[u8] = 38 | include_bytes!("../assets/rsa-pss/self_signed_sha256.der"); 39 | static RSA_PSS_SELF_SIGNED_SHA384: &[u8] = 40 | include_bytes!("../assets/rsa-pss/self_signed_sha384.der"); 41 | static RSA_PSS_SELF_SIGNED_SHA512: &[u8] = 42 | include_bytes!("../assets/rsa-pss/self_signed_sha512.der"); 43 | 44 | #[test] 45 | fn test_signature_verification_rsa_pss_sha256() { 46 | let (_, x509) = 47 | parse_x509_certificate(RSA_PSS_SELF_SIGNED_SHA256).expect("could not parse certificate"); 48 | let res = x509.verify_signature(None); 49 | eprintln!("Verification: {res:?}"); 50 | assert!(res.is_ok()); 51 | } 52 | 53 | #[test] 54 | fn test_signature_verification_rsa_pss_sha384() { 55 | let (_, x509) = 56 | parse_x509_certificate(RSA_PSS_SELF_SIGNED_SHA384).expect("could not parse certificate"); 57 | let res = x509.verify_signature(None); 58 | eprintln!("Verification: {res:?}"); 59 | assert!(res.is_ok()); 60 | } 61 | 62 | #[test] 63 | fn test_signature_verification_rsa_pss_sha512() { 64 | let (_, x509) = 65 | parse_x509_certificate(RSA_PSS_SELF_SIGNED_SHA512).expect("could not parse certificate"); 66 | let res = x509.verify_signature(None); 67 | eprintln!("Verification: {res:?}"); 68 | assert!(res.is_ok()); 69 | } 70 | --------------------------------------------------------------------------------