pax_global_header00006660000000000000000000000064147463731450014530gustar00rootroot0000000000000052 comment=eb3adc50014f9922c98ce22bf792afa9592374cb rusticata-x509-parser-a41eb37/000077500000000000000000000000001474637314500161345ustar00rootroot00000000000000rusticata-x509-parser-a41eb37/.github/000077500000000000000000000000001474637314500174745ustar00rootroot00000000000000rusticata-x509-parser-a41eb37/.github/dependabot.yml000066400000000000000000000003121474637314500223200ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "cargo" directory: "/" schedule: interval: "daily" - package-ecosystem: github-actions directory: "/" schedule: interval: weekly rusticata-x509-parser-a41eb37/.github/workflows/000077500000000000000000000000001474637314500215315ustar00rootroot00000000000000rusticata-x509-parser-a41eb37/.github/workflows/rust.yml000066400000000000000000000051321474637314500232520ustar00rootroot00000000000000name: Continuous integration on: push: pull_request: merge_group: schedule: - cron: '0 18 * * *' jobs: check: name: Check runs-on: ubuntu-latest strategy: matrix: rust: - stable - 1.67.1 - nightly steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} - name: Cargo update run: cargo update - run: RUSTFLAGS="-D warnings" cargo check check-all-features: name: Check All Features runs-on: ubuntu-latest strategy: matrix: rust: - stable - 1.67.1 - nightly steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} - name: Cargo update run: cargo update - run: RUSTFLAGS="-D warnings" cargo check --all-targets --all-features test: name: Test Suite needs: check runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: cargo test --all-features test_features: name: Test suite (with features) needs: check-all-features runs-on: ubuntu-latest strategy: matrix: features: - --no-default-features - --features=default - --all-features - --features=verify - --features=validate steps: - uses: actions/checkout@v4 - name: Install stable toolchain uses: dtolnay/rust-toolchain@stable - run: cargo test ${{ matrix.features }} fmt: name: Rustfmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - run: cargo fmt --all -- --check clippy: name: Clippy needs: check runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly with: components: clippy - run: cargo clippy --all-features -- -D warnings doc: name: Build documentation needs: check runs-on: ubuntu-latest env: RUSTDOCFLAGS: --cfg docsrs steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - run: cargo doc --workspace --no-deps --all-features semver: name: Check semver compatibility runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v4 - name: Check semver uses: obi1kenobi/cargo-semver-checks-action@v2 rusticata-x509-parser-a41eb37/.github/workflows/security-audit.yml000066400000000000000000000006301474637314500252260ustar00rootroot00000000000000name: Security audit on: schedule: - cron: '0 8 * * *' push: paths: - '**/Cargo.toml' - '**/Cargo.lock' pull_request: jobs: security_audit: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Run security audit uses: rustsec/audit-check@v2.0.0 with: token: ${{ secrets.GITHUB_TOKEN }} rusticata-x509-parser-a41eb37/.gitignore000066400000000000000000000000251474637314500201210ustar00rootroot00000000000000.*.swp target /.idea rusticata-x509-parser-a41eb37/CHANGELOG.md000066400000000000000000000263011474637314500177470ustar00rootroot00000000000000# ChangeLog ## [Unreleased][unreleased] ### Added/Changed/Fixed ### Thanks ## 0.17.0 ### Added/Changed/Fixed Global: - Upgrade `asn1-rs` to version 0.6.2. (#161) - Update asn1-rs to 0.7, der-parser to 10.0 and oid-registry to 0.8 - Upgrade time to 0.3.35 to make the crate compatible with rust >1.79.0 (#168, #175) - Update MSRV to 1.67 (due to time 0.3.35, see #168) - Add Visitor traits for X.509 Certificates and Certificate Revocation Lists (#179) Code: - Add support for RSA-PSS signature verification (#156) - ASN1Time: store the kind of time (UTC or Generalized) in ASN1Time (#163) - X509StructureValidator: add validation for dates encoding (#163) - X509StructureValidator: enforce version > 1 for issuerUniqueID or subjectUniqueID (Closes #162) ### Thanks - Daniel McCarney, DefiCake, Victor M. Alvarez, Nikolaus Thuemmel ## 0.16.0 ### Added/Changed/Fixed Global: - Updated `*ring*` to 0.17.7 (#148) - Updated `time` to 0.3.20 (#148) - Updated asn1-rs to 0.6, der-parser and oid-registry - Set MSRV to 1.63 (due to `time`/`ring`) (#148) Code: - Added support for parsing CRL `IssuingDistributionPoint` extensions (#146) - Fixed lifetime signature on `TbsCertificate::subject_alt_names` function (#151) - Fixed parsing of certificate `UniqueIdentifier` fields to use implicit tagging instead of explicit (#145) - Fixed `clippy::manual_try_fold` findings (#147) ### Thanks - aggstam, Biagio Festa, Daniel McCarney ## 0.15.1 ### Added/Changed/Fixed - Attribute: fix parsing of BmpString string type to use UTF-16 (Closes #143) - `revocation_list`: use correct OID for CRL number. - Fix receiver lifetimes in `AttributeTypeAndValue` ### Thanks - Sergio Benitez, Daniel McCarney, Lily Ballard ## 0.15.0 ### Added/Changed/Fixed Global: - Use SPDX license format (#137) - Set MSRV to 1.57 (due to `ring`/`once_cell`) - Switch base64 decoding to `data-encoding` crate (#136) Code: - Add `verify` feature to verify a certificate revocation list by a public key - Fixed CriAttributes parser (#131) - Refactor code for parsing X509Version - Add verify signature method to revocation list (#130) - Add support for parsing challenge password attribute in CSR's (#129) - Add support for multi-word PEM labels (C#135) Docs: - Fix broken FromDer trait link in README ### Thanks - Bernd Krietenstein, Florian Zipperle, Jean-Baptiste Trystram, Daniel McCarney, Jeff Hiner, Campbell He, Sebastian Dröge ## 0.14.0 ### Added/Changed - Add support for parsing signature parameters and value (closes #94) - Change `ASN1Time::to_rfc2822()` to return a Result - ASN1Time: modify `from_timestamp` to return a Result - ASN1Time: implement Display - Upgrade versions of asn1-rs, oid-registry and der-parser - AlgorithmIdentifier: add const methods to create object/access fields - Globally: start using `asn1-rs` types, simplify parsers: - AlgorithmIdentifier: automatically derive struct, use type ANY - Merge old FromDer trait into `asn1_rs::FromDer` (using X509Error) - Replace BitStringObject with BitString - AttributeTypeAndValue: use Any instead of DerObject - Extensions: replace UnparsedObject with Any - X509Error: add methods to simplify conversions - CRI Attributes: rewrite and simplify parsers - Simplify parsers for multiple types and extensions ### Fixed - Fix ECDSA signature verification when CA and certificate use different curves ### Thanks ## 0.13.2 ### Fixed - Fix panic in ASN1Time::to_rfc2822() when year is less than 1900 ## 0.13.1 ### Fixed - Fix regression with certificate verification for ECDSA signatures using the P-256 curve and SHA-384 (#118) - Set minimum version of `time` to 0.3.7 (#119) - Allow empty SEQUENCE when OPTIONAL, for ex in CRL extensions (#120) ### Thanks - @SergioBenitez, @flavio, @acarlson0000 ## 0.13.0 ### Added/Changed/Fixed Crate: - Update to der-parser 7.0 and asn1-rs - Remove chrono (#111) - Set MSRV to 1.53 Validators: - Add `Deref` trait to `X509Certificate` - Add `Validator` trait and deprecate `Validate` * The previous validation is implemented in `X509StructureValidator` * Split some checks (not on structure) to `X509CertificateValidator` Extensions: - add support for nsComment - add support for IssuerAltName - start adding support for CT Signed Certificate Timestamp (rfc6962) - raise error if a SAN entry cannot be parsed - deprecate `TbsCertificate::find_extension()` and add preferred method `TbsCertificate::get_extension_unique()`: the latter checks for duplicate extensions (#113) Signatures: - Fix signature verification for EC curves (#116) Public Keys: - Add base functions for parsing public keys (RSA, DSA, GOST) ### Thanks - @lilyball, @g2p ## 0.12.0 ### Added/Changed/Fixed - Upgrade to nom 7 ## 0.11.0 ### Added - Add SubjectPublicKeyInfo::raw field ### Changed/Fixed - Fix der-parser dependency (#102) - Update oid-registry dependency (#77) - Set MSRV to 1.46 (indirect dependency on lexical-core and bitvec) - Extend the lifetimes exposed on TbsCertificate (#104) - Add missing test assets (#103) ### Thanks - @jgalenson, @g2p, @kpp ## 0.10.0 ### Added - Add the `Validate` trait to run post-parsing validations of X.509 structure - Add the `FromDer` trait to unify parsing methods and visibility (#85) - Add method to format X509Name using a given registry - Add `X509Certificate::public_key()` method - Add ED25519 as a signature algorithm (#95) - Add support for extensions (#86): - CRL Distribution Points - Add `X509CertificateParser` builder to allow specifying parsing options ### Changed/Fixed - Extensions are now stored in order of appearance in the certificate/CRL (#80) - `.extensions` field is not public anymore, but methods `.extensions()` and `.extensions_map()` have been added - Store CRI attributes in order - Fix parsing of CertificatePolicies, and use named types (closes #82) - Allow specifying registry in oid2sn and similar functions (closes #88) - Mark X509Extension::new as const fn + inline - Allow leading zeroes in serial number - Derive `Clone` for all types (when possible) (#89) - Fix certificate validity period check to be inclusive (#90) - Do not fail GeneralName parsing for x400Address and ediPartyName, read it as unparsed objects (#87) - Change visibility of fields in `X509Name` (replaced by accessors) ### Thanks - @lilyball for numerous issues, ideas and comments - @SergioBenitez for lifetimes fixes (#93) and validity period check fixes (#90) - @rappet for Ed25519 signature verification support (#95) - @xonatius for the work on CRLDistributionPoints (#96, #98) ## 0.9.3 ### Added/Changed/Fixed - Add functions oid2description() and oid_registry() (closes #79) - Fix typo 'ocsp_signing' (closes #84) - Extension: use specific variant if unsupported or failed to parse (closes #83) - Relax constrains on parsing to accept certificates that do not strictly respect DER encoding, but are widely accepted by other X.509 libraries: - SubjectAltName: accept non-ia5string characters - Extensions: accept boolean values not enoded as `00` or `ff` - Serial: build BigUint from raw bytes (do not check sign) ## 0.9.2 ### Added/Changed/Fixed - Remove der-oid-macro from dependencies, not used directly - Use der_parser::num_bigint, remove it from direct dependencies - Add methods to iterate all blocks from a PEM file (#75) - Update MSRV to 1.45.0 ## 0.9.1 ### Added/Changed/Fixed - Fix: X509Name::iter_state_or_province OID value - Re-export oid-registry, and add doc to show how to access OID ### Thanks - @0xazure for fixing X509Name::iter_state_or_province ## 0.9.0 ### Added/Changed/Fixed - Upgrade to `nom` 6.0 - Upgrade to `der-parser` 5.0 - Upgrade MSRV to 1.44.0 - Re-export crates so crate users do not have to import them - Add function parse_x509_pem and deprecate pem_to_der (#53) - Add helper methods to X509Name and simplify accessing values - Add support for ReasonCode extension - Add support for InvalidityDate extension - Add support for CRL Number extension - Add support for Certificate Signing Request (#58) - Change type of X509Version (now directly using the u32 value) - X509Name: relax check, allow some non-rfc compliant strings (#50) - Relax some constraints for invalid dates - CRL: extract raw serial, and add methods to access it - CRL: add method to iterate revoked certificates - RevokedCertificate: convert extensions list to hashmap - Refactor crate modules and visibility - Rename top-level functions to `parse_x509_certificate` and parse_x509_crl` - Refactor error handling, return meaningful errors when possible - Make many more functions public (parse_tbs_certificate, etc.) ### Thanks - Dirkjan Ochtman (@djc): support for Certificate Signing Request (CSR), code refactoring, etc. ## 0.8.0 ### Added/Changed - Upgrade to `der-parser` 4.0 - Move from `time` to `chrono` - `time 0.1 is very old, and time 0.2 broke compatibility and cannot parse timezones - Add public type `ASN1Time` object to abstract implementation - *this breaks API for direct access to `not_before`, `not_after` etc.* - Fix clippy warnings - `nid2obj` argument is now passed by copy, not reference - Add method to get a formatted string of the certificate serial number - Add method to get decoded version - Add convenience methods to access the most common fields (subject, issuer, etc.) - Expose the raw DER of an X509Name - Make `parse_x509_name` public, for parsing distinguished names - Make OID objects public - Implement parsing for some extensions - Support for extensions is not complete, support for more types will be added later - Add example to decode and print certificates - Add `verify` feature to verify cryptographic signature by a public key ### Fixed - Fix parsing of types not representable by string in X509Name (#36) - Fix parsing of certificates with empty subject (#37) ### Thanks - @jannschu, @g2p for the extensions parsing - @wayofthepie for the tests and contributions - @nicholasbishop for contributions ## 0.7.0 - Expose raw bytes of the certificate serial number - Set edition to 2018 ## 0.6.4 - Fix infinite loop when certificate has no END mark ## 0.6.3 - Fix infinite loop when reading non-pem data (#28) ## 0.6.2 - Remove debug code left in `Pem::read` ## 0.6.1 - Add CRL parser - Expose CRL tbs bytes - PEM: ignore lines before BEGIN label (#21) - Fix parsing default values for TbsCertificate version field (#24) - Use BerResult from der-parser for simpler function signatures - Expose tbsCertificate bytes - Upgrade dependencies (base64) ## 0.6.0 - Update to der-parser 3.0 and nom 5 - Breaks API, cleaner error types ## 0.5.1 - Add `time_to_expiration` to `Validity` object - Add method to read a `Pem` object from `BufRead + Seek` - Add method to `Pem` to decode and extract certificate ## 0.5.0 - Update to der-parser 2.0 ## 0.4.3 - Make `parse_subject_public_key_info` public - Add function `sn2oid` (get an OID by short name) ## 0.4.2 - Support GeneralizedTime conversion ## 0.4.1 - Fix case where certificate has no extensions ## 0.4.0 - Upgrade to der-parser 1.1, and Use num-bigint over num - Rename x509_parser to parse_x509_der - Do not export subparsers - Improve documentation ## 0.3.0 - Upgrade to nom 4 ## 0.2.0 - Rewrite X.509 structures and parsing code to work in one pass **Warning: this is a breaking change** - Add support for PEM-encoded certificates - Add some documentation rusticata-x509-parser-a41eb37/Cargo.lock000066400000000000000000000272051474637314500200470ustar00rootroot00000000000000# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "asn1-rs" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "607495ec7113b178fbba7a6166a27f99e774359ef4823adbefd756b5b81d7970" dependencies = [ "asn1-rs-derive", "asn1-rs-impl", "displaydoc", "nom", "num-traits", "rusticata-macros", "thiserror", "time", ] [[package]] name = "asn1-rs-derive" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "asn1-rs-impl" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "cc" version = "1.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" dependencies = [ "shlex", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "data-encoding" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" [[package]] name = "der-parser" version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" dependencies = [ "asn1-rs", "displaydoc", "nom", "num-bigint", "num-traits", "rusticata-macros", ] [[package]] name = "deranged" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", ] [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "itoa" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "num-bigint" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "oid-registry" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "264c56d1492c13e769662197fb6b94e0a52abe52d27efac374615799a4bf453d" dependencies = [ "asn1-rs", ] [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] [[package]] name = "ring" version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", "getrandom", "libc", "spin", "untrusted", "windows-sys", ] [[package]] name = "rusticata-macros" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" dependencies = [ "nom", ] [[package]] name = "serde" version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "syn" version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "synstructure" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "thiserror" version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "time" version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", ] [[package]] name = "unicode-ident" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "x509-parser" version = "0.17.0" dependencies = [ "asn1-rs", "data-encoding", "der-parser", "lazy_static", "nom", "oid-registry", "ring", "rusticata-macros", "thiserror", "time", ] rusticata-x509-parser-a41eb37/Cargo.toml000066400000000000000000000025021474637314500200630ustar00rootroot00000000000000[package] name = "x509-parser" version = "0.17.0" description = "Parser for the X.509 v3 format (RFC 5280 certificates)" license = "MIT OR Apache-2.0" keywords = ["X509","Certificate","parser","nom"] authors = ["Pierre Chifflier "] homepage = "https://github.com/rusticata/x509-parser" repository = "https://github.com/rusticata/x509-parser.git" categories = ["parser-implementations", "cryptography"] readme = "README.md" edition = "2018" rust-version = "1.67.1" include = [ "CHANGELOG.md", "LICENSE-*", "README.md", ".gitignore", ".travis.yml", "Cargo.toml", "src/*.rs", "src/extensions/*.rs", "src/validate/*.rs", "src/visitor/*.rs", "tests/*.rs", "assets/*.crl", "assets/*.csr", "assets/*.der", "assets/*.pem", "assets/crl-ext/*.der", "examples/*.rs" ] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [features] default = [] verify = ["ring"] validate = [] [dependencies] asn1-rs = { version = "0.7.0", features=["datetime"] } data-encoding = "2.2.1" lazy_static = "1.4" nom = "7.0" oid-registry = { version="0.8", features=["crypto", "x509", "x962"] } rusticata-macros = "4.0" ring = { version="0.17.7", optional=true } der-parser = { version = "10.0", features=["bigint"] } thiserror = "2.0" time = { version="0.3.35", features=["formatting"] } rusticata-x509-parser-a41eb37/LICENSE-APACHE000066400000000000000000000251371474637314500200700ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. rusticata-x509-parser-a41eb37/LICENSE-MIT000066400000000000000000000020441474637314500175700ustar00rootroot00000000000000Copyright (c) 2017 Pierre Chifflier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rusticata-x509-parser-a41eb37/README.md000066400000000000000000000124731474637314500174220ustar00rootroot00000000000000 [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE-MIT) [![Apache License 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](./LICENSE-APACHE) [![docs.rs](https://docs.rs/x509-parser/badge.svg)](https://docs.rs/x509-parser) [![crates.io](https://img.shields.io/crates/v/x509-parser.svg)](https://crates.io/crates/x509-parser) [![Download numbers](https://img.shields.io/crates/d/x509-parser.svg)](https://crates.io/crates/x509-parser) [![Github CI](https://github.com/rusticata/x509-parser/workflows/Continuous%20integration/badge.svg)](https://github.com/rusticata/x509-parser/actions) [![Minimum rustc version](https://img.shields.io/badge/rustc-1.67.1+-lightgray.svg)](#rust-version-requirements) # X.509 Parser A X.509 v3 ([RFC5280]) parser, implemented with the [nom](https://github.com/Geal/nom) parser combinator framework. It is written in pure Rust, fast, and makes extensive use of zero-copy. A lot of care is taken to ensure security and safety of this crate, including design (recursion limit, defensive programming), tests, and fuzzing. It also aims to be panic-free. The code is available on [Github](https://github.com/rusticata/x509-parser) and is part of the [Rusticata](https://github.com/rusticata) project. Certificates are usually encoded in two main formats: PEM (usually the most common format) or DER. A PEM-encoded certificate is a container, storing a DER object. See the [`pem`](https://docs.rs/x509-parser/latest/x509_parser/pem/index.html) module for more documentation. To decode a DER-encoded certificate, the main parsing method is `X509Certificate::from_der` ( part of the [`FromDer`](https://docs.rs/x509-parser/latest/x509_parser/prelude/trait.FromDer.html) trait ), which builds a [`X509Certificate`](https://docs.rs/x509-parser/latest/x509_parser/certificate/struct.X509Certificate.html) object. An alternative method is to use [`X509CertificateParser`](https://docs.rs/x509-parser/latest/x509_parser/certificate/struct.X509CertificateParser.html), which allows specifying parsing options (for example, not automatically parsing option contents). The returned objects for parsers follow the definitions of the RFC. This means that accessing fields is done by accessing struct members recursively. Some helper functions are provided, for example [`X509Certificate::issuer()`](https://docs.rs/x509-parser/latest/x509_parser/certificate/struct.X509Certificate.html#method.issuer) returns the same as accessing `.tbs_certificate.issuer`. For PEM-encoded certificates, use the [`pem`](https://docs.rs/x509-parser/latest/x509_parser/pem/index.html) module. This crate also provides visitor traits: [`X509CertificateVisitor`](crate::visitor::X509CertificateVisitor). # Examples Parsing a certificate in DER format: ```rust use x509_parser::prelude::*; static IGCA_DER: &[u8] = include_bytes!("../assets/IGC_A.der"); let res = X509Certificate::from_der(IGCA_DER); match res { Ok((rem, cert)) => { assert!(rem.is_empty()); // assert_eq!(cert.version(), X509Version::V3); }, _ => panic!("x509 parsing failed: {:?}", res), } ``` To parse a CRL and print information about revoked certificates: ```rust # # let res = CertificateRevocationList::from_der(DER); match res { Ok((_rem, crl)) => { for revoked in crl.iter_revoked_certificates() { println!("Revoked certificate serial: {}", revoked.raw_serial_as_string()); println!(" Reason: {}", revoked.reason_code().unwrap_or_default().1); } }, _ => panic!("CRL parsing failed: {:?}", res), } ``` See also `examples/print-cert.rs`. # Features - The `verify` feature adds support for (cryptographic) signature verification, based on `ring`. It adds the [`X509Certificate::verify_signature()`](https://docs.rs/x509-parser/latest/x509_parser/certificate/struct.X509Certificate.html#method.verify_signature) to `X509Certificate`. ```rust /// Cryptographic signature verification: returns true if certificate was signed by issuer #[cfg(feature = "verify")] pub fn check_signature(cert: &X509Certificate<'_>, issuer: &X509Certificate<'_>) -> bool { let issuer_public_key = issuer.public_key(); cert .verify_signature(Some(issuer_public_key)) .is_ok() } ``` - The `validate` features add methods to run more validation functions on the certificate structure and values using the [`Validate`](https://docs.rs/x509-parser/latest/x509_parser/validate/trait.Validate.html) trait. It does not validate any cryptographic parameter (see `verify` above). ## Rust version requirements `x509-parser` requires **Rustc version 1.67.1 or greater**, based on der-parser dependencies and for proc-macro attributes support. [RFC5280]: https://tools.ietf.org/html/rfc5280 ## Changes See [CHANGELOG.md](CHANGELOG.md) # License Licensed under either of * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ## Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. rusticata-x509-parser-a41eb37/assets/000077500000000000000000000000001474637314500174365ustar00rootroot00000000000000rusticata-x509-parser-a41eb37/assets/IGC_A.der000066400000000000000000000020061474637314500207720ustar00rootroot00000000000000009E0  *H 01 0 UFR10 UFrance10 UParis10U PM/SGDN10 U DCSSI10 UIGC/A1#0! *H  igca@sgdn.pm.gouv.fr0 021213142923Z 201017142922Z01 0 UFR10 UFrance10 UParis10U PM/SGDN10 U DCSSI10 UIGC/A1#0! *H  igca@sgdn.pm.gouv.fr0"0  *H 0 b3;CzI\N'F;Jq-歫cPT nɐ6/ڑhr ~y2ppJU'r*\s .gGs5i: FBFIp2]`4}[(k3hN|6֣C Uad7GwвX]~F1Vp*0wy);wL A%.A]jGF@<3еw0u0U00 UF0U 0 0 *zy0U/`P‰ +!ON0160U#0/`P‰ +!ON0160  *H &wDh/f:t])JmQIt6ۉ/ݏ]|럞8G̳پ%~3 7l]12+]e`n<&BdZR]`n ɋ Z,<÷]x%?V :Hi^NM찾];O64LT~Ho~]Mĝβѳlyp~^Q6x/qYLF(`VZrusticata-x509-parser-a41eb37/assets/IGC_A.pem000066400000000000000000000026541474637314500210120ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYT AkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQ TS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG 9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMB4XDTAyMTIxMzE0MjkyM1oXDTIw MTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAM BgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEO MAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2 LmZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaI s9z4iPf930Pfeo2aSVz2TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2 xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCWSo7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4 u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYyHF2fYPepraX/z9E0+X1b F8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNdfrGoRpAx Vs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGd PDPQtQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNV HSAEDjAMMAoGCCqBegF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAx NjAfBgNVHSMEGDAWgBSjBS8YYFDCiQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUF AAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RKq89toB9RlPhJy3Q2FLwV3duJ L92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3QMZsyK10XZZOY YLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2a NjSaTFR+FwNIlQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R 0982gaEbeC9xs/FZTEYYKKuF0mBWWg== -----END CERTIFICATE----- rusticata-x509-parser-a41eb37/assets/ca_minimalcrl.der000066400000000000000000000012471474637314500227300ustar00rootroot00000000000000000  *H  010U snakeoil0"19700101000000Z20991231235959Z010U snakeoil0"0  *H 0  {k?>Ae7k!K슒7ԻE8i"5Wy[~q"ϿU2$𾇧nFxzga %/! ~p+R TBD/ =Z6D?ht|' o]b䷦4bL o")PS*;ϵ̜Έ_p{mS0  *H  M¾!-Mp_fkd{m;d[3YA)%eKof FH/B.`LѿrSmN6#Q#qk;"V A<.*l=6LJejkGi2gneS"N W?O8.G1܍{.NGo>)H.up`DGYܐY# uOM]k>^Cpe z;Cd'UzMZ.Gyvrusticata-x509-parser-a41eb37/assets/certificate.der000066400000000000000000000025371474637314500224230ustar00rootroot000000000000000[0C H 4 g]D2* \8Yh.5+g 83w/j࣋@nBJA.)s 9MeeԼ˭ȳ5 c5ICbcrlqf0b0U0U%0++0 U00U&=eihqUvQ0U#0Jjc}ݺ9Ee0o+c0a0.+0"http://ocsp.int-x3.letsencrypt.org0/+0#http://cert.int-x3.letsencrypt.org/0U0lists.for-our.info0LU E0C0g 07 +0(0&+http://cps.letsencrypt.org0 +yv)RJ#C:yGs!h-W-0s@W /bc$Tg(2)tzahuuMJ)񠚮HIA+Kc*W8w7Ou ;P[?FiPJ^x l2gX |2EU(#4d-Ӏ6ȱAJw0x14{Prusticata-x509-parser-a41eb37/assets/certificate.pem000066400000000000000000000035771474637314500224370ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIFWzCCBEOgAwIBAgISAyBIAwu7NBD5CTxX8suDCMgFMA0GCSqGSIb3DQEBCwUA MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xOTA3MTIxMTEyMzBaFw0x OTEwMTAxMTEyMzBaMB0xGzAZBgNVBAMTEmxpc3RzLmZvci1vdXIuaW5mbzCCASIw DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVoti34X46DaI2nX24C+aZ2Ofkm hKbidiXiRTon1MLSMGl1oNW9MyRyYYCzP4j6DNKChJnr8ZnVShh2oZD+yHWP9lpn XMGkbsUxejRMU9hnaAB50pXRIDAzavkVFCguFlJ8nKkv/Y1Avlw7tc2aZOd3lOZB Er8gJ8mRDGqqsNU+Z12I6slEstzGMpsq6AewCVw4lMjdWWgugzUrxQTRAsG87on6 gOiQH2cMODN3L7Fq4KOLQIjb3/luQhAQhpdKmEGFLin3c+f5or3thCDuwwDtOU1l Zf+8t9S8pZPLrZrIs6H2xjXqCRuUY7iRNbO18Ukc6rlDYhBj9LT+cpmBbHECAwEA AaOCAmYwggJiMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYI KwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUJj2pvRtl3GloH3He6FX1 ds3X0VEwHwYDVR0jBBgwFoAUqEpqYwR93brm0Tm3pkVl7/Oo7KEwbwYIKwYBBQUH AQEEYzBhMC4GCCsGAQUFBzABhiJodHRwOi8vb2NzcC5pbnQteDMubGV0c2VuY3J5 cHQub3JnMC8GCCsGAQUFBzAChiNodHRwOi8vY2VydC5pbnQteDMubGV0c2VuY3J5 cHQub3JnLzAdBgNVHREEFjAUghJsaXN0cy5mb3Itb3VyLmluZm8wTAYDVR0gBEUw QzAIBgZngQwBAgEwNwYLKwYBBAGC3xMBAQEwKDAmBggrBgEFBQcCARYaaHR0cDov L2Nwcy5sZXRzZW5jcnlwdC5vcmcwggEDBgorBgEEAdZ5AgQCBIH0BIHxAO8AdgAp PFGWVMg5ZbqqUPxYB9S3b79Yeily3KTDDPTlRUf0eAAAAWvmGV7yAAAEAwBHMEUC ICQL2Sm14aCMLxX9a9RbySgyBfichMRdbu6QA2Mbrl4eAiEA1vgJ7snqUWCgoqEE 3SEfK3ioMopzWBsPvG6LdCuCMRAAdQBvU3asMfAxGdiZAKRRFf93FRwR2QLBACkG jbIImjfZEwAAAWvmGV9oAAAEAwBGMEQCIExGqw3Lo0nSCyUuTRf92FgGASwWYji5 UGnXuYnpJrAvAiBw8AWVag8fzZ4ogAhY9EFRNdLrUcBjStipL888vyuxKzANBgkq hkiG9w0BAQsFAAOCAQEAF8BBLDvSWZg57B6aDtzfUTSGetCYs3k0vJqCJlL+Pz7/ UruCSsojQzp5R6jvvgYQ83MaIdwe2mgt+OCQB5v7ylctyBzBmYIw9nPnxEC7HlcJ L2K/k5ZjJFRnv4kV1Si8+TIpEAV0ksf39KGKemG8kGi4GXV1v03zSv0p8aCarpuo SKBJ4qlB0CvmS2MqV4KnzO0O2h0c/ZQ4jg7l53eiN7VPdRMMO1DRw+MaW6I/hEZp +oZQ7hhKXgKUBvF4IGwyrfyIZ8AeWKG4IP98COgyRbz7qtrAVevRKCM0ZC2t04A2 Fcix40FKEeiE093Aj3cweMYxNLPgwgQP8Xu3kA5QEw== -----END CERTIFICATE----- rusticata-x509-parser-a41eb37/assets/crl-ext/000077500000000000000000000000001474637314500210145ustar00rootroot00000000000000rusticata-x509-parser-a41eb37/assets/crl-ext/crl-complex.der000066400000000000000000000017051474637314500237400ustar00rootroot0000000000000000 Uj0  *H  081 0 UUS10U Organisation10U Some Name0  210727223020Z21210703223020Z081 0 UUS10U Organisation10U Some Name0"0  *H 0 }j(w4B53r{5Bޔ5sNT9d-"]knO`Nu7%ڡv! 3 L>xͶnDֿ/<[d64<юbʬ,;U%enӗ5 }WJs\U;IXqes Fż>!$M#+;".w0=l ȉ(\HضN*MR0(v^B?K+Gu00 U0U%0++0U00dhttp://example.com/myca.crl`<:081 0 UUS10U Organisation10U Some Name0& http://example.com/myca2.crl`0  *H  T%;%%/s[]H]!pQ.vepDwM8{ي%5CaU8q9s{lcf;Y)X'?9zC70`(I*x Ybn7^R΄}b [B( )$uy/GW5$'.B>_!Gբ_!ǭ3ֆHGE*>f؇lb|4cV 'M#.*7*O{/.0Ud4`rusticata-x509-parser-a41eb37/assets/crl-ext/crl-no-crl.der000066400000000000000000000014471474637314500234660ustar00rootroot000000000000000#0  u0 _`: iFPWG7ޒ+k]a56g_n7m%3#9>dl*f=H fvhGrsP9\Ca"-`y<3gMd_`#! bJp+svpNRrusticata-x509-parser-a41eb37/assets/crl-idp/000077500000000000000000000000001474637314500207705ustar00rootroot00000000000000rusticata-x509-parser-a41eb37/assets/crl-idp/indirect.der000066400000000000000000000004461474637314500232710ustar00rootroot000000000000000"00 *H=0D1+0)U "crl-distrib-point-test.example.com10U indirect_crl 861223015911Z 861223020011ZT0R0U]+8n>W1901U'0% http://example.com/crl.1.der0 *H=H0E!1t>]0(NvlI8 FQWVgnGXu ?%6Gn='#Irusticata-x509-parser-a41eb37/assets/crl-idp/minimal.der000066400000000000000000000014621474637314500231150ustar00rootroot000000000000000.00  *H  0[1 0 UPA1$0"U TrustCor Systems S. de R.L.1&0$U TrustCor DV SSL CA - G2 - RSA 230421132515Z 230425132514Z00+ KD לg 220519141521Z0 0 U 0+ U)t$х 220611121807Z0 0 U 0, "';"iwl 220601161710Z0 0 U 0, i8 220430004306Z0 0 U 0, 0ȟ^ 220807125515Z0 0 U 0, =)YK 220522074846Z0 0 U q0o0U#0$p/`tW0 U 0?U5031/-http://crl.trustcor.ca/sub/dv-ssl-rsa-s-0.crl0  *H   ÄEzhhe#iP]}L`Dl#qr%FˮYd}OмËK2:Xxe2]Pax,idYzDs[+F%bYi \v Y\?,ʁ!&5Y.ھAU/)"RjP[{UP*/i(|%@th9ș B]$E t/]PߏeS# orusticata-x509-parser-a41eb37/assets/crl-idp/only_attribute_certs.der000066400000000000000000000004551474637314500257340ustar00rootroot000000000000000)00 *H=0L1+0)U "crl-distrib-point-test.example.com10U only_attribute_certs 861223015911Z 861223020011ZT0R0Up>"e)? c9/&01U'0% http://example.com/crl.1.der0 *H=G0D ~u"b,̓&T1~= g4֯*T^k%z`˶b#q7rusticata-x509-parser-a41eb37/assets/crl-idp/only_ca_certs.der000066400000000000000000000004471474637314500243150ustar00rootroot000000000000000#00 *H=0E1+0)U "crl-distrib-point-test.example.com10U only_ca_certs 861223015911Z 861223020011ZT0R0U=&nGY憗'!01U'0% http://example.com/crl.1.der0 *H=H0E Z3^Lղy5,L@8nv#!4VsT Ƣrusticata-x509-parser-a41eb37/assets/crl-idp/only_some_reasons.der000066400000000000000000000004531474637314500252240ustar00rootroot000000000000000'00 *H=0I1+0)U "crl-distrib-point-test.example.com10U only_some_reasons 861223015911Z 861223020011ZU0S0U!cC'K *tp݅UV02U(0& http://example.com/crl.1.derP0 *H=G0D M6s(06 ιMڧ.~;HCtn X򚨪\e_ ݜ nڲrusticata-x509-parser-a41eb37/assets/crl-idp/only_user_certs.der000066400000000000000000000004501474637314500247020ustar00rootroot000000000000000$00 *H=0G1+0)U "crl-distrib-point-test.example.com10U only_user_certs 861223015911Z 861223020011ZT0R0U~!k>L;MSAd01U'0% http://example.com/crl.1.der0 *H=G0D ƮtaH`"iKڭQ Qm-qcW w K[Z1) uvpd!䫪Brusticata-x509-parser-a41eb37/assets/csr-challenge-password.pem000066400000000000000000000040151474637314500245100ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- MIIFuDCCA6ACAQAwgb4xCzAJBgNVBAYTAkdCMR8wHQYDVQQIDBZUZXN0IFN0YXRl IG9yIFByb3ZpbmNlMRYwFAYDVQQHDA1UZXN0IExvY2FsaXR5MRowGAYDVQQKDBFP cmdhbml6YXRpb24gTmFtZTEhMB8GA1UECwwYT3JnYW5pemF0aW9uYWwgVW5pdCBO YW1lMRQwEgYDVQQDDAtDb21tb24gTmFtZTEhMB8GCSqGSIb3DQEJARYSdGVzdEBl bWFpbC5hZGRyZXNzMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2pTx B0wQkImcsMaHCO1jYsFRii4JhWJQT326yzI/1FM7pWA/s/kecMOkWVn/KtGDroqE 9Jw6UQOEsmFpLJEOTzAaGbNMM2BkJBNdhO2Vmmhyjks1sJljTftA+aKu4zpxs0i6 r83oMc6iJ08kMMyDYrKCt4oJjln7dpJXc8OoV9INoRpyU2KhTiqJdw0+4V8lB3BI QGP6o4mN1Jx/ElJogWyS4CB3wt9I7n1RH08+f/StCs0iAkASKFxAoCLrdIKSk4CM yUFilaZz6SvHXiLLi+NoLkc+nDaDu4Nu7Pj/e2fYtq8dLRB8A25v5wqQplF7s/ZJ wiFHVfFJDY8wtdBJ12bgLOPQbqmpBiWVrnBZHGMQTsc5YSMHy6EPv33w16cUWneu Ho/ryqBwTWww+fCnjOapkSajckmVpm8e4fijhNmq6N6VTpfgOZ3loCjVOp4/EW3M L+qsYQyM5Y4trY4h0zZh9hZZmQig6vLMIl9n1r+580rLeGsdeyHwHAc6jfwsy4Zz /jOfbWF3rB1xNgSBHL0po02k8PWFr+uBfKPmfTK8Yvlz3fsTJfzLr6u6y3XAejw/ ZZjjPydwLn3hL/q+J9SAhA3wxnCu3puwiyjhQQHgUHLkQsYpivdF1a7Pbz+l1D89 cTOwwkYonkReosLI5QiKbQAX2NbSfVqv8ALn09sCAwEAAaCBszAjBgkqhkiG9w0B CQcxFgwUQSBjaGFsbGVuZ2UgcGFzc3dvcmQwgYsGCSqGSIb3DQEJDjF+MHwwDgYD VR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNV HQ4EFgQUkDknz2bwybBXuXEG1RmUpP84GxMwLAYDVR0RBCUwI4cEfwAAAYcQAAAA AAAAAAAAAAAAAAAAAYIJbG9jYWxob3N0MA0GCSqGSIb3DQEBCwUAA4ICAQAll1fo 6YIKQHINhdj6aKRHKZ/CluFSTAvctga5+JQG8clbWWxjI+RDUZhnw/r5Jr+8OaBW rm7c/aDePP9AOQ93tBfIEv4OMwP4jQVOrZJuAOuyrUoSTSehMPZoR7IZv2QcyRk+ RUi2aqZbYLPFptsJ6+8FlX/ieSM+GEBmj2W9U5LsvI2y6B34N0uy/if9IbQO8OHj afrjZc2jZ1QYj69OUJekEjPZzrb3kSIVELyWfZEocq+e0hLgQEdI0ZCIdzKFeH8i yQfCtJ7g7k/hVkrQcyd3JwFvhIJRlyhw8ORxOHqFsyZ2XWvg0lGVWk9ksRDGFcf3 g38Erl1sU3jPzEfb/B2aio2DO+42ZY65P7nuLNB5Tatdyy+yYmd3OlxL0XtUb/VW DS9zKjNrjBLvgN9w5QUE0hc4HOHfcceEcPljALoeZWF/XmPjEUjbiKSIP7vIKDi2 bkx1LiLnuDzjvapG/C6YmuFrTxoqjc9qZs/e2NjrxJ5tI3s/d9R9UET+jh/1swLH wz35SohLLDLc2ShaNlNuMOwWOySFV93RwjxcP6OsEFyCZrVsQ6UXz6odXk509A5e w3xhpMdQFlsNRpH0f7CjcxsTUx3qFEXMwq0iYt9w0wrTezOTL25oGouHH11JQsZh g7iXkOHZIfzZVIF3sagZhdp7stnJTWnUIhJvXw== -----END CERTIFICATE REQUEST----- rusticata-x509-parser-a41eb37/assets/csr-empty-attributes.csr000066400000000000000000000012361474637314500242600ustar00rootroot00000000000000000U1 0 UFR10U Some-State10U Locality1 0 U Org10U Test CN0"0  *H 0 g_'ѷe,qQ8j50 L͟Q9:iv̤[1gW%EMқB -KՇŭ7l^g{bB%+\rmʯjlo8d_(ZxtDg5HڎP}˶rusticata-x509-parser-a41eb37/assets/duplicate_value_in_authority_info_access.der000066400000000000000000000030431474637314500304320ustar00rootroot0000000000000000  R_^#Ս0  *H  01 0 UDE1E0CU O#b9Ns[_3:c؍L k'ڤK&Y>pkY^68[m=Z9QWY%0 ?&%&p!d<#4ogPE~W9'{aY7ķ:/9^*N @ep1k1tv)(}\YuCaf0b0YU R0P0+!,0+!,0 +!,0  +!,0g 0 U00U0U% 0 +0U-*hgp\50U0U#0k:S୲2 ;t0'U 0 cas.dhbw.dewww.cas.dhbw.de0U00?=;9http://cdp1.pca.dfn.de/dfn-ca-global-g2/pub/crl/cacrl.crl0?=;9http://cdp2.pca.dfn.de/dfn-ca-global-g2/pub/crl/cacrl.crl0+003+0'http://ocsp.pca.dfn.de/OCSP-Server/OCSP0I+0=http://cdp1.pca.dfn.de/dfn-ca-global-g2/pub/cacert/cacert.crt0I+0=http://cdp2.pca.dfn.de/dfn-ca-global-g2/pub/cacert/cacert.crt0  *H  pN~]BReO7:A;bOXNXyMB4i`fe*Ut%es6$Ф}Ek遮T8KBt]Ik5l(Xx%Q`%&0ɿDOj ${ˉGC4}r.L_?[ !WcN۶8 vAzPz=< xD8P=bM̛Ek+"g࿟ zrusticata-x509-parser-a41eb37/assets/ed25519.der000066400000000000000000000004141474637314500211270ustar00rootroot0000000000000000w4"lEIC0+ep0'1 0 UDE10U www.example.com0 210727123714Z 230627123714Z0'1 0 UDE10U www.example.com0*0+ep!>>MNDy=g%7`L0+epA zˆYjʲ,H U'W2Rh?#ôpk Ī6qȃnRּ&rusticata-x509-parser-a41eb37/assets/empty.crl000066400000000000000000000004451474637314500213010ustar00rootroot000000000000000!00 *H=0h10U Intel SGX Root CA10U Intel Corporation10U Santa Clara1 0 U CA1 0 UUS 190522084451Z 200531084451Z/0-0 U0U#0"e Z4󃴕RP9'0 *H=H0E pb}E 3WsM ;23Yt=!qಠ' +E! vRڮrusticata-x509-parser-a41eb37/assets/example.crl000066400000000000000000000014301474637314500215710ustar00rootroot00000000000000000  *H 0_1#0!U Sample Signer Organization10U Sample Signer Unit10USample Signer Cert 130218103200Z 130218104200Z060<yG 130218102212Z0&0 U 0U20130218102200Z0<yH 130218102222Z0&0 U 0U20130218102200Z0<yI 130218102232Z0&0 U 0U20130218102200Z0<yJ 130218102242Z0&0 U 0U20130218102200Z0<yK 130218102251Z0&0 U 0U20130218102200Z/0-0U#0̪.ǵ40 U0  *H B!yvf[!h<ïKx5Lolh'mB͐5 In- FCdZ!$iMPo xaS!Db #%[[]!OӉe'lLiFEH. u;i!S@fl AKXij,kDY.? ot-%-vre@t'c[ %pcL`s-~WҀ"Vm GSBMkJSL\i{tRk=(,һv¶Q2u`m;{ \/EVoT9㒥^&ޱ+)𜓐Eo IX5oũV5ݻJ0F0U00 U0.U%'0%+++**0 U$0 0 U60U0foo@example.comhttp://my.url.here/M0K1 0 UUK10U My Organization10U My Unit10U My Name localhost*Z* some other identifier0"U00 0 foo.com0  *H  [3VF 6^Y{۲ܙJNr5r5Wy7bB,*^uO^e=FKE A&{Hj 2ʖ)TZR\Bc}N7V --ˋ#Q#E=?7r%?^x<"(@sS'GFQ3|ӓV2K|BtA9Q"eNR"DVs4]~`p%8>B3ြy"MPdB^^?@SBqjuJz~jFBI5aNBV/L#9xy_P1@0>0 U$00-U!&0$0rR0)0RR0RR0  *H  tGR+|q$w'ƙ[x Ř&i9!n+~Q۪P?bfIԎzX |I+bi&|(Q6K/(MEչU ZẋFk+Z:s#rGTe-?e%G:̴/OiN/>r SG%EۉYØ==, 1QF;|IzNrusticata-x509-parser-a41eb37/assets/gen_minimal_crl.py000066400000000000000000000023371474637314500231340ustar00rootroot00000000000000"""Generates a DER encoded CRL with a single revoked serial and no extensions. This exercises the optional-parsing functionalitites of `parse_crl_der`. """ import os.path as osp from OpenSSL import crypto def main(): pkey = crypto.PKey() pkey.generate_key(crypto.TYPE_RSA, 2048) ca = crypto.X509() ca.set_version(2) ca.set_serial_number(1) ca.get_subject().CN = 'snakeoil' ca.set_notBefore(b'19700101000000Z') ca.set_notAfter(b'20991231235959Z') ca.set_issuer(ca.get_subject()) ca.set_pubkey(pkey) ca.sign(pkey, 'sha256') with open(osp.join(osp.dirname(__file__), 'ca_minimalcrl.der'), 'wb') as f_ca: f_ca.write(crypto.dump_certificate(crypto.FILETYPE_ASN1, ca)) revoked = crypto.Revoked() revoked.set_serial(b'2a') revoked.set_rev_date(b'19700101000000Z') revoked.set_reason(None) crl = crypto.CRL() crl.set_lastUpdate(b'19700101000000Z') crl.set_nextUpdate(b'20990101000000Z') crl.add_revoked(revoked) crl.sign(issuer_cert=ca, issuer_key=pkey, digest=b'sha256') with open(osp.join(osp.dirname(__file__), 'minimal.crl'), 'wb') as f_crl: f_crl.write(crypto.dump_crl(crypto.FILETYPE_ASN1, crl)) if __name__ == '__main__': main() rusticata-x509-parser-a41eb37/assets/lets-encrypt-x3-cross-signed.der000066400000000000000000000022261474637314500255130ustar00rootroot0000000000000000z ABSsj 0  *H  0?1$0"U Digital Signature Trust Co.10UDST Root CA X30 160317164046Z 210317164046Z0J1 0 UUS10U  Let's Encrypt1#0!ULet's Encrypt Authority X30"0  *H 0  Z.Gr]7hc05&%὾5p/KA5X*h ubqy`בxgqi`W#bʷ? HebT* 2wye+(:RR ._3wl@2\Atl[] _3M8/{,b٣o%/F=~zzm%/X/,h&Kڟ CJDNosz(ꤪn{L}D4[Brusticata-x509-parser-a41eb37/assets/minimal.crl000066400000000000000000000005471474637314500215740ustar00rootroot000000000000000c0M0  *H  010U snakeoil19700101000000Z00*19700101000000Z0  *H  tᏒrv*FXw`5+cK1Ju m2s~voaO/npD5%P"Z;%׾Qȴ) FB-;@ts6 JC2bmM.ELG +/ Uo/ziVd<jӔ|"2Xl3 %&?jhDD2oF^ 'VXdUT:N?}KY%խrusticata-x509-parser-a41eb37/assets/no_end.pem000066400000000000000000000007541474637314500214110ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBUTCB2KADAgECAgkAtXGSKEzrTR4wCgYIKoZIzj0EAwIwEDEOMAwGA1UEAwwF YmVubm8wHhcNMTgxMTEzMDI1NDQwWhcNMTkxMTEzMDI1NDQwWjAQMQ4wDAYDVQQD DAViZW5ubzB2MBAGByqGSM49AgEGBSuBBAAiA2IABDhrHLVTMHC7GTyB/MNztToW ss2zlmvR62X1pQaBN6fYhBJE1XYa0V2C1fGGXj92MencOtXyfYVxn+DY07gyT/71 HQ12TJOe90wwjy2/6N1W1jOv5HjphVT8JQlVNqAC+DAKBggqhkjOPQQDAgNoADBl AjAfzS1tmZ+GSAaXrsPcfAd1A9yfWVtB8tWxFNNo2j7/cL3puf2vQnlwV/0BoZZ1 K4ICMQDaE0HemgYp6RPQzeb96a2gjlaEbwNy1B8O74fE24WRXiOUm4eln9wGQ3I1 iV6NtZU= rusticata-x509-parser-a41eb37/assets/no_extensions.der000066400000000000000000000005251474637314500230270ustar00rootroot000000000000000Q0ؠ q(LM0 *H=010 U benno0 181113025440Z 191113025440Z010 U benno0v0*H=+"b8kS0ppBypWu+1Aޚ)魠Vorۅ^#Cr5^rusticata-x509-parser-a41eb37/assets/no_extensions.pem000066400000000000000000000010061474637314500230310ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIBUTCB2KADAgECAgkAtXGSKEzrTR4wCgYIKoZIzj0EAwIwEDEOMAwGA1UEAwwF YmVubm8wHhcNMTgxMTEzMDI1NDQwWhcNMTkxMTEzMDI1NDQwWjAQMQ4wDAYDVQQD DAViZW5ubzB2MBAGByqGSM49AgEGBSuBBAAiA2IABDhrHLVTMHC7GTyB/MNztToW ss2zlmvR62X1pQaBN6fYhBJE1XYa0V2C1fGGXj92MencOtXyfYVxn+DY07gyT/71 HQ12TJOe90wwjy2/6N1W1jOv5HjphVT8JQlVNqAC+DAKBggqhkjOPQQDAgNoADBl AjAfzS1tmZ+GSAaXrsPcfAd1A9yfWVtB8tWxFNNo2j7/cL3puf2vQnlwV/0BoZZ1 K4ICMQDaE0HemgYp6RPQzeb96a2gjlaEbwNy1B8O74fE24WRXiOUm4eln9wGQ3I1 iV6NtZU= -----END CERTIFICATE----- rusticata-x509-parser-a41eb37/assets/rsa-pss/000077500000000000000000000000001474637314500210265ustar00rootroot00000000000000rusticata-x509-parser-a41eb37/assets/rsa-pss/README.md000066400000000000000000000011001474637314500222750ustar00rootroot00000000000000# Generating Test Certificates ```shell 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 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 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 ``` rusticata-x509-parser-a41eb37/assets/rsa-pss/self_signed_sha256.der000066400000000000000000000017171474637314500251020ustar00rootroot0000000000000000L/ eXs0= *H  00 0  `He0 *H 0  `He 0E1 0 UAU10U Some-State1!0U Internet Widgits Pty Ltd0 240328111127Z 240427111127Z0E1 0 UAU10U Some-State1!0U Internet Widgits Pty Ltd0"0  *H 0 ybnkBKEkY\Z+w^+2 -oM\NRe!tf׋"'l1j >N>=g=tE>i4}'+TV!e˽sGlx}?vU%&H3(xWF!W=`8#e1boib_ׁ#I#H/P^4p 8e@S0Q0UB0Ό|]XrU1* 0U#0B0Ό|]XrU1* 0U00= *H  00 0  `He0 *H 0  `He 4'fW07V%2R#+u zc꟠2%arSf'K z;yu !Z~LSxƨ_Bƛ}; ʷB*~rF ANE›PdGERٙu"yx;BPt_2c{V(!^џlvVZ]2,LPRBHƚ(*ҥ 5Zrusticata-x509-parser-a41eb37/assets/rsa-pss/self_signed_sha384.der000066400000000000000000000017171474637314500251040ustar00rootroot0000000000000000wJL%;Nu0= *H  00 0  `He0 *H 0  `He00E1 0 UAU10U Some-State1!0U Internet Widgits Pty Ltd0 240328111127Z 240427111127Z0E1 0 UAU10U Some-State1!0U Internet Widgits Pty Ltd0"0  *H 0 lu.K0- }[(>2]r5lQyi3ƿ}rVT$Ľ/tQUj_0n8@hQ$ Y[/~ oT4բ=>MW|WBE0g9lT0ur;/+p)+xZ ;~7|EdW "WƜw{y\ȼ%YU$5G \++ZS0Q0U U` E= 0U#0 U` E= 0U00= *H  00 0  `He0 *H 0  `He0vF$cA^ܡɤiH:pz'Bj Xʍ8=-rYn(D'- ›ᤘ܏U yB":tdTHX\k;\E gV\%kejt hb!lW$bZq;s?މզz U_crusticata-x509-parser-a41eb37/assets/rsa-pss/self_signed_sha512.der000066400000000000000000000017171474637314500250750ustar00rootroot0000000000000000Z-{˓n}ij"^0= *H  00 0  `He0 *H 0  `He@0E1 0 UAU10U Some-State1!0U Internet Widgits Pty Ltd0 240328111127Z 240427111127Z0E1 0 UAU10U Some-State1!0U Internet Widgits Pty Ltd0"0  *H 0 y4yu@&g /6Zd Z{;M.i ]ydsph߶Zܝ;p֍ [9,oOi-5y>~ʳ  , {q Ti]==1ΪąawZ5@rHӊV/nȾ^!L ȅ C' Ҧѹ>XF!WSd}x2,9Q Ᲊx"vS0Q0UUoiǩ;nI;60U#0Uoiǩ;nI;60U00= *H  00 0  `He0 *H 0  `He@aQPPs q,tXirqw؝S1Tl8? =@aAk@()B gܒ4E6L ^d3'L(Um.Ah<'KlN^8R/<H!ByD`3*R֔(r|Gok#Bd6!O(U odN% ,HIժ~SVr(}HՎZH;ym CI)52'tXvd=8VLRd<]O sG0x nLιoTib=V榐&,B0@0U0x nLιoT0Uib=V榐&,0  *H c 21əNtkGo$ebzrg;BJׄeXQ]Ep=9޷|%9aayo_wZ~y2qPkQ$2!GUJ(gh$%R֟rusticata-x509-parser-a41eb37/assets/v1.der000066400000000000000000000012411474637314500204560ustar00rootroot0000000000000000n[0  *H  010U marquee0 191127145331Z 291127145511Z010U marquee0"0  *H 0 #HDi?cVo[29DM#7FeQ3@w&eO43s OYbm\UV-#z«Ȭ¡W~ !=Wh{Ay>FiߙDEnK]$yu=4 Y;D[:EMS6g\SlO6EˈDB MM@bZv߮g:>d.@&Q2גzxx0  *H  2tPbMF<Qhu˵h[c,F3E)60eO͊ReEfWn;Xנ&.CAPVb,vE~|n2d_o-[e7!)p#k0^3*G5;y#[]+c%sL.1HdOfVHOdMK%~7jM?Vd2Ļ\rusticata-x509-parser-a41eb37/examples/000077500000000000000000000000001474637314500177525ustar00rootroot00000000000000rusticata-x509-parser-a41eb37/examples/print-cert.rs000066400000000000000000000361751474637314500224230ustar00rootroot00000000000000use der_parser::der::Tag; use der_parser::oid::Oid; use nom::HexDisplay; use std::cmp::min; use std::convert::TryFrom; use std::env; use std::io; use std::net::{Ipv4Addr, Ipv6Addr}; use x509_parser::prelude::*; use x509_parser::public_key::PublicKey; use x509_parser::signature_algorithm::SignatureAlgorithm; const PARSE_ERRORS_FATAL: bool = false; #[cfg(feature = "validate")] const VALIDATE_ERRORS_FATAL: bool = false; fn print_hex_dump(bytes: &[u8], max_len: usize) { let m = min(bytes.len(), max_len); print!("{}", &bytes[..m].to_hex(16)); if bytes.len() > max_len { println!("... "); } } fn format_oid(oid: &Oid) -> String { match oid2sn(oid, oid_registry()) { Ok(s) => s.to_owned(), _ => format!("{}", oid), } } fn generalname_to_string(gn: &GeneralName) -> String { match gn { GeneralName::DNSName(name) => format!("DNSName:{}", name), GeneralName::DirectoryName(n) => format!("DirName:{}", n), GeneralName::EDIPartyName(obj) => format!("EDIPartyName:{:?}", obj), GeneralName::IPAddress(n) => format!("IPAddress:{:?}", n), GeneralName::OtherName(oid, n) => format!("OtherName:{}, {:?}", oid, n), GeneralName::RFC822Name(n) => format!("RFC822Name:{}", n), GeneralName::RegisteredID(oid) => format!("RegisteredID:{}", oid), GeneralName::URI(n) => format!("URI:{}", n), GeneralName::X400Address(obj) => format!("X400Address:{:?}", obj), } } fn print_x509_extension(oid: &Oid, ext: &X509Extension) { println!( " [crit:{} l:{}] {}: ", ext.critical, ext.value.len(), format_oid(oid) ); match ext.parsed_extension() { ParsedExtension::AuthorityKeyIdentifier(aki) => { println!(" X509v3 Authority Key Identifier"); if let Some(key_id) = &aki.key_identifier { println!(" Key Identifier: {:x}", key_id); } if let Some(issuer) = &aki.authority_cert_issuer { for name in issuer { println!(" Cert Issuer: {}", name); } } if let Some(serial) = aki.authority_cert_serial { println!(" Cert Serial: {}", format_serial(serial)); } } ParsedExtension::BasicConstraints(bc) => { println!(" X509v3 CA: {}", bc.ca); } ParsedExtension::CRLDistributionPoints(points) => { println!(" X509v3 CRL Distribution Points:"); for point in points.iter() { if let Some(name) = &point.distribution_point { println!(" Full Name: {:?}", name); } if let Some(reasons) = &point.reasons { println!(" Reasons: {}", reasons); } if let Some(crl_issuer) = &point.crl_issuer { print!(" CRL Issuer: "); for gn in crl_issuer { print!("{} ", generalname_to_string(gn)); } println!(); } println!(); } } ParsedExtension::KeyUsage(ku) => { println!(" X509v3 Key Usage: {}", ku); } ParsedExtension::NSCertType(ty) => { println!(" Netscape Cert Type: {}", ty); } ParsedExtension::SubjectAlternativeName(san) => { for name in &san.general_names { let s = match name { GeneralName::DNSName(s) => { format!("DNS:{}", s) } GeneralName::IPAddress(b) => { let ip = match b.len() { 4 => { let b = <[u8; 4]>::try_from(*b).unwrap(); let ip = Ipv4Addr::from(b); format!("{}", ip) } 16 => { let b = <[u8; 16]>::try_from(*b).unwrap(); let ip = Ipv6Addr::from(b); format!("{}", ip) } l => format!("invalid (len={})", l), }; format!("IP Address:{}", ip) } _ => { format!("{:?}", name) } }; println!(" X509v3 SAN: {}", s); } } ParsedExtension::SubjectKeyIdentifier(id) => { println!(" X509v3 Subject Key Identifier: {:x}", id); } x => println!(" {:?}", x), } } fn print_x509_digest_algorithm(alg: &AlgorithmIdentifier, level: usize) { println!( "{:indent$}Oid: {}", "", format_oid(&alg.algorithm), indent = level ); if let Some(parameter) = &alg.parameters { let s = match parameter.tag() { Tag::Oid => { let oid = parameter.as_oid().unwrap(); format_oid(&oid) } _ => format!("{}", parameter.tag()), }; println!("{:indent$}Parameter: {}", "", s, indent = level); let bytes = parameter.as_bytes(); print_hex_dump(bytes, 32); } else { println!("{:indent$}Parameter: ", "", indent = level); } } fn print_x509_info(x509: &X509Certificate) -> io::Result<()> { let version = x509.version(); if version.0 < 3 { println!(" Version: {}", version); } else { println!(" Version: INVALID({})", version.0); } println!(" Serial: {}", x509.tbs_certificate.raw_serial_as_string()); println!(" Subject: {}", x509.subject()); println!(" Issuer: {}", x509.issuer()); println!(" Validity:"); println!(" NotBefore: {}", x509.validity().not_before); println!(" NotAfter: {}", x509.validity().not_after); println!(" is_valid: {}", x509.validity().is_valid()); println!(" Subject Public Key Info:"); print_x509_ski(x509.public_key()); print_x509_signature_algorithm(&x509.signature_algorithm, 4); println!(" Signature Value:"); for l in format_number_to_hex_with_colon(&x509.signature_value.data, 16) { println!(" {}", l); } println!(" Extensions:"); for ext in x509.extensions() { print_x509_extension(&ext.oid, ext); } println!(); print!("Structure validation status: "); #[cfg(feature = "validate")] { let mut logger = VecLogger::default(); // structure validation status let ok = X509StructureValidator .chain(X509CertificateValidator) .validate(x509, &mut logger); if ok { println!("Ok"); } else { println!("FAIL"); } for warning in logger.warnings() { println!(" [W] {}", warning); } for error in logger.errors() { println!(" [E] {}", error); } println!(); if VALIDATE_ERRORS_FATAL && !logger.errors().is_empty() { return Err(io::Error::new(io::ErrorKind::Other, "validation failed")); } } #[cfg(not(feature = "validate"))] { println!("Unknown (feature 'validate' not enabled)"); } #[cfg(feature = "verify")] { print!("Signature verification: "); if x509.subject() == x509.issuer() { if x509.verify_signature(None).is_ok() { println!("OK"); println!(" [I] certificate is self-signed"); } else if x509.subject() == x509.issuer() { println!("FAIL"); println!(" [W] certificate looks self-signed, but signature verification failed"); } } else { // if subject is different from issuer, we cannot verify certificate without the public key of the issuer println!("N/A"); } } Ok(()) } fn print_x509_signature_algorithm(signature_algorithm: &AlgorithmIdentifier, indent: usize) { match SignatureAlgorithm::try_from(signature_algorithm) { Ok(sig_alg) => { print!(" Signature Algorithm: "); match sig_alg { SignatureAlgorithm::DSA => println!("DSA"), SignatureAlgorithm::ECDSA => println!("ECDSA"), SignatureAlgorithm::ED25519 => println!("ED25519"), SignatureAlgorithm::RSA => println!("RSA"), SignatureAlgorithm::RSASSA_PSS(params) => { println!("RSASSA-PSS"); let indent_s = format!("{:indent$}", "", indent = indent + 2); println!( "{}Hash Algorithm: {}", indent_s, format_oid(params.hash_algorithm_oid()), ); print!("{}Mask Generation Function: ", indent_s); if let Ok(mask_gen) = params.mask_gen_algorithm() { println!( "{}/{}", format_oid(&mask_gen.mgf), format_oid(&mask_gen.hash), ); } else { println!("INVALID"); } println!("{}Salt Length: {}", indent_s, params.salt_length()); } SignatureAlgorithm::RSAAES_OAEP(params) => { println!("RSAAES-OAEP"); let indent_s = format!("{:indent$}", "", indent = indent + 2); println!( "{}Hash Algorithm: {}", indent_s, format_oid(params.hash_algorithm_oid()), ); print!("{}Mask Generation Function: ", indent_s); if let Ok(mask_gen) = params.mask_gen_algorithm() { println!( "{}/{}", format_oid(&mask_gen.mgf), format_oid(&mask_gen.hash), ); } else { println!("INVALID"); } println!( "{}pSourceFunc: {}", indent_s, format_oid(¶ms.p_source_alg().algorithm), ); } } } Err(e) => { eprintln!("Could not parse signature algorithm: {}", e); println!(" Signature Algorithm:"); print_x509_digest_algorithm(signature_algorithm, indent); } } } fn print_x509_ski(public_key: &SubjectPublicKeyInfo) { println!(" Public Key Algorithm:"); print_x509_digest_algorithm(&public_key.algorithm, 6); match public_key.parsed() { Ok(PublicKey::RSA(rsa)) => { println!(" RSA Public Key: ({} bit)", rsa.key_size()); // print_hex_dump(rsa.modulus, 1024); for l in format_number_to_hex_with_colon(rsa.modulus, 16) { println!(" {}", l); } if let Ok(e) = rsa.try_exponent() { println!(" exponent: 0x{:x} ({})", e, e); } else { println!(" exponent: :"); print_hex_dump(rsa.exponent, 32); } } Ok(PublicKey::EC(ec)) => { println!(" EC Public Key: ({} bit)", ec.key_size()); for l in format_number_to_hex_with_colon(ec.data(), 16) { println!(" {}", l); } // // identify curve // if let Some(params) = &public_key.algorithm.parameters { // let curve_oid = params.as_oid(); // let curve = curve_oid // .map(|oid| { // oid_registry() // .get(oid) // .map(|entry| entry.sn()) // .unwrap_or("") // }) // .unwrap_or(""); // println!(" Curve: {}", curve); // } } Ok(PublicKey::DSA(y)) => { println!(" DSA Public Key: ({} bit)", 8 * y.len()); for l in format_number_to_hex_with_colon(y, 16) { println!(" {}", l); } } Ok(PublicKey::GostR3410(y)) => { println!(" GOST R 34.10-94 Public Key: ({} bit)", 8 * y.len()); for l in format_number_to_hex_with_colon(y, 16) { println!(" {}", l); } } Ok(PublicKey::GostR3410_2012(y)) => { println!(" GOST R 34.10-2012 Public Key: ({} bit)", 8 * y.len()); for l in format_number_to_hex_with_colon(y, 16) { println!(" {}", l); } } Ok(PublicKey::Unknown(b)) => { println!(" Unknown key type"); print_hex_dump(b, 256); if let Ok((rem, res)) = der_parser::parse_der(b) { eprintln!("rem: {} bytes", rem.len()); eprintln!("{:?}", res); } else { eprintln!(" "); } } Err(_) => { println!(" INVALID PUBLIC KEY"); } } // dbg!(&public_key); // todo!(); } fn format_number_to_hex_with_colon(b: &[u8], row_size: usize) -> Vec { let mut v = Vec::with_capacity(1 + b.len() / row_size); for r in b.chunks(row_size) { let s = r.iter().fold(String::with_capacity(3 * r.len()), |a, b| { a + &format!("{:02x}:", b) }); v.push(s) } v } fn handle_certificate(file_name: &str, data: &[u8]) -> io::Result<()> { match parse_x509_certificate(data) { Ok((_, x509)) => { print_x509_info(&x509)?; Ok(()) } Err(e) => { let s = format!("Error while parsing {}: {}", file_name, e); if PARSE_ERRORS_FATAL { Err(io::Error::new(io::ErrorKind::Other, s)) } else { eprintln!("{}", s); Ok(()) } } } } pub fn main() -> io::Result<()> { for file_name in env::args().skip(1) { println!("File: {}", file_name); let data = std::fs::read(file_name.clone()).expect("Unable to read file"); if matches!((data[0], data[1]), (0x30, 0x81..=0x83)) { // probably DER handle_certificate(&file_name, &data)?; } else { // try as PEM for (n, pem) in Pem::iter_from_buffer(&data).enumerate() { match pem { Ok(pem) => { let data = &pem.contents; println!("Certificate [{}]", n); handle_certificate(&file_name, data)?; } Err(e) => { eprintln!("Error while decoding PEM entry {}: {}", n, e); } } } } } Ok(()) } rusticata-x509-parser-a41eb37/examples/print-crl.rs000066400000000000000000000113311474637314500222310ustar00rootroot00000000000000use der_parser::oid::Oid; use nom::HexDisplay; use std::cmp::min; use std::env; use std::io; use x509_parser::prelude::*; fn print_hex_dump(bytes: &[u8], max_len: usize) { let m = min(bytes.len(), max_len); print!("{}", &bytes[..m].to_hex(16)); if bytes.len() > max_len { println!("... "); } } fn format_oid(oid: &Oid) -> String { match oid2sn(oid, oid_registry()) { Ok(s) => s.to_owned(), _ => format!("{}", oid), } } fn print_authority_key_identifier(aki: &AuthorityKeyIdentifier, level: usize) { if let Some(id) = &aki.key_identifier { println!("{:indent$}keyid: {:x}", "", id, indent = level); } if aki.authority_cert_issuer.is_some() { unimplemented!(); } if let Some(serial) = aki.authority_cert_serial { let s = format_serial(serial); println!("{:indent$}serial: {}", "", &s, indent = level); } } fn print_x509_extension(oid: &Oid, ext: &X509Extension, level: usize) { match ext.parsed_extension() { ParsedExtension::CRLNumber(num) => { println!("{:indent$}X509v3 CRL Number: {}", "", num, indent = level); } ParsedExtension::ReasonCode(code) => { println!( "{:indent$}X509v3 CRL Reason Code: {}", "", code, indent = level ); } ParsedExtension::InvalidityDate(date) => { println!("{:indent$}Invalidity Date: {}", "", date, indent = level); } ParsedExtension::AuthorityKeyIdentifier(aki) => { println!( "{:indent$}X509v3 Authority Key Identifier:", "", indent = level ); print_authority_key_identifier(aki, level + 2); } x => { print!("{:indent$}{}:", "", format_oid(oid), indent = level); print!(" Critical={}", ext.critical); print!(" len={}", ext.value.len()); println!(); println!(" {:indent$}{:?}", "", x, indent = level); } } } fn print_x509_digest_algorithm(alg: &AlgorithmIdentifier, level: usize) { println!( "{:indent$}Oid: {}", "", format_oid(&alg.algorithm), indent = level ); if let Some(parameter) = &alg.parameters { println!( "{:indent$}Parameter: {:?}", "", parameter.tag(), indent = level ); let bytes = parameter.as_bytes(); print_hex_dump(bytes, 32); } else { println!("{:indent$}Parameter: ", "", indent = level); } } fn print_revoked_certificate(revoked: &RevokedCertificate, level: usize) { println!( "{:indent$}Serial number: {}", "", revoked.raw_serial_as_string(), indent = level ); println!( "{:indent$}Revocation Date: {}", "", revoked.revocation_date, indent = level + 2 ); println!("{:indent$}CRL Extensions:", "", indent = level + 2); for ext in revoked.extensions() { print_x509_extension(&ext.oid, ext, level + 4); } } fn print_crl_info(crl: &CertificateRevocationList) { println!(" Version: {}", crl.version().unwrap_or(X509Version(0))); // println!(" Subject: {}", crl.subject()); println!(" Signature Algorithm:"); print_x509_digest_algorithm(&crl.signature_algorithm, 4); println!(" Issuer: {}", crl.issuer()); // println!(" Serial: {}", crl.tbs_certificate.raw_serial_as_string()); println!(" Last Update: {}", crl.last_update()); println!( " Next Update: {}", crl.next_update() .map_or_else(|| "NONE".to_string(), |d| d.to_string()) ); println!("{:indent$}CRL Extensions:", "", indent = 2); for ext in crl.extensions() { print_x509_extension(&ext.oid, ext, 4); } println!(" Revoked certificates:"); for revoked in crl.iter_revoked_certificates() { print_revoked_certificate(revoked, 4); } println!(); } pub fn main() -> io::Result<()> { for file_name in env::args().skip(1) { // placeholder to store decoded PEM data, if needed let tmpdata; println!("File: {}", file_name); let data = std::fs::read(file_name.clone()).expect("Unable to read file"); let der_data: &[u8] = if (data[0], data[1]) == (0x30, 0x82) { // probably DER &data } else { // try as PEM let (_, data) = parse_x509_pem(&data).expect("Could not decode the PEM file"); tmpdata = data; &tmpdata.contents }; let (_, crl) = parse_x509_crl(der_data).expect("Could not decode DER data"); print_crl_info(&crl); } Ok(()) } rusticata-x509-parser-a41eb37/fuzz/000077500000000000000000000000001474637314500171325ustar00rootroot00000000000000rusticata-x509-parser-a41eb37/fuzz/Cargo.toml000066400000000000000000000015041474637314500210620ustar00rootroot00000000000000 [package] name = "x509-parser-fuzz" version = "0.0.1" authors = ["Automatically generated"] publish = false edition = "2018" [package.metadata] cargo-fuzz = true [dependencies.nom] version = "7" [dependencies.rand] version = "0.8" [dependencies.x509-parser] path = ".." [dependencies.libfuzzer-sys] # git = "https://github.com/rust-fuzz/libfuzzer-sys.git" version = "0.4.0" # Prevent this from interfering with workspaces [workspace] members = ["."] [[bin]] name = "x509_parse" path = "fuzz_targets/x509_parse.rs" [[bin]] name = "x509_with_mutator" path = "fuzz_targets/x509_with_mutator.rs" test = false doc = false [patch.crates-io] # der-parser = { path="../../der-parser" } oid-registry = { git="https://github.com/rusticata/oid-registry" } [[bin]] name = "certreq" path = "fuzz_targets/certreq.rs" test = false doc = false rusticata-x509-parser-a41eb37/fuzz/fuzz_targets/000077500000000000000000000000001474637314500216615ustar00rootroot00000000000000rusticata-x509-parser-a41eb37/fuzz/fuzz_targets/certreq.rs000066400000000000000000000002761474637314500237010ustar00rootroot00000000000000#![no_main] use libfuzzer_sys::fuzz_target; use x509_parser::prelude::*; fuzz_target!(|data: &[u8]| { // fuzzed code goes here let _ = X509CertificationRequest::from_der(data); }); rusticata-x509-parser-a41eb37/fuzz/fuzz_targets/x509_parse.rs000066400000000000000000000002421474637314500241240ustar00rootroot00000000000000#![no_main] use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { // fuzzed code goes here let _ = x509_parser::parse_x509_certificate(data); }); rusticata-x509-parser-a41eb37/fuzz/fuzz_targets/x509_with_mutator.rs000066400000000000000000000130661474637314500255500ustar00rootroot00000000000000#![no_main] use libfuzzer_sys::{fuzz_mutator, fuzz_target}; use rand::rngs::ThreadRng; use rand::seq::SliceRandom; use rand::Rng; use std::cell::RefCell; use std::ops::DerefMut; thread_local! { pub static RNG: RefCell = RefCell::new(rand::thread_rng()); } fuzz_target!(|data: &[u8]| { // fuzzed code goes here let _ = x509_parser::parse_x509_certificate(data); }); fuzz_mutator!(|data: &mut [u8], size: usize, max_size: usize, seed: u32| { const MUTATORS: &[fn(&mut [u8], usize, usize, u32) -> usize] = &[ mutator_flip_constructed, mutator_change_type, mutator_change_length, ]; let mut rng = rand::thread_rng(); let idx = rng.gen_range(0..MUTATORS.len()); MUTATORS[idx](data, size, max_size, seed) }); // adapted from https://searchfox.org/mozilla-central/source/security/nss/fuzz/asn1_mutators.cc // with changes to port to rust, and new mutators (length, etc.) fn mutator_flip_constructed(data: &mut [u8], size: usize, _max_size: usize, _seed: u32) -> usize { // eprintln!("FLIP"); let items = parse_items(data); let s = RNG.with(|rng| items.choose(rng.borrow_mut().deref_mut()).unwrap()); let s: &mut [u8] = unsafe { std::slice::from_raw_parts_mut(s.as_ptr() as *mut _, s.len()) }; // Flip "constructed" type bit s[0] ^= 0x20; size } fn mutator_change_type(data: &mut [u8], size: usize, _max_size: usize, _seed: u32) -> usize { // eprintln!("CHANGE TYPE"); let items = parse_items(data); let s = RNG.with(|rng| items.choose(rng.borrow_mut().deref_mut()).unwrap()); let s: &mut [u8] = unsafe { std::slice::from_raw_parts_mut(s.as_ptr() as *mut _, s.len()) }; // Change type to a random int [0..=30] let ty = RNG.with(|rng| rng.borrow_mut().gen_range(0..=30)); s[0] = ty; size } fn mutator_change_length(data: &mut [u8], size: usize, _max_size: usize, _seed: u32) -> usize { // eprintln!("CHANGE LENGTH"); let items = parse_items(data); let s = RNG.with(|rng| items.choose(rng.borrow_mut().deref_mut()).unwrap()); let s: &mut [u8] = unsafe { std::slice::from_raw_parts_mut(s.as_ptr() as *mut _, s.len()) }; // Change type to a random int [0..=30] let rand = RNG.with(|rng| rng.borrow_mut().gen_range(0..=1)); // check if using short-form length if s.len() < 2 { return size; } if s[1] & 0x80 == 0 { match rand { 0 => s[1] = s[1].wrapping_sub(1), _ => s[1] = s[1].wrapping_add(1), } } size } fn parse_items(data: &[u8]) -> Vec<&[u8]> { // eprintln!("PARSE_ITEMS (len: {})", data.len()); // use nom::HexDisplay; // let l = std::cmp::min(data.len(), 32); // eprintln!("{}", data[..l].to_hex(16)); let mut v = Vec::new(); // The first item is always the whole corpus. v.push(data); // we cannot use iterators here, since we are iterating and modifying v // this is safe because we call v.len() at each iteration, and we only // append elements let mut i = 0; while i < v.len() { let mut item = v[i].clone(); let mut remaining = item.len(); // Empty or primitive items have no children. if remaining == 0 || (0x20 & item[0]) == 0 { i += 1; continue; } while remaining > 2 { // dbg!(remaining); // dbg!(item); if item.len() < 2 { break; } let content = parse_item(item); if !content.is_empty() { // Record the item v.push(content); } else { break; } // Reduce number of bytes left in current item. // if remaining < content.len() { // eprintln!("remaining: {}", remaining); // eprintln!("content.len: {}", content.len()); // let l = std::cmp::min(content.len(), 32); // dbg!(&content[..l]); // panic!(); // } remaining -= std::cmp::min(content.len(), remaining); // Skip the item we just parsed item = &item[content.len()..]; } i += 1; } // eprintln!("#v: {}", v.len()); // for s in &v { // eprintln!(" 0x{:x} +{}", s.as_ptr() as usize, s.len()); // // use nom::HexDisplay; // let l = std::cmp::min(s.len(), 32); // eprintln!("{}", s[..l].to_hex(16)); // } // loop { // // // } v } // ASSERT: data.len() > 2 fn parse_item(data: &[u8]) -> &[u8] { // Short form. Bit 8 has value "0" and bits 7-1 give the length. if data[0] & 0x80 == 0 { let length = std::cmp::min(2 + data[1] as usize, data.len()); return &data[2..length]; } // Constructed, indefinite length. Read until {0x00, 0x00}. if data[1] == 0x80 { let length = data[2..] .windows(2) .position(|window| window == &[0, 0]) .unwrap_or(data.len() - 2); return &data[2..2 + length]; } // Long form. Two to 127 octets. Bit 8 of first octet has value "1" // and bits 7-1 give the number of additional length octets. let octets = std::cmp::min((data[1] & 0x7f) as usize, data.len() - 2); // Handle lengths bigger than 32 bits. if octets > 4 { // Ignore any further children, assign remaining length. return &data[2 + octets..]; } // parse the length let length = (0..octets).fold(0usize, |acc, b| (acc << 8) | (data[2 + b] as usize)); let length = std::cmp::min(2 + octets + length, data.len()); &data[2 + octets..length] } rusticata-x509-parser-a41eb37/src/000077500000000000000000000000001474637314500167235ustar00rootroot00000000000000rusticata-x509-parser-a41eb37/src/certificate.rs000066400000000000000000000670001474637314500215560ustar00rootroot00000000000000//! X.509 Certificate object definitions and operations use crate::error::{X509Error, X509Result}; use crate::extensions::*; use crate::time::ASN1Time; use crate::utils::format_serial; #[cfg(feature = "validate")] use crate::validate::*; use crate::x509::{ parse_serial, parse_signature_value, AlgorithmIdentifier, SubjectPublicKeyInfo, X509Name, X509Version, }; #[cfg(feature = "verify")] use crate::verify::verify_signature; use asn1_rs::{BitString, FromDer, OptTaggedImplicit}; use core::ops::Deref; use der_parser::der::*; use der_parser::error::*; use der_parser::num_bigint::BigUint; use der_parser::*; use nom::{Offset, Parser}; use oid_registry::*; use std::collections::HashMap; use time::Duration; /// An X.509 v3 Certificate. /// /// X.509 v3 certificates are defined in [RFC5280](https://tools.ietf.org/html/rfc5280), section /// 4.1. This object uses the same structure for content, so for ex the subject can be accessed /// using the path `x509.tbs_certificate.subject`. /// /// `X509Certificate` also contains convenience methods to access the most common fields (subject, /// issuer, etc.). These are provided using `Deref`, so documentation for /// these methods can be found in the [`TbsCertificate`] object. /// /// A `X509Certificate` is a zero-copy view over a buffer, so the lifetime is the same as the /// buffer containing the binary representation. /// /// ```rust /// # use x509_parser::prelude::FromDer; /// # use x509_parser::certificate::X509Certificate; /// # /// # static DER: &'static [u8] = include_bytes!("../assets/IGC_A.der"); /// # /// fn display_x509_info(x509: &X509Certificate<'_>) { /// let subject = x509.subject(); /// let issuer = x509.issuer(); /// println!("X.509 Subject: {}", subject); /// println!("X.509 Issuer: {}", issuer); /// println!("X.509 serial: {}", x509.tbs_certificate.raw_serial_as_string()); /// } /// # /// # fn main() { /// # let res = X509Certificate::from_der(DER); /// # match res { /// # Ok((_rem, x509)) => { /// # display_x509_info(&x509); /// # }, /// # _ => panic!("x509 parsing failed: {:?}", res), /// # } /// # } /// ``` #[derive(Clone, Debug, PartialEq)] pub struct X509Certificate<'a> { pub tbs_certificate: TbsCertificate<'a>, pub signature_algorithm: AlgorithmIdentifier<'a>, pub signature_value: BitString<'a>, } impl X509Certificate<'_> { /// Verify the cryptographic signature of this certificate /// /// `public_key` is the public key of the **signer**. For a self-signed certificate, /// (for ex. a public root certificate authority), this is the key from the certificate, /// so you can use `None`. /// /// For a leaf certificate, this is the public key of the certificate that signed it. /// It is usually an intermediate authority. /// /// Not all algorithms are supported, this function is limited to what `ring` supports. #[cfg(feature = "verify")] #[cfg_attr(docsrs, doc(cfg(feature = "verify")))] pub fn verify_signature( &self, public_key: Option<&SubjectPublicKeyInfo>, ) -> Result<(), X509Error> { let spki = public_key.unwrap_or_else(|| self.public_key()); verify_signature( spki, &self.signature_algorithm, &self.signature_value, self.tbs_certificate.raw, ) } } impl<'a> Deref for X509Certificate<'a> { type Target = TbsCertificate<'a>; fn deref(&self) -> &Self::Target { &self.tbs_certificate } } impl<'a> FromDer<'a, X509Error> for X509Certificate<'a> { /// Parse a DER-encoded X.509 Certificate, and return the remaining of the input and the built /// object. /// /// The returned object uses zero-copy, and so has the same lifetime as the input. /// /// Note that only parsing is done, not validation. /// ///
    /// Certificate  ::=  SEQUENCE  {
    ///         tbsCertificate       TBSCertificate,
    ///         signatureAlgorithm   AlgorithmIdentifier,
    ///         signatureValue       BIT STRING  }
    /// 
/// /// # Example /// /// To parse a certificate and print the subject and issuer: /// /// ```rust /// # use x509_parser::parse_x509_certificate; /// # /// # static DER: &'static [u8] = include_bytes!("../assets/IGC_A.der"); /// # /// # fn main() { /// let res = parse_x509_certificate(DER); /// match res { /// Ok((_rem, x509)) => { /// let subject = x509.subject(); /// let issuer = x509.issuer(); /// println!("X.509 Subject: {}", subject); /// println!("X.509 Issuer: {}", issuer); /// }, /// _ => panic!("x509 parsing failed: {:?}", res), /// } /// # } /// ``` fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { // run parser with default options X509CertificateParser::new().parse(i) } } /// X.509 Certificate parser /// /// This object is a parser builder, and allows specifying parsing options. /// Currently, the only option is to control deep parsing of X.509v3 extensions: /// a parser can decide to skip deep-parsing to be faster (the structure of extensions is still /// parsed, and the contents can be parsed later using the [`from_der`](FromDer::from_der) /// method from individual extension objects). /// /// This object uses the `nom::Parser` trait, which must be imported. /// /// # Example /// /// To parse a certificate without parsing extensions: /// /// ```rust /// use x509_parser::certificate::X509CertificateParser; /// use x509_parser::nom::Parser; /// /// # static DER: &'static [u8] = include_bytes!("../assets/IGC_A.der"); /// # /// # fn main() { /// // create a parser that will not parse extensions /// let mut parser = X509CertificateParser::new() /// .with_deep_parse_extensions(false); /// let res = parser.parse(DER); /// match res { /// Ok((_rem, x509)) => { /// let subject = x509.subject(); /// let issuer = x509.issuer(); /// println!("X.509 Subject: {}", subject); /// println!("X.509 Issuer: {}", issuer); /// }, /// _ => panic!("x509 parsing failed: {:?}", res), /// } /// # } /// ``` #[derive(Clone, Copy, Debug)] pub struct X509CertificateParser { deep_parse_extensions: bool, // strict: bool, } impl X509CertificateParser { #[inline] pub const fn new() -> Self { X509CertificateParser { deep_parse_extensions: true, } } #[inline] pub const fn with_deep_parse_extensions(self, deep_parse_extensions: bool) -> Self { X509CertificateParser { deep_parse_extensions, } } } impl Default for X509CertificateParser { fn default() -> Self { X509CertificateParser::new() } } impl<'a> Parser<&'a [u8], X509Certificate<'a>, X509Error> for X509CertificateParser { fn parse(&mut self, input: &'a [u8]) -> IResult<&'a [u8], X509Certificate<'a>, X509Error> { parse_der_sequence_defined_g(|i, _| { // pass options to TbsCertificate parser let mut tbs_parser = TbsCertificateParser::new().with_deep_parse_extensions(self.deep_parse_extensions); let (i, tbs_certificate) = tbs_parser.parse(i)?; let (i, signature_algorithm) = AlgorithmIdentifier::from_der(i)?; let (i, signature_value) = parse_signature_value(i)?; let cert = X509Certificate { tbs_certificate, signature_algorithm, signature_value, }; Ok((i, cert)) })(input) } } #[allow(deprecated)] #[cfg(feature = "validate")] #[cfg_attr(docsrs, doc(cfg(feature = "validate")))] impl Validate for X509Certificate<'_> { fn validate(&self, warn: W, err: E) -> bool where W: FnMut(&str), E: FnMut(&str), { X509StructureValidator.validate(self, &mut CallbackLogger::new(warn, err)) } } /// The sequence `TBSCertificate` contains information associated with the /// subject of the certificate and the CA that issued it. /// /// RFC5280 definition: /// ///
///   TBSCertificate  ::=  SEQUENCE  {
///        version         [0]  EXPLICIT Version DEFAULT v1,
///        serialNumber         CertificateSerialNumber,
///        signature            AlgorithmIdentifier,
///        issuer               Name,
///        validity             Validity,
///        subject              Name,
///        subjectPublicKeyInfo SubjectPublicKeyInfo,
///        issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
///                             -- If present, version MUST be v2 or v3
///        subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
///                             -- If present, version MUST be v2 or v3
///        extensions      [3]  EXPLICIT Extensions OPTIONAL
///                             -- If present, version MUST be v3
///        }
/// 
#[derive(Clone, Debug, PartialEq)] pub struct TbsCertificate<'a> { pub version: X509Version, pub serial: BigUint, pub signature: AlgorithmIdentifier<'a>, pub issuer: X509Name<'a>, pub validity: Validity, pub subject: X509Name<'a>, pub subject_pki: SubjectPublicKeyInfo<'a>, pub issuer_uid: Option>, pub subject_uid: Option>, extensions: Vec>, pub(crate) raw: &'a [u8], pub(crate) raw_serial: &'a [u8], } impl<'a> TbsCertificate<'a> { /// Get the version of the encoded certificate pub fn version(&self) -> X509Version { self.version } /// Get the certificate subject. #[inline] pub fn subject(&self) -> &X509Name { &self.subject } /// Get the certificate issuer. #[inline] pub fn issuer(&self) -> &X509Name { &self.issuer } /// Get the certificate validity. #[inline] pub fn validity(&self) -> &Validity { &self.validity } /// Get the certificate public key information. #[inline] pub fn public_key(&self) -> &SubjectPublicKeyInfo { &self.subject_pki } /// Returns the certificate extensions #[inline] pub fn extensions(&self) -> &[X509Extension<'a>] { &self.extensions } /// Returns an iterator over the certificate extensions #[inline] pub fn iter_extensions(&self) -> impl Iterator> { self.extensions.iter() } /// Searches for an extension with the given `Oid`. /// /// Return `Ok(Some(extension))` if exactly one was found, `Ok(None)` if none was found, /// or an error `DuplicateExtensions` if the extension is present twice or more. #[inline] pub fn get_extension_unique(&self, oid: &Oid) -> Result>, X509Error> { get_extension_unique(&self.extensions, oid) } /// Searches for an extension with the given `Oid`. /// /// ## Duplicate extensions /// /// Note: if there are several extensions with the same `Oid`, the first one is returned, masking other values. /// /// RFC5280 forbids having duplicate extensions, but does not specify how errors should be handled. /// /// **Because of this, the `find_extension` method is not safe and should not be used!** /// The [`get_extension_unique`](Self::get_extension_unique) method checks for duplicate extensions and should be /// preferred. #[deprecated( since = "0.13.0", note = "Do not use this function (duplicate extensions are not checked), use `get_extension_unique`" )] pub fn find_extension(&self, oid: &Oid) -> Option<&X509Extension<'a>> { self.extensions.iter().find(|&ext| ext.oid == *oid) } /// Builds and returns a map of extensions. /// /// If an extension is present twice, this will fail and return `DuplicateExtensions`. pub fn extensions_map(&self) -> Result>, X509Error> { self.extensions .iter() .try_fold(HashMap::new(), |mut m, ext| { if m.contains_key(&ext.oid) { return Err(X509Error::DuplicateExtensions); } m.insert(ext.oid.clone(), ext); Ok(m) }) } /// Attempt to get the certificate Basic Constraints extension /// /// Return `Ok(Some(extension))` if exactly one was found, `Ok(None)` if none was found, /// or an error if the extension is present twice or more. pub fn basic_constraints( &self, ) -> Result>, X509Error> { let r = self .get_extension_unique(&OID_X509_EXT_BASIC_CONSTRAINTS)? .and_then(|ext| match ext.parsed_extension { ParsedExtension::BasicConstraints(ref bc) => { Some(BasicExtension::new(ext.critical, bc)) } _ => None, }); Ok(r) } /// Attempt to get the certificate Key Usage extension /// /// Return `Ok(Some(extension))` if exactly one was found, `Ok(None)` if none was found, /// or an error if the extension is invalid, or is present twice or more. pub fn key_usage(&self) -> Result>, X509Error> { self.get_extension_unique(&OID_X509_EXT_KEY_USAGE)? .map_or(Ok(None), |ext| match ext.parsed_extension { ParsedExtension::KeyUsage(ref value) => { Ok(Some(BasicExtension::new(ext.critical, value))) } _ => Err(X509Error::InvalidExtensions), }) } /// Attempt to get the certificate Extended Key Usage extension /// /// Return `Ok(Some(extension))` if exactly one was found, `Ok(None)` if none was found, /// or an error if the extension is invalid, or is present twice or more. pub fn extended_key_usage( &self, ) -> Result>, X509Error> { self.get_extension_unique(&OID_X509_EXT_EXTENDED_KEY_USAGE)? .map_or(Ok(None), |ext| match ext.parsed_extension { ParsedExtension::ExtendedKeyUsage(ref value) => { Ok(Some(BasicExtension::new(ext.critical, value))) } _ => Err(X509Error::InvalidExtensions), }) } /// Attempt to get the certificate Policy Constraints extension /// /// Return `Ok(Some(extension))` if exactly one was found, `Ok(None)` if none was found, /// or an error if the extension is invalid, or is present twice or more. pub fn policy_constraints( &self, ) -> Result>, X509Error> { self.get_extension_unique(&OID_X509_EXT_POLICY_CONSTRAINTS)? .map_or(Ok(None), |ext| match ext.parsed_extension { ParsedExtension::PolicyConstraints(ref value) => { Ok(Some(BasicExtension::new(ext.critical, value))) } _ => Err(X509Error::InvalidExtensions), }) } /// Attempt to get the certificate Policy Constraints extension /// /// Return `Ok(Some(extension))` if exactly one was found, `Ok(None)` if none was found, /// or an error if the extension is invalid, or is present twice or more. pub fn inhibit_anypolicy( &self, ) -> Result>, X509Error> { self.get_extension_unique(&OID_X509_EXT_INHIBITANT_ANY_POLICY)? .map_or(Ok(None), |ext| match ext.parsed_extension { ParsedExtension::InhibitAnyPolicy(ref value) => { Ok(Some(BasicExtension::new(ext.critical, value))) } _ => Err(X509Error::InvalidExtensions), }) } /// Attempt to get the certificate Policy Mappings extension /// /// Return `Ok(Some(extension))` if exactly one was found, `Ok(None)` if none was found, /// or an error if the extension is invalid, or is present twice or more. pub fn policy_mappings(&self) -> Result>, X509Error> { self.get_extension_unique(&OID_X509_EXT_POLICY_MAPPINGS)? .map_or(Ok(None), |ext| match ext.parsed_extension { ParsedExtension::PolicyMappings(ref value) => { Ok(Some(BasicExtension::new(ext.critical, value))) } _ => Err(X509Error::InvalidExtensions), }) } /// Attempt to get the certificate Subject Alternative Name extension /// /// Return `Ok(Some(extension))` if exactly one was found, `Ok(None)` if none was found, /// or an error if the extension is invalid, or is present twice or more. pub fn subject_alternative_name( &self, ) -> Result>>, X509Error> { self.get_extension_unique(&OID_X509_EXT_SUBJECT_ALT_NAME)? .map_or(Ok(None), |ext| match ext.parsed_extension { ParsedExtension::SubjectAlternativeName(ref value) => { Ok(Some(BasicExtension::new(ext.critical, value))) } _ => Err(X509Error::InvalidExtensions), }) } /// Attempt to get the certificate Name Constraints extension /// /// Return `Ok(Some(extension))` if exactly one was found, `Ok(None)` if none was found, /// or an error if the extension is invalid, or is present twice or more. pub fn name_constraints(&self) -> Result>, X509Error> { self.get_extension_unique(&OID_X509_EXT_NAME_CONSTRAINTS)? .map_or(Ok(None), |ext| match ext.parsed_extension { ParsedExtension::NameConstraints(ref value) => { Ok(Some(BasicExtension::new(ext.critical, value))) } _ => Err(X509Error::InvalidExtensions), }) } /// Returns true if certificate has `basicConstraints CA:true` pub fn is_ca(&self) -> bool { self.basic_constraints() .unwrap_or(None) .map(|ext| ext.value.ca) .unwrap_or(false) } /// Get the raw bytes of the certificate serial number pub fn raw_serial(&self) -> &'a [u8] { self.raw_serial } /// Get a formatted string of the certificate serial number, separated by ':' pub fn raw_serial_as_string(&self) -> String { format_serial(self.raw_serial) } } /// Searches for an extension with the given `Oid`. /// /// Note: if there are several extensions with the same `Oid`, an error `DuplicateExtensions` is returned. fn get_extension_unique<'a, 'b>( extensions: &'a [X509Extension<'b>], oid: &Oid, ) -> Result>, X509Error> { let mut res = None; for ext in extensions { if ext.oid == *oid { if res.is_some() { return Err(X509Error::DuplicateExtensions); } res = Some(ext); } } Ok(res) } impl AsRef<[u8]> for TbsCertificate<'_> { #[inline] fn as_ref(&self) -> &[u8] { self.raw } } impl<'a> FromDer<'a, X509Error> for TbsCertificate<'a> { /// Parse a DER-encoded TbsCertificate object /// ///
    /// TBSCertificate  ::=  SEQUENCE  {
    ///      version         [0]  Version DEFAULT v1,
    ///      serialNumber         CertificateSerialNumber,
    ///      signature            AlgorithmIdentifier,
    ///      issuer               Name,
    ///      validity             Validity,
    ///      subject              Name,
    ///      subjectPublicKeyInfo SubjectPublicKeyInfo,
    ///      issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
    ///                           -- If present, version MUST be v2 or v3
    ///      subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
    ///                           -- If present, version MUST be v2 or v3
    ///      extensions      [3]  Extensions OPTIONAL
    ///                           -- If present, version MUST be v3 --  }
    /// 
fn from_der(i: &'a [u8]) -> X509Result<'a, TbsCertificate<'a>> { let start_i = i; parse_der_sequence_defined_g(move |i, _| { let (i, version) = X509Version::from_der_tagged_0(i)?; let (i, serial) = parse_serial(i)?; let (i, signature) = AlgorithmIdentifier::from_der(i)?; let (i, issuer) = X509Name::from_der(i)?; let (i, validity) = Validity::from_der(i)?; let (i, subject) = X509Name::from_der(i)?; let (i, subject_pki) = SubjectPublicKeyInfo::from_der(i)?; let (i, issuer_uid) = UniqueIdentifier::from_der_issuer(i)?; let (i, subject_uid) = UniqueIdentifier::from_der_subject(i)?; let (i, extensions) = parse_extensions(i, Tag(3))?; let len = start_i.offset(i); let tbs = TbsCertificate { version, serial: serial.1, signature, issuer, validity, subject, subject_pki, issuer_uid, subject_uid, extensions, raw: &start_i[..len], raw_serial: serial.0, }; Ok((i, tbs)) })(i) } } /// `TbsCertificate` parser builder #[derive(Clone, Copy, Debug)] pub struct TbsCertificateParser { deep_parse_extensions: bool, } impl TbsCertificateParser { #[inline] pub const fn new() -> Self { TbsCertificateParser { deep_parse_extensions: true, } } #[inline] pub const fn with_deep_parse_extensions(self, deep_parse_extensions: bool) -> Self { TbsCertificateParser { deep_parse_extensions, } } } impl Default for TbsCertificateParser { fn default() -> Self { TbsCertificateParser::new() } } impl<'a> Parser<&'a [u8], TbsCertificate<'a>, X509Error> for TbsCertificateParser { fn parse(&mut self, input: &'a [u8]) -> IResult<&'a [u8], TbsCertificate<'a>, X509Error> { let start_i = input; parse_der_sequence_defined_g(move |i, _| { let (i, version) = X509Version::from_der_tagged_0(i)?; let (i, serial) = parse_serial(i)?; let (i, signature) = AlgorithmIdentifier::from_der(i)?; let (i, issuer) = X509Name::from_der(i)?; let (i, validity) = Validity::from_der(i)?; let (i, subject) = X509Name::from_der(i)?; let (i, subject_pki) = SubjectPublicKeyInfo::from_der(i)?; let (i, issuer_uid) = UniqueIdentifier::from_der_issuer(i)?; let (i, subject_uid) = UniqueIdentifier::from_der_subject(i)?; let (i, extensions) = if self.deep_parse_extensions { parse_extensions(i, Tag(3))? } else { parse_extensions_envelope(i, Tag(3))? }; let len = start_i.offset(i); let tbs = TbsCertificate { version, serial: serial.1, signature, issuer, validity, subject, subject_pki, issuer_uid, subject_uid, extensions, raw: &start_i[..len], raw_serial: serial.0, }; Ok((i, tbs)) })(input) } } #[allow(deprecated)] #[cfg(feature = "validate")] #[cfg_attr(docsrs, doc(cfg(feature = "validate")))] impl Validate for TbsCertificate<'_> { fn validate(&self, warn: W, err: E) -> bool where W: FnMut(&str), E: FnMut(&str), { TbsCertificateStructureValidator.validate(self, &mut CallbackLogger::new(warn, err)) } } /// Basic extension structure, used in search results #[derive(Debug, PartialEq, Eq)] pub struct BasicExtension { pub critical: bool, pub value: T, } impl BasicExtension { pub const fn new(critical: bool, value: T) -> Self { Self { critical, value } } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct Validity { pub not_before: ASN1Time, pub not_after: ASN1Time, } impl Validity { /// The time left before the certificate expires. /// /// If the certificate is not currently valid, then `None` is /// returned. Otherwise, the `Duration` until the certificate /// expires is returned. pub fn time_to_expiration(&self) -> Option { let now = ASN1Time::now(); if !self.is_valid_at(now) { return None; } // Note that the duration below is guaranteed to be positive, // since we just checked that now < na self.not_after - now } /// Check the certificate time validity for the provided date/time #[inline] pub fn is_valid_at(&self, time: ASN1Time) -> bool { time >= self.not_before && time <= self.not_after } /// Check the certificate time validity #[inline] pub fn is_valid(&self) -> bool { self.is_valid_at(ASN1Time::now()) } } impl FromDer<'_, X509Error> for Validity { fn from_der(i: &[u8]) -> X509Result { parse_der_sequence_defined_g(|i, _| { let (i, not_before) = ASN1Time::from_der(i)?; let (i, not_after) = ASN1Time::from_der(i)?; let v = Validity { not_before, not_after, }; Ok((i, v)) })(i) } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct UniqueIdentifier<'a>(pub BitString<'a>); impl<'a> UniqueIdentifier<'a> { // issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL fn from_der_issuer(i: &'a [u8]) -> X509Result<'a, Option> { Self::parse::<1>(i).map_err(|_| X509Error::InvalidIssuerUID.into()) } // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL fn from_der_subject(i: &[u8]) -> X509Result> { Self::parse::<2>(i).map_err(|_| X509Error::InvalidSubjectUID.into()) } // Parse a [tag] UniqueIdentifier OPTIONAL // // UniqueIdentifier ::= BIT STRING fn parse(i: &[u8]) -> BerResult> { let (rem, unique_id) = OptTaggedImplicit::::from_der(i)?; let unique_id = unique_id.map(|u| UniqueIdentifier(u.into_inner())); Ok((rem, unique_id)) } } #[cfg(test)] mod tests { use super::*; #[test] fn check_validity_expiration() { let mut v = Validity { not_before: ASN1Time::now(), not_after: ASN1Time::now(), }; assert_eq!(v.time_to_expiration(), None); v.not_after = (v.not_after + Duration::new(60, 0)).unwrap(); assert!(v.time_to_expiration().is_some()); assert!(v.time_to_expiration().unwrap() <= Duration::new(60, 0)); // The following assumes this timing won't take 10 seconds... I // think that is safe. assert!(v.time_to_expiration().unwrap() > Duration::new(50, 0)); } #[test] fn extension_duplication() { let extensions = vec![ X509Extension::new(oid! {1.2}, true, &[], ParsedExtension::Unparsed), X509Extension::new(oid! {1.3}, true, &[], ParsedExtension::Unparsed), X509Extension::new(oid! {1.2}, true, &[], ParsedExtension::Unparsed), X509Extension::new(oid! {1.4}, true, &[], ParsedExtension::Unparsed), X509Extension::new(oid! {1.4}, true, &[], ParsedExtension::Unparsed), ]; let r2 = get_extension_unique(&extensions, &oid! {1.2}); assert!(r2.is_err()); let r3 = get_extension_unique(&extensions, &oid! {1.3}); assert!(r3.is_ok()); let r4 = get_extension_unique(&extensions, &oid! {1.4}); assert!(r4.is_err()); } } rusticata-x509-parser-a41eb37/src/certification_request.rs000066400000000000000000000133451474637314500236720ustar00rootroot00000000000000use crate::cri_attributes::*; use crate::error::{X509Error, X509Result}; use crate::extensions::*; use crate::x509::{ parse_signature_value, AlgorithmIdentifier, SubjectPublicKeyInfo, X509Name, X509Version, }; #[cfg(feature = "verify")] use crate::verify::verify_signature; use asn1_rs::{BitString, FromDer}; use der_parser::der::*; use der_parser::*; use nom::Offset; use std::collections::HashMap; /// Certification Signing Request (CSR) #[derive(Debug, PartialEq)] pub struct X509CertificationRequest<'a> { pub certification_request_info: X509CertificationRequestInfo<'a>, pub signature_algorithm: AlgorithmIdentifier<'a>, pub signature_value: BitString<'a>, } impl X509CertificationRequest<'_> { pub fn requested_extensions(&self) -> Option> { self.certification_request_info .iter_attributes() .find_map(|attr| { if let ParsedCriAttribute::ExtensionRequest(requested) = &attr.parsed_attribute { Some(requested.extensions.iter().map(|ext| &ext.parsed_extension)) } else { None } }) } /// Verify the cryptographic signature of this certification request /// /// Uses the public key contained in the CSR, which must be the one of the entity /// requesting the certification for this verification to succeed. #[cfg(feature = "verify")] pub fn verify_signature(&self) -> Result<(), X509Error> { let spki = &self.certification_request_info.subject_pki; verify_signature( spki, &self.signature_algorithm, &self.signature_value, self.certification_request_info.raw, ) } } ///
/// CertificationRequest ::= SEQUENCE {
///     certificationRequestInfo CertificationRequestInfo,
///     signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }},
///     signature          BIT STRING
/// }
/// 
impl<'a> FromDer<'a, X509Error> for X509CertificationRequest<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parse_der_sequence_defined_g(|i, _| { let (i, certification_request_info) = X509CertificationRequestInfo::from_der(i)?; let (i, signature_algorithm) = AlgorithmIdentifier::from_der(i)?; let (i, signature_value) = parse_signature_value(i)?; let cert = X509CertificationRequest { certification_request_info, signature_algorithm, signature_value, }; Ok((i, cert)) })(i) } } /// Certification Request Info structure /// /// Certification request information is defined by the following ASN.1 structure: /// ///
/// CertificationRequestInfo ::= SEQUENCE {
///      version       INTEGER { v1(0) } (v1,...),
///      subject       Name,
///      subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }},
///      attributes    [0] Attributes{{ CRIAttributes }}
/// }
/// 
/// /// version is the version number; subject is the distinguished name of the certificate /// subject; subject_pki contains information about the public key being certified, and /// attributes is a collection of attributes providing additional information about the /// subject of the certificate. #[derive(Debug, PartialEq)] pub struct X509CertificationRequestInfo<'a> { pub version: X509Version, pub subject: X509Name<'a>, pub subject_pki: SubjectPublicKeyInfo<'a>, attributes: Vec>, pub raw: &'a [u8], } impl X509CertificationRequestInfo<'_> { /// Get the CRL entry extensions. #[inline] pub fn attributes(&self) -> &[X509CriAttribute] { &self.attributes } /// Returns an iterator over the CRL entry extensions #[inline] pub fn iter_attributes(&self) -> impl Iterator { self.attributes.iter() } /// Searches for a CRL entry extension with the given `Oid`. /// /// Note: if there are several extensions with the same `Oid`, the first one is returned. pub fn find_attribute(&self, oid: &Oid) -> Option<&X509CriAttribute> { self.attributes.iter().find(|&ext| ext.oid == *oid) } /// Builds and returns a map of CRL entry extensions. /// /// If an extension is present twice, this will fail and return `DuplicateExtensions`. pub fn attributes_map(&self) -> Result, X509Error> { self.attributes .iter() .try_fold(HashMap::new(), |mut m, ext| { if m.contains_key(&ext.oid) { return Err(X509Error::DuplicateAttributes); } m.insert(ext.oid.clone(), ext); Ok(m) }) } } ///
/// CertificationRequestInfo ::= SEQUENCE {
///      version       INTEGER { v1(0) } (v1,...),
///      subject       Name,
///      subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }},
///      attributes    [0] Attributes{{ CRIAttributes }}
/// }
/// 
impl<'a> FromDer<'a, X509Error> for X509CertificationRequestInfo<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { let start_i = i; parse_der_sequence_defined_g(move |i, _| { let (i, version) = X509Version::from_der(i)?; let (i, subject) = X509Name::from_der(i)?; let (i, subject_pki) = SubjectPublicKeyInfo::from_der(i)?; let (i, attributes) = parse_cri_attributes(i)?; let len = start_i.offset(i); let tbs = X509CertificationRequestInfo { version, subject, subject_pki, attributes, raw: &start_i[..len], }; Ok((i, tbs)) })(i) } } rusticata-x509-parser-a41eb37/src/cri_attributes.rs000066400000000000000000000136151474637314500223220ustar00rootroot00000000000000use crate::{ error::{X509Error, X509Result}, extensions::X509Extension, }; use asn1_rs::{Error, FromDer, Header, Oid, Sequence, Tag}; use nom::combinator::{all_consuming, complete}; use nom::multi::many0; use nom::Err; use oid_registry::*; use std::collections::HashMap; /// Attributes for Certification Request #[derive(Clone, Debug, PartialEq)] pub struct X509CriAttribute<'a> { pub oid: Oid<'a>, pub value: &'a [u8], pub(crate) parsed_attribute: ParsedCriAttribute<'a>, } impl<'a> FromDer<'a, X509Error> for X509CriAttribute<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, X509CriAttribute<'a>> { Sequence::from_ber_and_then(i, |i| { let (i, oid) = Oid::from_der(i)?; let value_start = i; let (i, hdr) = Header::from_der(i)?; if hdr.tag() != Tag::Set { return Err(Err::Error(Error::BerTypeError)); }; let (i, parsed_attribute) = parser::parse_attribute(i, &oid).map_err(|_| Err::Error(Error::BerValueError))?; let attribute = X509CriAttribute { oid, value: &value_start[..value_start.len() - i.len()], parsed_attribute, }; Ok((i, attribute)) }) .map_err(|_| X509Error::InvalidAttributes.into()) } } impl<'a> X509CriAttribute<'a> { /// Return the attribute type or `UnsupportedAttribute` if the attribute is unknown. #[inline] pub fn parsed_attribute(&self) -> &ParsedCriAttribute<'a> { &self.parsed_attribute } } /// Section 3.1 of rfc 5272 #[derive(Clone, Debug, PartialEq)] pub struct ExtensionRequest<'a> { pub extensions: Vec>, } impl<'a> FromDer<'a, X509Error> for ExtensionRequest<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parser::parse_extension_request(i).map_err(Err::convert) } } #[derive(Clone, Debug, Eq, PartialEq)] pub struct ChallengePassword(pub String); /// Attributes for Certification Request #[derive(Clone, Debug, PartialEq)] pub enum ParsedCriAttribute<'a> { ChallengePassword(ChallengePassword), ExtensionRequest(ExtensionRequest<'a>), UnsupportedAttribute, } pub(crate) mod parser { use crate::cri_attributes::*; use der_parser::der::{ parse_der_bmpstring, parse_der_printablestring, parse_der_t61string, parse_der_universalstring, parse_der_utf8string, }; use lazy_static::lazy_static; use nom::branch::alt; use nom::combinator::map; type AttrParser = fn(&[u8]) -> X509Result; lazy_static! { static ref ATTRIBUTE_PARSERS: HashMap, AttrParser> = { macro_rules! add { ($m:ident, $oid:ident, $p:ident) => { $m.insert($oid, $p as AttrParser); }; } let mut m = HashMap::new(); add!(m, OID_PKCS9_EXTENSION_REQUEST, parse_extension_request_attr); add!( m, OID_PKCS9_CHALLENGE_PASSWORD, parse_challenge_password_attr ); m }; } // look into the parser map if the extension is known, and parse it // otherwise, leave it as UnsupportedExtension pub(crate) fn parse_attribute<'a>( i: &'a [u8], oid: &Oid, ) -> X509Result<'a, ParsedCriAttribute<'a>> { if let Some(parser) = ATTRIBUTE_PARSERS.get(oid) { parser(i) } else { Ok((i, ParsedCriAttribute::UnsupportedAttribute)) } } pub(super) fn parse_extension_request(i: &[u8]) -> X509Result { crate::extensions::parse_extension_sequence(i) .map(|(i, extensions)| (i, ExtensionRequest { extensions })) } fn parse_extension_request_attr(i: &[u8]) -> X509Result { map( parse_extension_request, ParsedCriAttribute::ExtensionRequest, )(i) } // RFC 2985, 5.4.1 Challenge password // challengePassword ATTRIBUTE ::= { // WITH SYNTAX DirectoryString {pkcs-9-ub-challengePassword} // EQUALITY MATCHING RULE caseExactMatch // SINGLE VALUE TRUE // ID pkcs-9-at-challengePassword // } // RFC 5280, 4.1.2.4. Issuer // DirectoryString ::= CHOICE { // teletexString TeletexString (SIZE (1..MAX)), // printableString PrintableString (SIZE (1..MAX)), // universalString UniversalString (SIZE (1..MAX)), // utf8String UTF8String (SIZE (1..MAX)), // bmpString BMPString (SIZE (1..MAX)) // } pub(super) fn parse_challenge_password(i: &[u8]) -> X509Result { let (rem, obj) = match alt(( parse_der_utf8string, parse_der_printablestring, parse_der_universalstring, parse_der_bmpstring, parse_der_t61string, // == teletexString ))(i) { Ok((rem, obj)) => (rem, obj), Err(_) => return Err(Err::Error(X509Error::InvalidAttributes)), }; match obj.content.as_str() { Ok(s) => Ok((rem, ChallengePassword(s.to_string()))), Err(_) => Err(Err::Error(X509Error::InvalidAttributes)), } } fn parse_challenge_password_attr(i: &[u8]) -> X509Result { map( parse_challenge_password, ParsedCriAttribute::ChallengePassword, )(i) } } pub(crate) fn parse_cri_attributes(i: &[u8]) -> X509Result> { let (i, hdr) = Header::from_der(i).map_err(|_| Err::Error(X509Error::InvalidAttributes))?; if hdr.is_contextspecific() && hdr.tag().0 == 0 { all_consuming(many0(complete(X509CriAttribute::from_der)))(i) } else { Err(Err::Error(X509Error::InvalidAttributes)) } } rusticata-x509-parser-a41eb37/src/error.rs000066400000000000000000000064311474637314500204260ustar00rootroot00000000000000//! X.509 errors use der_parser::error::BerError; use nom::error::{ErrorKind, ParseError}; use nom::IResult; /// An error that can occur while converting an OID to a Nid. #[derive(Debug, PartialEq, Eq)] pub struct NidError; /// Holds the result of parsing functions (X.509) /// /// Note that this type is also a `Result`, so usual functions (`map`, `unwrap` etc.) are available. pub type X509Result<'a, T> = IResult<&'a [u8], T, X509Error>; /// An error that can occur while parsing or validating a certificate. #[derive(Clone, Debug, PartialEq, thiserror::Error)] pub enum X509Error { #[error("generic error")] Generic, #[error("invalid version")] InvalidVersion, #[error("invalid serial")] InvalidSerial, #[error("invalid algorithm identifier")] InvalidAlgorithmIdentifier, #[error("invalid X.509 name")] InvalidX509Name, #[error("invalid date")] InvalidDate, #[error("invalid X.509 Subject Public Key Info")] InvalidSPKI, #[error("invalid X.509 Subject Unique ID")] InvalidSubjectUID, #[error("invalid X.509 Issuer Unique ID")] InvalidIssuerUID, #[error("invalid extensions")] InvalidExtensions, #[error("invalid attributes")] InvalidAttributes, #[error("duplicate extensions")] DuplicateExtensions, #[error("duplicate attributes")] DuplicateAttributes, #[error("invalid Signature DER Value")] InvalidSignatureValue, #[error("invalid TBS certificate")] InvalidTbsCertificate, // error types from CRL #[error("invalid User certificate")] InvalidUserCertificate, /// Top-level certificate structure is invalid #[error("invalid certificate")] InvalidCertificate, #[error("signature verification error")] SignatureVerificationError, #[error("signature unsupported algorithm")] SignatureUnsupportedAlgorithm, #[error("invalid number")] InvalidNumber, #[error("BER error: {0}")] Der(#[from] BerError), #[error("nom error: {0:?}")] NomError(ErrorKind), } impl From> for X509Error { fn from(e: nom::Err) -> Self { Self::Der(BerError::from(e)) } } impl From> for X509Error { fn from(e: nom::Err) -> Self { match e { nom::Err::Error(e) | nom::Err::Failure(e) => e, nom::Err::Incomplete(i) => Self::Der(BerError::Incomplete(i)), } } } impl From for nom::Err { fn from(e: X509Error) -> nom::Err { nom::Err::Error(e) } } impl From for X509Error { fn from(e: ErrorKind) -> X509Error { X509Error::NomError(e) } } impl ParseError for X509Error { fn from_error_kind(_input: I, kind: ErrorKind) -> Self { X509Error::NomError(kind) } fn append(_input: I, kind: ErrorKind, _other: Self) -> Self { X509Error::NomError(kind) } } /// An error that can occur while parsing or validating a certificate. #[derive(Debug, thiserror::Error)] pub enum PEMError { #[error("base64 decode error")] Base64DecodeError, #[error("incomplete PEM")] IncompletePEM, #[error("invalid header")] InvalidHeader, #[error("missing header")] MissingHeader, #[error("IO error: {0}")] IOError(#[from] std::io::Error), } rusticata-x509-parser-a41eb37/src/extensions/000077500000000000000000000000001474637314500211225ustar00rootroot00000000000000rusticata-x509-parser-a41eb37/src/extensions/generalname.rs000066400000000000000000000103161474637314500237470ustar00rootroot00000000000000use crate::error::{X509Error, X509Result}; use crate::prelude::format_serial; use crate::x509::X509Name; use asn1_rs::{Any, CheckDerConstraints, Class, Error, FromDer, Oid, Sequence}; use core::convert::TryFrom; use nom::combinator::all_consuming; use nom::{Err, IResult}; use std::fmt; #[derive(Clone, Debug, PartialEq)] /// Represents a GeneralName as defined in RFC5280. There /// is no support X.400 addresses and EDIPartyName. /// /// String formats are not validated. pub enum GeneralName<'a> { OtherName(Oid<'a>, &'a [u8]), /// More or less an e-mail, the format is not checked. RFC822Name(&'a str), /// A hostname, format is not checked. DNSName(&'a str), /// X400Address, X400Address(Any<'a>), /// RFC5280 defines several string types, we always try to parse as utf-8 /// which is more or less a superset of the string types. DirectoryName(X509Name<'a>), /// EDIPartyName EDIPartyName(Any<'a>), /// An uniform resource identifier. The format is not checked. URI(&'a str), /// An ip address, provided as encoded. IPAddress(&'a [u8]), RegisteredID(Oid<'a>), } impl<'a> TryFrom> for GeneralName<'a> { type Error = Error; fn try_from(any: Any<'a>) -> Result { any.class().assert_eq(Class::ContextSpecific)?; fn ia5str(any: Any) -> Result<&str, Err> { // Relax constraints from RFC here: we are expecting an IA5String, but many certificates // are using unicode characters std::str::from_utf8(any.data).map_err(|_| Err::Failure(Error::BerValueError)) } let name = match any.tag().0 { 0 => { // otherName SEQUENCE { OID, [0] explicit any defined by oid } let (rest, oid) = Oid::from_der(any.data)?; GeneralName::OtherName(oid, rest) } 1 => GeneralName::RFC822Name(ia5str(any)?), 2 => GeneralName::DNSName(ia5str(any)?), 3 => { // XXX Not yet implemented GeneralName::X400Address(any) } 4 => { // directoryName, name let (_, name) = all_consuming(X509Name::from_der)(any.data) .or(Err(Error::Unsupported)) // XXX remove me ?; GeneralName::DirectoryName(name) } 5 => { // XXX Not yet implemented GeneralName::EDIPartyName(any) } 6 => GeneralName::URI(ia5str(any)?), 7 => { // IPAddress, OctetString GeneralName::IPAddress(any.data) } 8 => { let oid = Oid::new(any.data.into()); GeneralName::RegisteredID(oid) } _ => return Err(Error::unexpected_tag(None, any.tag())), }; Ok(name) } } impl CheckDerConstraints for GeneralName<'_> { fn check_constraints(any: &Any) -> asn1_rs::Result<()> { Sequence::check_constraints(any) } } impl<'a> FromDer<'a, X509Error> for GeneralName<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parse_generalname(i).map_err(Err::convert) } } impl fmt::Display for GeneralName<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { GeneralName::OtherName(oid, _) => write!(f, "OtherName({}, [...])", oid), GeneralName::RFC822Name(s) => write!(f, "RFC822Name({})", s), GeneralName::DNSName(s) => write!(f, "DNSName({})", s), GeneralName::X400Address(_) => write!(f, "X400Address()"), GeneralName::DirectoryName(dn) => write!(f, "DirectoryName({})", dn), GeneralName::EDIPartyName(_) => write!(f, "EDIPartyName()"), GeneralName::URI(s) => write!(f, "URI({})", s), GeneralName::IPAddress(b) => write!(f, "IPAddress({})", format_serial(b)), GeneralName::RegisteredID(oid) => write!(f, "RegisteredID({})", oid), } } } pub(crate) fn parse_generalname(i: &[u8]) -> IResult<&[u8], GeneralName, Error> { let (rest, any) = Any::from_der(i)?; let gn = GeneralName::try_from(any)?; Ok((rest, gn)) } rusticata-x509-parser-a41eb37/src/extensions/keyusage.rs000066400000000000000000000102031474637314500233010ustar00rootroot00000000000000use crate::error::{X509Error, X509Result}; use asn1_rs::FromDer; use der_parser::der::*; use der_parser::error::BerError; use der_parser::{oid, oid::Oid}; use nom::{Err, IResult}; use std::fmt; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct KeyUsage { pub flags: u16, } impl KeyUsage { pub fn digital_signature(&self) -> bool { self.flags & 1 == 1 } pub fn non_repudiation(&self) -> bool { (self.flags >> 1) & 1u16 == 1 } pub fn key_encipherment(&self) -> bool { (self.flags >> 2) & 1u16 == 1 } pub fn data_encipherment(&self) -> bool { (self.flags >> 3) & 1u16 == 1 } pub fn key_agreement(&self) -> bool { (self.flags >> 4) & 1u16 == 1 } pub fn key_cert_sign(&self) -> bool { (self.flags >> 5) & 1u16 == 1 } pub fn crl_sign(&self) -> bool { (self.flags >> 6) & 1u16 == 1 } pub fn encipher_only(&self) -> bool { (self.flags >> 7) & 1u16 == 1 } pub fn decipher_only(&self) -> bool { (self.flags >> 8) & 1u16 == 1 } } // This list must have the same order as KeyUsage flags declaration (4.2.1.3) const KEY_USAGE_FLAGS: &[&str] = &[ "Digital Signature", "Non Repudiation", "Key Encipherment", "Data Encipherment", "Key Agreement", "Key Cert Sign", "CRL Sign", "Encipher Only", "Decipher Only", ]; impl fmt::Display for KeyUsage { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut s = KEY_USAGE_FLAGS .iter() .enumerate() .fold(String::new(), |acc, (idx, s)| { if (self.flags >> idx) & 1 != 0 { acc + s + ", " } else { acc } }); s.pop(); s.pop(); f.write_str(&s) } } impl<'a> FromDer<'a, X509Error> for KeyUsage { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parse_keyusage(i).map_err(Err::convert) } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct ExtendedKeyUsage<'a> { pub any: bool, pub server_auth: bool, pub client_auth: bool, pub code_signing: bool, pub email_protection: bool, pub time_stamping: bool, pub ocsp_signing: bool, pub other: Vec>, } impl<'a> FromDer<'a, X509Error> for ExtendedKeyUsage<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parse_extendedkeyusage(i).map_err(Err::convert) } } pub(crate) fn parse_keyusage(i: &[u8]) -> IResult<&[u8], KeyUsage, BerError> { let (rest, obj) = parse_der_bitstring(i)?; let bitstring = obj .content .as_bitstring() .or(Err(Err::Error(BerError::BerTypeError)))?; let flags = bitstring .data .iter() .rev() .fold(0, |acc, x| (acc << 8) | (x.reverse_bits() as u16)); Ok((rest, KeyUsage { flags })) } pub(crate) fn parse_extendedkeyusage(i: &[u8]) -> IResult<&[u8], ExtendedKeyUsage, BerError> { let (ret, seq) = >::from_der(i)?; let mut seen = std::collections::HashSet::new(); let mut eku = ExtendedKeyUsage { any: false, server_auth: false, client_auth: false, code_signing: false, email_protection: false, time_stamping: false, ocsp_signing: false, other: Vec::new(), }; for oid in &seq { if !seen.insert(oid.clone()) { continue; } let asn1 = oid.as_bytes(); if asn1 == oid!(raw 2.5.29.37.0) { eku.any = true; } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.1) { eku.server_auth = true; } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.2) { eku.client_auth = true; } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.3) { eku.code_signing = true; } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.4) { eku.email_protection = true; } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.8) { eku.time_stamping = true; } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.9) { eku.ocsp_signing = true; } else { eku.other.push(oid.clone()); } } Ok((ret, eku)) } rusticata-x509-parser-a41eb37/src/extensions/mod.rs000066400000000000000000001534061474637314500222600ustar00rootroot00000000000000//! X.509 Extensions objects and types use crate::error::{X509Error, X509Result}; use crate::time::ASN1Time; use crate::utils::format_serial; use crate::x509::{ReasonCode, RelativeDistinguishedName}; use asn1_rs::FromDer; use der_parser::ber::parse_ber_bool; use der_parser::der::*; use der_parser::error::{BerError, BerResult}; use der_parser::num_bigint::BigUint; use nom::combinator::{all_consuming, complete, cut, map, map_res, opt}; use nom::multi::{many0, many1}; use nom::{Err, IResult, Parser}; use oid_registry::*; use std::collections::HashMap; use std::fmt::{self, LowerHex}; mod generalname; mod keyusage; mod nameconstraints; mod policymappings; mod sct; pub use generalname::*; pub use keyusage::*; pub use nameconstraints::*; pub use policymappings::*; pub use sct::*; /// X.509 version 3 extension /// /// X.509 extensions allow adding attributes to objects like certificates or revocation lists. /// /// Each extension in a certificate is designated as either critical or non-critical. A /// certificate using system MUST reject the certificate if it encounters a critical extension it /// does not recognize; however, a non-critical extension MAY be ignored if it is not recognized. /// /// Each extension includes an OID and an ASN.1 structure. When an extension appears in a /// certificate, the OID appears as the field extnID and the corresponding ASN.1 encoded structure /// is the value of the octet string extnValue. A certificate MUST NOT include more than one /// instance of a particular extension. /// /// When parsing an extension, the global extension structure (described above) is parsed, /// and the object is returned if it succeeds. /// During this step, it also attempts to parse the content of the extension, if known. /// The returned object has a /// [`X509Extension::parsed_extension()`] method. The returned /// enum is either a known extension, or the special value `ParsedExtension::UnsupportedExtension`. /// /// # Example /// /// ```rust /// use x509_parser::prelude::FromDer; /// use x509_parser::extensions::{X509Extension, ParsedExtension}; /// /// static DER: &[u8] = &[ /// 0x30, 0x1D, 0x06, 0x03, 0x55, 0x1D, 0x0E, 0x04, 0x16, 0x04, 0x14, 0xA3, 0x05, 0x2F, 0x18, /// 0x60, 0x50, 0xC2, 0x89, 0x0A, 0xDD, 0x2B, 0x21, 0x4F, 0xFF, 0x8E, 0x4E, 0xA8, 0x30, 0x31, /// 0x36 ]; /// /// # fn main() { /// let res = X509Extension::from_der(DER); /// match res { /// Ok((_rem, ext)) => { /// println!("Extension OID: {}", ext.oid); /// println!(" Critical: {}", ext.critical); /// let parsed_ext = ext.parsed_extension(); /// assert!(!parsed_ext.unsupported()); /// assert!(parsed_ext.error().is_none()); /// if let ParsedExtension::SubjectKeyIdentifier(key_id) = parsed_ext { /// assert!(key_id.0.len() > 0); /// } else { /// panic!("Extension has wrong type"); /// } /// }, /// _ => panic!("x509 extension parsing failed: {:?}", res), /// } /// # } /// ``` #[derive(Clone, Debug, PartialEq)] pub struct X509Extension<'a> { /// OID describing the extension content pub oid: Oid<'a>, /// Boolean value describing the 'critical' attribute of the extension /// /// An extension includes the boolean critical, with a default value of FALSE. pub critical: bool, /// Raw content of the extension pub value: &'a [u8], pub(crate) parsed_extension: ParsedExtension<'a>, } impl<'a> X509Extension<'a> { /// Creates a new extension with the provided values. #[inline] pub const fn new( oid: Oid<'a>, critical: bool, value: &'a [u8], parsed_extension: ParsedExtension<'a>, ) -> X509Extension<'a> { X509Extension { oid, critical, value, parsed_extension, } } /// Return the extension type or `UnsupportedExtension` if the extension is not implemented. #[inline] pub fn parsed_extension(&self) -> &ParsedExtension<'a> { &self.parsed_extension } } ///
/// Extension  ::=  SEQUENCE  {
///     extnID      OBJECT IDENTIFIER,
///     critical    BOOLEAN DEFAULT FALSE,
///     extnValue   OCTET STRING  }
/// 
impl<'a> FromDer<'a, X509Error> for X509Extension<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { X509ExtensionParser::new().parse(i) } } /// `X509Extension` parser builder #[derive(Clone, Copy, Debug)] pub struct X509ExtensionParser { deep_parse_extensions: bool, } impl X509ExtensionParser { #[inline] pub const fn new() -> Self { X509ExtensionParser { deep_parse_extensions: true, } } #[inline] pub const fn with_deep_parse_extensions(self, deep_parse_extensions: bool) -> Self { X509ExtensionParser { deep_parse_extensions, } } } impl Default for X509ExtensionParser { fn default() -> Self { X509ExtensionParser::new() } } impl<'a> Parser<&'a [u8], X509Extension<'a>, X509Error> for X509ExtensionParser { fn parse(&mut self, input: &'a [u8]) -> IResult<&'a [u8], X509Extension<'a>, X509Error> { parse_der_sequence_defined_g(|i, _| { let (i, oid) = Oid::from_der(i)?; let (i, critical) = der_read_critical(i)?; let (i, value) = <&[u8]>::from_der(i)?; let (i, parsed_extension) = if self.deep_parse_extensions { parser::parse_extension(i, value, &oid)? } else { (&[] as &[_], ParsedExtension::Unparsed) }; let ext = X509Extension { oid, critical, value, parsed_extension, }; Ok((i, ext)) })(input) .map_err(|_| X509Error::InvalidExtensions.into()) } } #[derive(Clone, Debug, PartialEq)] pub enum ParsedExtension<'a> { /// Crate parser does not support this extension (yet) UnsupportedExtension { oid: Oid<'a>, }, ParseError { error: Err, }, /// Section 4.2.1.1 of rfc 5280 AuthorityKeyIdentifier(AuthorityKeyIdentifier<'a>), /// Section 4.2.1.2 of rfc 5280 SubjectKeyIdentifier(KeyIdentifier<'a>), /// Section 4.2.1.3 of rfc 5280 KeyUsage(KeyUsage), /// Section 4.2.1.4 of rfc 5280 CertificatePolicies(CertificatePolicies<'a>), /// Section 4.2.1.5 of rfc 5280 PolicyMappings(PolicyMappings<'a>), /// Section 4.2.1.6 of rfc 5280 SubjectAlternativeName(SubjectAlternativeName<'a>), /// Section 4.2.1.7 of rfc 5280 IssuerAlternativeName(IssuerAlternativeName<'a>), /// Section 4.2.1.9 of rfc 5280 BasicConstraints(BasicConstraints), /// Section 4.2.1.10 of rfc 5280 NameConstraints(NameConstraints<'a>), /// Section 4.2.1.11 of rfc 5280 PolicyConstraints(PolicyConstraints), /// Section 4.2.1.12 of rfc 5280 ExtendedKeyUsage(ExtendedKeyUsage<'a>), /// Section 4.2.1.13 of rfc 5280 CRLDistributionPoints(CRLDistributionPoints<'a>), /// Section 4.2.1.14 of rfc 5280 InhibitAnyPolicy(InhibitAnyPolicy), /// Section 4.2.2.1 of rfc 5280 AuthorityInfoAccess(AuthorityInfoAccess<'a>), /// Netscape certificate type (subject is SSL client, an SSL server, or a CA) NSCertType(NSCertType), /// Netscape certificate comment NsCertComment(&'a str), /// Section 5.2.5 of rfc 5280 IssuingDistributionPoint(IssuingDistributionPoint<'a>), /// Section 5.3.1 of rfc 5280 CRLNumber(BigUint), /// Section 5.3.1 of rfc 5280 ReasonCode(ReasonCode), /// Section 5.3.3 of rfc 5280 InvalidityDate(ASN1Time), /// rfc 6962 SCT(Vec>), /// Unparsed extension (was not requested in parsing options) Unparsed, } impl ParsedExtension<'_> { /// Return `true` if the extension is unsupported pub fn unsupported(&self) -> bool { matches!(self, &ParsedExtension::UnsupportedExtension { .. }) } /// Return a reference on the parsing error if the extension parsing failed pub fn error(&self) -> Option<&Err> { match self { ParsedExtension::ParseError { error } => Some(error), _ => None, } } } #[derive(Clone, Debug, PartialEq)] pub struct AuthorityKeyIdentifier<'a> { pub key_identifier: Option>, pub authority_cert_issuer: Option>>, pub authority_cert_serial: Option<&'a [u8]>, } impl<'a> FromDer<'a, X509Error> for AuthorityKeyIdentifier<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parser::parse_authoritykeyidentifier(i).map_err(Err::convert) } } pub type CertificatePolicies<'a> = Vec>; // impl<'a> FromDer<'a> for CertificatePolicies<'a> { // fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { // parser::parse_certificatepolicies(i).map_err(Err::convert) // } // } #[derive(Clone, Debug, PartialEq, Eq)] pub struct PolicyInformation<'a> { pub policy_id: Oid<'a>, pub policy_qualifiers: Option>>, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct PolicyQualifierInfo<'a> { pub policy_qualifier_id: Oid<'a>, pub qualifier: &'a [u8], } /// Identifies whether the subject of the certificate is a CA, and the max validation depth. #[derive(Clone, Debug, PartialEq, Eq)] pub struct BasicConstraints { pub ca: bool, pub path_len_constraint: Option, } impl<'a> FromDer<'a, X509Error> for BasicConstraints { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parser::parse_basicconstraints(i).map_err(Err::convert) } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct KeyIdentifier<'a>(pub &'a [u8]); impl<'a> FromDer<'a, X509Error> for KeyIdentifier<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parser::parse_keyidentifier(i).map_err(Err::convert) } } impl LowerHex for KeyIdentifier<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let s = format_serial(self.0); f.write_str(&s) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct NSCertType(u8); // The value is a bit-string, where the individual bit positions are defined as: // // bit-0 SSL client - this cert is certified for SSL client authentication use // bit-1 SSL server - this cert is certified for SSL server authentication use // bit-2 S/MIME - this cert is certified for use by clients (New in PR3) // bit-3 Object Signing - this cert is certified for signing objects such as Java applets and plugins(New in PR3) // bit-4 Reserved - this bit is reserved for future use // bit-5 SSL CA - this cert is certified for issuing certs for SSL use // bit-6 S/MIME CA - this cert is certified for issuing certs for S/MIME use (New in PR3) // bit-7 Object Signing CA - this cert is certified for issuing certs for Object Signing (New in PR3) impl NSCertType { pub fn ssl_client(&self) -> bool { self.0 & 0x1 == 1 } pub fn ssl_server(&self) -> bool { (self.0 >> 1) & 1 == 1 } pub fn smime(&self) -> bool { (self.0 >> 2) & 1 == 1 } pub fn object_signing(&self) -> bool { (self.0 >> 3) & 1 == 1 } pub fn ssl_ca(&self) -> bool { (self.0 >> 5) & 1 == 1 } pub fn smime_ca(&self) -> bool { (self.0 >> 6) & 1 == 1 } pub fn object_signing_ca(&self) -> bool { (self.0 >> 7) & 1 == 1 } } const NS_CERT_TYPE_FLAGS: &[&str] = &[ "SSL CLient", "SSL Server", "S/MIME", "Object Signing", "Reserved", "SSL CA", "S/MIME CA", "Object Signing CA", ]; impl fmt::Display for NSCertType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut s = String::new(); let mut acc = self.0; for flag_text in NS_CERT_TYPE_FLAGS { if acc & 1 != 0 { s = s + flag_text + ", "; } acc >>= 1; } s.pop(); s.pop(); f.write_str(&s) } } impl<'a> FromDer<'a, X509Error> for NSCertType { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parser::parse_nscerttype(i).map_err(Err::convert) } } #[derive(Clone, Debug, PartialEq)] pub struct AuthorityInfoAccess<'a> { pub accessdescs: Vec>, } impl<'a> AuthorityInfoAccess<'a> { /// Returns an iterator over the Access Descriptors pub fn iter(&self) -> impl Iterator> { self.accessdescs.iter() } /// Returns a `HashMap` mapping `Oid` to the list of references to `GeneralNames` /// /// If several names match the same `Oid`, they are merged in the same entry. pub fn as_hashmap(&self) -> HashMap, Vec<&GeneralName<'a>>> { // create the hashmap and merge entries with same OID let mut m: HashMap> = HashMap::new(); for desc in &self.accessdescs { let AccessDescription { access_method: oid, access_location: gn, } = desc; if let Some(general_names) = m.get_mut(oid) { general_names.push(gn); } else { m.insert(oid.clone(), vec![gn]); } } m } /// Returns a `HashMap` mapping `Oid` to the list of `GeneralNames` (consuming the input) /// /// If several names match the same `Oid`, they are merged in the same entry. pub fn into_hashmap(self) -> HashMap, Vec>> { let mut aia_list = self.accessdescs; // create the hashmap and merge entries with same OID let mut m: HashMap> = HashMap::new(); for desc in aia_list.drain(..) { let AccessDescription { access_method: oid, access_location: gn, } = desc; if let Some(general_names) = m.get_mut(&oid) { general_names.push(gn); } else { m.insert(oid, vec![gn]); } } m } } impl<'a> FromDer<'a, X509Error> for AuthorityInfoAccess<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parser::parse_authorityinfoaccess(i).map_err(Err::convert) } } #[derive(Clone, Debug, PartialEq)] pub struct AccessDescription<'a> { pub access_method: Oid<'a>, pub access_location: GeneralName<'a>, } impl<'a> AccessDescription<'a> { pub const fn new(access_method: Oid<'a>, access_location: GeneralName<'a>) -> Self { AccessDescription { access_method, access_location, } } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct InhibitAnyPolicy { pub skip_certs: u32, } impl<'a> FromDer<'a, X509Error> for InhibitAnyPolicy { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { map(parse_der_u32, |skip_certs| InhibitAnyPolicy { skip_certs })(i).map_err(Err::convert) } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct PolicyConstraints { pub require_explicit_policy: Option, pub inhibit_policy_mapping: Option, } impl<'a> FromDer<'a, X509Error> for PolicyConstraints { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parser::parse_policyconstraints(i).map_err(Err::convert) } } #[derive(Clone, Debug, PartialEq)] pub struct SubjectAlternativeName<'a> { pub general_names: Vec>, } impl<'a> FromDer<'a, X509Error> for SubjectAlternativeName<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parse_der_sequence_defined_g(|input, _| { let (i, general_names) = all_consuming(many0(complete(cut(GeneralName::from_der))))(input)?; Ok((i, SubjectAlternativeName { general_names })) })(i) } } #[derive(Clone, Debug, PartialEq)] pub struct IssuerAlternativeName<'a> { pub general_names: Vec>, } impl<'a> FromDer<'a, X509Error> for IssuerAlternativeName<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parse_der_sequence_defined_g(|input, _| { let (i, general_names) = all_consuming(many0(complete(cut(GeneralName::from_der))))(input)?; Ok((i, IssuerAlternativeName { general_names })) })(i) } } #[derive(Clone, Debug, PartialEq)] pub struct CRLDistributionPoints<'a> { pub points: Vec>, } impl<'a> std::ops::Deref for CRLDistributionPoints<'a> { type Target = Vec>; fn deref(&self) -> &Self::Target { &self.points } } impl<'a> FromDer<'a, X509Error> for CRLDistributionPoints<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parser::parse_crldistributionpoints(i).map_err(Err::convert) } } #[derive(Clone, Debug, PartialEq)] pub struct CRLDistributionPoint<'a> { pub distribution_point: Option>, pub reasons: Option, pub crl_issuer: Option>>, } #[derive(Clone, Debug, PartialEq)] pub enum DistributionPointName<'a> { FullName(Vec>), NameRelativeToCRLIssuer(RelativeDistinguishedName<'a>), } #[derive(Clone, Debug, PartialEq, Eq)] pub struct ReasonFlags { pub flags: u16, } impl ReasonFlags { pub fn key_compromise(&self) -> bool { (self.flags >> 1) & 1 == 1 } pub fn ca_compromise(&self) -> bool { (self.flags >> 2) & 1 == 1 } pub fn affilation_changed(&self) -> bool { (self.flags >> 3) & 1 == 1 } pub fn superseded(&self) -> bool { (self.flags >> 4) & 1 == 1 } pub fn cessation_of_operation(&self) -> bool { (self.flags >> 5) & 1 == 1 } pub fn certificate_hold(&self) -> bool { (self.flags >> 6) & 1 == 1 } pub fn privelege_withdrawn(&self) -> bool { (self.flags >> 7) & 1 == 1 } pub fn aa_compromise(&self) -> bool { (self.flags >> 8) & 1 == 1 } } const REASON_FLAGS: &[&str] = &[ "Unused", "Key Compromise", "CA Compromise", "Affiliation Changed", "Superseded", "Cessation Of Operation", "Certificate Hold", "Privilege Withdrawn", "AA Compromise", ]; impl fmt::Display for ReasonFlags { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut s = String::new(); let mut acc = self.flags; for flag_text in REASON_FLAGS { if acc & 1 != 0 { s = s + flag_text + ", "; } acc >>= 1; } s.pop(); s.pop(); f.write_str(&s) } } #[derive(Clone, Debug, PartialEq)] pub struct IssuingDistributionPoint<'a> { pub distribution_point: Option>, pub only_contains_user_certs: bool, pub only_contains_ca_certs: bool, pub only_some_reasons: Option, pub indirect_crl: bool, pub only_contains_attribute_certs: bool, } pub(crate) mod parser { use crate::extensions::*; use asn1_rs::{GeneralizedTime, ParseResult}; use der_parser::ber::BerObject; use lazy_static::lazy_static; type ExtParser = fn(&[u8]) -> IResult<&[u8], ParsedExtension, BerError>; lazy_static! { static ref EXTENSION_PARSERS: HashMap, ExtParser> = { macro_rules! add { ($m:ident, $oid:ident, $p:ident) => { $m.insert($oid, $p as ExtParser); }; } let mut m = HashMap::new(); add!( m, OID_X509_EXT_SUBJECT_KEY_IDENTIFIER, parse_keyidentifier_ext ); add!(m, OID_X509_EXT_KEY_USAGE, parse_keyusage_ext); add!( m, OID_X509_EXT_SUBJECT_ALT_NAME, parse_subjectalternativename_ext ); add!( m, OID_X509_EXT_ISSUER_ALT_NAME, parse_issueralternativename_ext ); add!( m, OID_X509_EXT_BASIC_CONSTRAINTS, parse_basicconstraints_ext ); add!(m, OID_X509_EXT_NAME_CONSTRAINTS, parse_nameconstraints_ext); add!( m, OID_X509_EXT_CERTIFICATE_POLICIES, parse_certificatepolicies_ext ); add!(m, OID_X509_EXT_POLICY_MAPPINGS, parse_policymappings_ext); add!( m, OID_X509_EXT_POLICY_CONSTRAINTS, parse_policyconstraints_ext ); add!( m, OID_X509_EXT_EXTENDED_KEY_USAGE, parse_extendedkeyusage_ext ); add!( m, OID_X509_EXT_CRL_DISTRIBUTION_POINTS, parse_crldistributionpoints_ext ); add!( m, OID_X509_EXT_INHIBITANT_ANY_POLICY, parse_inhibitanypolicy_ext ); add!( m, OID_PKIX_AUTHORITY_INFO_ACCESS, parse_authorityinfoaccess_ext ); add!( m, OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER, parse_authoritykeyidentifier_ext ); add!(m, OID_CT_LIST_SCT, parse_sct_ext); add!(m, OID_X509_EXT_CERT_TYPE, parse_nscerttype_ext); add!(m, OID_X509_EXT_CERT_COMMENT, parse_nscomment_ext); add!(m, OID_X509_EXT_CRL_NUMBER, parse_crl_number); add!(m, OID_X509_EXT_REASON_CODE, parse_reason_code); add!(m, OID_X509_EXT_INVALIDITY_DATE, parse_invalidity_date); add!( m, OID_X509_EXT_ISSUER_DISTRIBUTION_POINT, parse_issuingdistributionpoint_ext ); m }; } // look into the parser map if the extension is known, and parse it // otherwise, leave it as UnsupportedExtension fn parse_extension0<'a>( orig_i: &'a [u8], i: &'a [u8], oid: &Oid, ) -> IResult<&'a [u8], ParsedExtension<'a>, BerError> { if let Some(parser) = EXTENSION_PARSERS.get(oid) { match parser(i) { Ok((_, ext)) => Ok((orig_i, ext)), Err(error) => Ok((orig_i, ParsedExtension::ParseError { error })), } } else { Ok(( orig_i, ParsedExtension::UnsupportedExtension { oid: oid.to_owned(), }, )) } } pub(crate) fn parse_extension<'a>( orig_i: &'a [u8], i: &'a [u8], oid: &Oid, ) -> IResult<&'a [u8], ParsedExtension<'a>, BerError> { parse_extension0(orig_i, i, oid) } /// Parse a "Basic Constraints" extension /// ///
    ///   id-ce-basicConstraints OBJECT IDENTIFIER ::=  { id-ce 19 }
    ///   BasicConstraints ::= SEQUENCE {
    ///        cA                      BOOLEAN DEFAULT FALSE,
    ///        pathLenConstraint       INTEGER (0..MAX) OPTIONAL }
    /// 
/// /// Note the maximum length of the `pathLenConstraint` field is limited to the size of a 32-bits /// unsigned integer, and parsing will fail if value if larger. pub(super) fn parse_basicconstraints(i: &[u8]) -> IResult<&[u8], BasicConstraints, BerError> { let (rem, obj) = parse_der_sequence(i)?; if let Ok(seq) = obj.as_sequence() { let (ca, path_len_constraint) = match seq.len() { 0 => (false, None), 1 => { if let Ok(b) = seq[0].as_bool() { (b, None) } else if let Ok(u) = seq[0].as_u32() { (false, Some(u)) } else { return Err(Err::Error(BerError::InvalidTag)); } } 2 => { let ca = seq[0] .as_bool() .or(Err(Err::Error(BerError::InvalidLength)))?; let pl = seq[1] .as_u32() .or(Err(Err::Error(BerError::InvalidLength)))?; (ca, Some(pl)) } _ => return Err(Err::Error(BerError::InvalidLength)), }; Ok(( rem, BasicConstraints { ca, path_len_constraint, }, )) } else { Err(Err::Error(BerError::InvalidLength)) } } fn parse_basicconstraints_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map(parse_basicconstraints, ParsedExtension::BasicConstraints)(i) } fn parse_nameconstraints_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map(parse_nameconstraints, ParsedExtension::NameConstraints)(i) } pub(super) fn parse_subjectalternativename_ext( i: &[u8], ) -> IResult<&[u8], ParsedExtension, BerError> { parse_der_sequence_defined_g(|input, _| { let (i, general_names) = all_consuming(many0(complete(cut(parse_generalname))))(input)?; Ok(( i, ParsedExtension::SubjectAlternativeName(SubjectAlternativeName { general_names }), )) })(i) } pub(super) fn parse_issueralternativename_ext( i: &[u8], ) -> IResult<&[u8], ParsedExtension, BerError> { parse_der_sequence_defined_g(|input, _| { let (i, general_names) = all_consuming(many0(complete(cut(parse_generalname))))(input)?; Ok(( i, ParsedExtension::IssuerAlternativeName(IssuerAlternativeName { general_names }), )) })(i) } pub(super) fn parse_policyconstraints(i: &[u8]) -> IResult<&[u8], PolicyConstraints, BerError> { parse_der_sequence_defined_g(|input, _| { let (i, require_explicit_policy) = opt(complete(map_res( parse_der_tagged_implicit(0, parse_der_content(Tag::Integer)), |x| x.as_u32(), )))(input)?; let (i, inhibit_policy_mapping) = all_consuming(opt(complete(map_res( parse_der_tagged_implicit(1, parse_der_content(Tag::Integer)), |x| x.as_u32(), ))))(i)?; let policy_constraint = PolicyConstraints { require_explicit_policy, inhibit_policy_mapping, }; Ok((i, policy_constraint)) })(i) } fn parse_policyconstraints_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map(parse_policyconstraints, ParsedExtension::PolicyConstraints)(i) } fn parse_policymappings_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map(parse_policymappings, ParsedExtension::PolicyMappings)(i) } fn parse_inhibitanypolicy_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { let (ret, skip_certs) = parse_der_u32(i)?; Ok(( ret, ParsedExtension::InhibitAnyPolicy(InhibitAnyPolicy { skip_certs }), )) } fn parse_extendedkeyusage_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map(parse_extendedkeyusage, ParsedExtension::ExtendedKeyUsage)(i) } // DistributionPointName ::= CHOICE { // fullName [0] GeneralNames, // nameRelativeToCRLIssuer [1] RelativeDistinguishedName } fn parse_distributionpointname(i: &[u8]) -> IResult<&[u8], DistributionPointName, BerError> { let (rem, header) = der_read_element_header(i)?; match header.tag().0 { 0 => { let (rem, names) = many1(complete(parse_generalname))(rem)?; Ok((rem, DistributionPointName::FullName(names))) } 1 => { let (rem, rdn) = RelativeDistinguishedName::from_der(rem) .map_err(|_| BerError::BerValueError)?; Ok((rem, DistributionPointName::NameRelativeToCRLIssuer(rdn))) } _ => Err(Err::Error(BerError::InvalidTag)), } } fn parse_implicit_tagged_reasons(tag: u32) -> impl Fn(&[u8]) -> BerResult { move |i: &[u8]| { let (rem, obj) = parse_der_tagged_implicit(tag, parse_der_content(Tag::BitString))(i)?; parse_reasons(rem, obj) } } // ReasonFlags ::= BIT STRING { // unused (0), // keyCompromise (1), // cACompromise (2), // affiliationChanged (3), // superseded (4), // cessationOfOperation (5), // certificateHold (6), // privilegeWithdrawn (7), // aACompromise (8) } fn parse_reasons<'a>(rem: &'a [u8], obj: BerObject<'a>) -> BerResult<'a, ReasonFlags> { if let DerObjectContent::BitString(_, b) = obj.content { let flags = b .data .iter() .rev() .fold(0, |acc, x| (acc << 8) | (x.reverse_bits() as u16)); Ok((rem, ReasonFlags { flags })) } else { Err(Err::Failure(BerError::InvalidTag)) } } fn parse_crlissuer_content(i: &[u8]) -> BerResult> { many1(complete(parse_generalname))(i) } // DistributionPoint ::= SEQUENCE { // distributionPoint [0] DistributionPointName OPTIONAL, // reasons [1] ReasonFlags OPTIONAL, // cRLIssuer [2] GeneralNames OPTIONAL } pub(super) fn parse_crldistributionpoint( i: &[u8], ) -> IResult<&[u8], CRLDistributionPoint, BerError> { parse_der_sequence_defined_g(|content, _| { let (rem, distribution_point) = opt(complete(parse_der_tagged_explicit_g(0, |b, _| { parse_distributionpointname(b) })))(content)?; let (rem, reasons) = opt(complete(parse_implicit_tagged_reasons(1)))(rem)?; let (rem, crl_issuer) = opt(complete(parse_der_tagged_implicit_g(2, |i, _, _| { parse_crlissuer_content(i) })))(rem)?; let crl_dp = CRLDistributionPoint { distribution_point, reasons, crl_issuer, }; Ok((rem, crl_dp)) })(i) } pub(super) fn parse_crldistributionpoints( i: &[u8], ) -> IResult<&[u8], CRLDistributionPoints, BerError> { let (ret, crldps) = parse_der_sequence_of_v(parse_crldistributionpoint)(i)?; Ok((ret, CRLDistributionPoints { points: crldps })) } fn parse_crldistributionpoints_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map( parse_crldistributionpoints, ParsedExtension::CRLDistributionPoints, )(i) } // IssuingDistributionPoint ::= SEQUENCE { // distributionPoint [0] DistributionPointName OPTIONAL, // onlyContainsUserCerts [1] BOOLEAN DEFAULT FALSE, // onlyContainsCACerts [2] BOOLEAN DEFAULT FALSE, // onlySomeReasons [3] ReasonFlags OPTIONAL, // indirectCRL [4] BOOLEAN DEFAULT FALSE, // onlyContainsAttributeCerts [5] BOOLEAN DEFAULT FALSE } pub(super) fn parse_issuingdistributionpoint( i: &[u8], ) -> IResult<&[u8], IssuingDistributionPoint, BerError> { parse_der_sequence_defined_g(|content, _| { let parse_tagged_bool = |tag: u32, rem| -> IResult<&[u8], bool, BerError> { let (rem, value) = opt(complete(|_| { parse_der_implicit(rem, tag, parse_der_content(Tag::Boolean)) .map(|(res, ob)| (res, ob.as_bool().unwrap_or(false))) }))(rem)?; Ok((rem, value.unwrap_or_default())) }; let (rem, distribution_point) = opt(complete(parse_der_tagged_explicit_g(0, |b, _| { parse_distributionpointname(b) })))(content)?; let (rem, only_contains_user_certs) = parse_tagged_bool(1, rem)?; let (rem, only_contains_ca_certs) = parse_tagged_bool(2, rem)?; let (rem, only_some_reasons) = opt(complete(parse_implicit_tagged_reasons(3)))(rem)?; let (rem, indirect_crl) = parse_tagged_bool(4, rem)?; let (rem, only_contains_attribute_certs) = parse_tagged_bool(5, rem)?; let crl_idp = IssuingDistributionPoint { distribution_point, only_contains_user_certs, only_contains_ca_certs, only_some_reasons, indirect_crl, only_contains_attribute_certs, }; Ok((rem, crl_idp)) })(i) } fn parse_issuingdistributionpoint_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map( parse_issuingdistributionpoint, ParsedExtension::IssuingDistributionPoint, )(i) } // AuthorityInfoAccessSyntax ::= // SEQUENCE SIZE (1..MAX) OF AccessDescription // // AccessDescription ::= SEQUENCE { // accessMethod OBJECT IDENTIFIER, // accessLocation GeneralName } pub(super) fn parse_authorityinfoaccess( i: &[u8], ) -> IResult<&[u8], AuthorityInfoAccess, BerError> { fn parse_aia(i: &[u8]) -> IResult<&[u8], AccessDescription, BerError> { parse_der_sequence_defined_g(|content, _| { // Read first element, an oid. let (gn, oid) = Oid::from_der(content)?; // Parse second element let (rest, gn) = parse_generalname(gn)?; Ok((rest, AccessDescription::new(oid, gn))) })(i) } let (ret, accessdescs) = parse_der_sequence_of_v(parse_aia)(i)?; Ok((ret, AuthorityInfoAccess { accessdescs })) } fn parse_authorityinfoaccess_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map( parse_authorityinfoaccess, ParsedExtension::AuthorityInfoAccess, )(i) } fn parse_aki_content<'a>( i: &'a [u8], _hdr: Header<'_>, ) -> IResult<&'a [u8], AuthorityKeyIdentifier<'a>, BerError> { let (i, key_identifier) = opt(complete(parse_der_tagged_implicit_g(0, |d, _, _| { Ok((&[], KeyIdentifier(d))) })))(i)?; let (i, authority_cert_issuer) = opt(complete(parse_der_tagged_implicit_g(1, |d, _, _| { many0(complete(parse_generalname))(d) })))(i)?; let (i, authority_cert_serial) = opt(complete(parse_der_tagged_implicit( 2, parse_der_content(Tag::Integer), )))(i)?; let authority_cert_serial = authority_cert_serial.and_then(|o| o.as_slice().ok()); let aki = AuthorityKeyIdentifier { key_identifier, authority_cert_issuer, authority_cert_serial, }; Ok((i, aki)) } // RFC 5280 section 4.2.1.1: Authority Key Identifier pub(super) fn parse_authoritykeyidentifier( i: &[u8], ) -> IResult<&[u8], AuthorityKeyIdentifier, BerError> { let (rem, aki) = parse_der_sequence_defined_g(parse_aki_content)(i)?; Ok((rem, aki)) } fn parse_authoritykeyidentifier_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map( parse_authoritykeyidentifier, ParsedExtension::AuthorityKeyIdentifier, )(i) } pub(super) fn parse_keyidentifier(i: &[u8]) -> IResult<&[u8], KeyIdentifier, BerError> { let (rest, id) = <&[u8]>::from_der(i)?; let ki = KeyIdentifier(id); Ok((rest, ki)) } fn parse_keyidentifier_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map(parse_keyidentifier, ParsedExtension::SubjectKeyIdentifier)(i) } fn parse_keyusage_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map(parse_keyusage, ParsedExtension::KeyUsage)(i) } pub(super) fn parse_nscerttype(i: &[u8]) -> IResult<&[u8], NSCertType, BerError> { let (rest, obj) = parse_der_bitstring(i)?; let bitstring = obj .content .as_bitstring() .or(Err(Err::Error(BerError::BerTypeError)))?; // bitstring should be 1 byte long if bitstring.data.len() != 1 { return Err(Err::Error(BerError::BerValueError)); } let flags = bitstring.data[0].reverse_bits(); Ok((rest, NSCertType(flags))) } fn parse_nscerttype_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map(parse_nscerttype, ParsedExtension::NSCertType)(i) } fn parse_nscomment_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { match parse_der_ia5string(i) { Ok((i, obj)) => { let s = obj.as_str()?; Ok((i, ParsedExtension::NsCertComment(s))) } Err(e) => { // Some implementations encode the comment directly, without // wrapping it in an IA5String if let Ok(s) = std::str::from_utf8(i) { Ok((&[], ParsedExtension::NsCertComment(s))) } else { Err(e) } } } } // CertificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation // // PolicyInformation ::= SEQUENCE { // policyIdentifier CertPolicyId, // policyQualifiers SEQUENCE SIZE (1..MAX) OF // PolicyQualifierInfo OPTIONAL } // // CertPolicyId ::= OBJECT IDENTIFIER // // PolicyQualifierInfo ::= SEQUENCE { // policyQualifierId PolicyQualifierId, // qualifier ANY DEFINED BY policyQualifierId } // // -- Implementations that recognize additional policy qualifiers MUST // -- augment the following definition for PolicyQualifierId // // PolicyQualifierId ::= OBJECT IDENTIFIER ( id-qt-cps | id-qt-unotice ) pub(super) fn parse_certificatepolicies( i: &[u8], ) -> IResult<&[u8], Vec, BerError> { fn parse_policy_qualifier_info(i: &[u8]) -> IResult<&[u8], PolicyQualifierInfo, BerError> { parse_der_sequence_defined_g(|content, _| { let (rem, policy_qualifier_id) = Oid::from_der(content)?; let info = PolicyQualifierInfo { policy_qualifier_id, qualifier: rem, }; Ok((&[], info)) })(i) } fn parse_policy_information(i: &[u8]) -> IResult<&[u8], PolicyInformation, BerError> { parse_der_sequence_defined_g(|content, _| { let (rem, policy_id) = Oid::from_der(content)?; let (rem, policy_qualifiers) = opt(complete(parse_der_sequence_defined_g(|content, _| { many1(complete(parse_policy_qualifier_info))(content) })))(rem)?; let info = PolicyInformation { policy_id, policy_qualifiers, }; Ok((rem, info)) })(i) } parse_der_sequence_of_v(parse_policy_information)(i) } fn parse_certificatepolicies_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map( parse_certificatepolicies, ParsedExtension::CertificatePolicies, )(i) } // CRLReason ::= ENUMERATED { ... fn parse_reason_code(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { let (rest, obj) = parse_der_enum(i)?; let code = obj .content .as_u32() .or(Err(Err::Error(BerError::BerValueError)))?; if code > 10 { return Err(Err::Error(BerError::BerValueError)); } let ret = ParsedExtension::ReasonCode(ReasonCode(code as u8)); Ok((rest, ret)) } // invalidityDate ::= GeneralizedTime fn parse_invalidity_date(i: &[u8]) -> ParseResult { let (rest, t) = GeneralizedTime::from_der(i)?; let dt = t.utc_datetime()?; Ok((rest, ParsedExtension::InvalidityDate(ASN1Time::new(dt)))) } // CRLNumber ::= INTEGER (0..MAX) // Note from RFC 3280: "CRL verifiers MUST be able to handle CRLNumber values up to 20 octets." fn parse_crl_number(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { let (rest, num) = map_res(parse_der_integer, |obj| obj.as_biguint())(i)?; Ok((rest, ParsedExtension::CRLNumber(num))) } fn parse_sct_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map( parse_ct_signed_certificate_timestamp_list, ParsedExtension::SCT, )(i) } } /// Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension pub(crate) fn parse_extension_sequence(i: &[u8]) -> X509Result> { parse_der_sequence_defined_g(|a, _| all_consuming(many0(complete(X509Extension::from_der)))(a))( i, ) } pub(crate) fn parse_extensions(i: &[u8], explicit_tag: Tag) -> X509Result> { if i.is_empty() { return Ok((i, Vec::new())); } match der_read_element_header(i) { Ok((rem, hdr)) => { if hdr.tag() != explicit_tag { return Err(Err::Error(X509Error::InvalidExtensions)); } all_consuming(parse_extension_sequence)(rem) } Err(_) => Err(X509Error::InvalidExtensions.into()), } } /// Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension pub(crate) fn parse_extension_envelope_sequence(i: &[u8]) -> X509Result> { let parser = X509ExtensionParser::new().with_deep_parse_extensions(false); parse_der_sequence_defined_g(move |a, _| all_consuming(many0(complete(parser)))(a))(i) } pub(crate) fn parse_extensions_envelope( i: &[u8], explicit_tag: Tag, ) -> X509Result> { if i.is_empty() { return Ok((i, Vec::new())); } match der_read_element_header(i) { Ok((rem, hdr)) => { if hdr.tag() != explicit_tag { return Err(Err::Error(X509Error::InvalidExtensions)); } all_consuming(parse_extension_envelope_sequence)(rem) } Err(_) => Err(X509Error::InvalidExtensions.into()), } } fn der_read_critical(i: &[u8]) -> BerResult { // Some certificates do not respect the DER BOOLEAN constraint (true must be encoded as 0xff) // so we attempt to parse as BER let (rem, obj) = opt(parse_ber_bool)(i)?; let value = obj .map(|o| o.as_bool().unwrap_or_default()) // unwrap cannot fail, we just read a bool .unwrap_or(false) // default critical value ; Ok((rem, value)) } #[cfg(test)] mod tests { use super::*; #[test] fn test_keyusage_flags() { let ku = KeyUsage { flags: 98 }; assert!(!ku.digital_signature()); assert!(ku.non_repudiation()); assert!(!ku.key_encipherment()); assert!(!ku.data_encipherment()); assert!(!ku.key_agreement()); assert!(ku.key_cert_sign()); assert!(ku.crl_sign()); assert!(!ku.encipher_only()); assert!(!ku.decipher_only()); } #[test] fn test_extensions1() { use der_parser::oid; let crt = crate::parse_x509_certificate(include_bytes!("../../assets/extension1.der")) .unwrap() .1; let tbs = &crt.tbs_certificate; let bc = crt .basic_constraints() .expect("could not get basic constraints") .expect("no basic constraints found"); assert_eq!( bc.value, &BasicConstraints { ca: true, path_len_constraint: Some(1) } ); { let ku = tbs .key_usage() .expect("could not get key usage") .expect("no key usage found") .value; assert!(ku.digital_signature()); assert!(!ku.non_repudiation()); assert!(ku.key_encipherment()); assert!(ku.data_encipherment()); assert!(ku.key_agreement()); assert!(!ku.key_cert_sign()); assert!(!ku.crl_sign()); assert!(ku.encipher_only()); assert!(ku.decipher_only()); } { let eku = tbs .extended_key_usage() .expect("could not get extended key usage") .expect("no extended key usage found") .value; assert!(!eku.any); assert!(eku.server_auth); assert!(!eku.client_auth); assert!(eku.code_signing); assert!(!eku.email_protection); assert!(eku.time_stamping); assert!(!eku.ocsp_signing); assert_eq!(eku.other, vec![oid!(1.2.3 .4 .0 .42)]); } assert_eq!( tbs.policy_constraints() .expect("could not get policy constraints") .expect("no policy constraints found") .value, &PolicyConstraints { require_explicit_policy: None, inhibit_policy_mapping: Some(10) } ); let val = tbs .inhibit_anypolicy() .expect("could not get inhibit_anypolicy") .expect("no inhibit_anypolicy found") .value; assert_eq!(val, &InhibitAnyPolicy { skip_certs: 2 }); { let alt_names = &tbs .subject_alternative_name() .expect("could not get subject alt names") .expect("no subject alt names found") .value .general_names; assert_eq!(alt_names[0], GeneralName::RFC822Name("foo@example.com")); assert_eq!(alt_names[1], GeneralName::URI("http://my.url.here/")); assert_eq!( alt_names[2], GeneralName::IPAddress([192, 168, 7, 1].as_ref()) ); assert_eq!( format!( "{}", match alt_names[3] { GeneralName::DirectoryName(ref dn) => dn, _ => unreachable!(), } ), "C=UK, O=My Organization, OU=My Unit, CN=My Name" ); assert_eq!(alt_names[4], GeneralName::DNSName("localhost")); assert_eq!(alt_names[5], GeneralName::RegisteredID(oid!(1.2.90 .0))); assert_eq!( alt_names[6], GeneralName::OtherName(oid!(1.2.3 .4), b"\xA0\x17\x0C\x15some other identifier") ); } { let name_constraints = &tbs .name_constraints() .expect("could not get name constraints") .expect("no name constraints found") .value; assert_eq!(name_constraints.permitted_subtrees, None); assert_eq!( name_constraints.excluded_subtrees, Some(vec![ GeneralSubtree { base: GeneralName::IPAddress([192, 168, 0, 0, 255, 255, 0, 0].as_ref()) }, GeneralSubtree { base: GeneralName::RFC822Name("foo.com") }, ]) ); } } #[test] fn test_extensions2() { use der_parser::oid; let crt = crate::parse_x509_certificate(include_bytes!("../../assets/extension2.der")) .unwrap() .1; let tbs = crt.tbs_certificate; assert_eq!( tbs.policy_constraints() .expect("could not get policy constraints") .expect("no policy constraints found") .value, &PolicyConstraints { require_explicit_policy: Some(5000), inhibit_policy_mapping: None } ); { let pm = tbs .policy_mappings() .expect("could not get policy_mappings") .expect("no policy_mappings found") .value .clone() .into_hashmap(); let mut pm_ref = HashMap::new(); pm_ref.insert(oid!(2.34.23), vec![oid!(2.2)]); pm_ref.insert(oid!(1.1), vec![oid!(0.0.4)]); pm_ref.insert(oid!(2.2), vec![oid!(2.2.1), oid!(2.2.3)]); assert_eq!(pm, pm_ref); } } #[test] fn test_extensions_crl_distribution_points() { // Extension not present { let crt = crate::parse_x509_certificate(include_bytes!( "../../assets/crl-ext/crl-no-crl.der" )) .unwrap() .1; assert!(!crt .tbs_certificate .extensions_map() .unwrap() .contains_key(&OID_X509_EXT_CRL_DISTRIBUTION_POINTS)); } // CRLDistributionPoints has 1 entry with 1 URI { let crt = crate::parse_x509_certificate(include_bytes!( "../../assets/crl-ext/crl-simple.der" )) .unwrap() .1; let crl = crt .tbs_certificate .extensions_map() .unwrap() .get(&OID_X509_EXT_CRL_DISTRIBUTION_POINTS) .unwrap() .parsed_extension(); assert!(matches!(crl, ParsedExtension::CRLDistributionPoints(_))); if let ParsedExtension::CRLDistributionPoints(crl) = crl { assert_eq!(crl.len(), 1); assert!(crl[0].reasons.is_none()); assert!(crl[0].crl_issuer.is_none()); let distribution_point = crl[0].distribution_point.as_ref().unwrap(); assert!(matches!( distribution_point, DistributionPointName::FullName(_) )); if let DistributionPointName::FullName(names) = distribution_point { assert_eq!(names.len(), 1); assert!(matches!(names[0], GeneralName::URI(_))); if let GeneralName::URI(uri) = names[0] { assert_eq!(uri, "http://example.com/myca.crl") } } } } // CRLDistributionPoints has 2 entries { let crt = crate::parse_x509_certificate(include_bytes!( "../../assets/crl-ext/crl-complex.der" )) .unwrap() .1; let crl = crt .tbs_certificate .extensions_map() .unwrap() .get(&OID_X509_EXT_CRL_DISTRIBUTION_POINTS) .unwrap() .parsed_extension(); assert!(matches!(crl, ParsedExtension::CRLDistributionPoints(_))); if let ParsedExtension::CRLDistributionPoints(crl) = crl { assert_eq!(crl.len(), 2); // First CRL Distribution point let reasons = crl[0].reasons.as_ref().unwrap(); assert!(reasons.key_compromise()); assert!(reasons.ca_compromise()); assert!(!reasons.affilation_changed()); assert!(!reasons.superseded()); assert!(!reasons.cessation_of_operation()); assert!(!reasons.certificate_hold()); assert!(!reasons.privelege_withdrawn()); assert!(reasons.aa_compromise()); assert_eq!( format!("{}", reasons), "Key Compromise, CA Compromise, AA Compromise" ); let issuers = crl[0].crl_issuer.as_ref().unwrap(); assert_eq!(issuers.len(), 1); assert!(matches!(issuers[0], GeneralName::DirectoryName(_))); if let GeneralName::DirectoryName(name) = &issuers[0] { assert_eq!(name.to_string(), "C=US, O=Organisation, CN=Some Name"); } let distribution_point = crl[0].distribution_point.as_ref().unwrap(); assert!(matches!( distribution_point, DistributionPointName::FullName(_) )); if let DistributionPointName::FullName(names) = distribution_point { assert_eq!(names.len(), 1); assert!(matches!(names[0], GeneralName::URI(_))); if let GeneralName::URI(uri) = names[0] { assert_eq!(uri, "http://example.com/myca.crl") } } // Second CRL Distribution point let reasons = crl[1].reasons.as_ref().unwrap(); assert!(reasons.key_compromise()); assert!(reasons.ca_compromise()); assert!(!reasons.affilation_changed()); assert!(!reasons.superseded()); assert!(!reasons.cessation_of_operation()); assert!(!reasons.certificate_hold()); assert!(!reasons.privelege_withdrawn()); assert!(!reasons.aa_compromise()); assert_eq!(format!("{}", reasons), "Key Compromise, CA Compromise"); assert!(crl[1].crl_issuer.is_none()); let distribution_point = crl[1].distribution_point.as_ref().unwrap(); assert!(matches!( distribution_point, DistributionPointName::FullName(_) )); if let DistributionPointName::FullName(names) = distribution_point { assert_eq!(names.len(), 1); assert!(matches!(names[0], GeneralName::URI(_))); if let GeneralName::URI(uri) = names[0] { assert_eq!(uri, "http://example.com/myca2.crl") } } } } } // Test cases for: // - parsing SubjectAlternativeName // - parsing NameConstraints } rusticata-x509-parser-a41eb37/src/extensions/nameconstraints.rs000066400000000000000000000040131474637314500246760ustar00rootroot00000000000000use super::GeneralName; use crate::error::{X509Error, X509Result}; use crate::extensions::parse_generalname; use asn1_rs::FromDer; use der_parser::der::*; use der_parser::error::BerError; use nom::combinator::{all_consuming, complete, map, opt}; use nom::multi::many1; use nom::{Err, IResult}; #[derive(Clone, Debug, PartialEq)] pub struct NameConstraints<'a> { pub permitted_subtrees: Option>>, pub excluded_subtrees: Option>>, } impl<'a> FromDer<'a, X509Error> for NameConstraints<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parse_nameconstraints(i).map_err(Err::convert) } } #[derive(Clone, Debug, PartialEq)] /// Represents the structure used in the name constraints extensions. /// The fields minimum and maximum are not supported (openssl also has no support). pub struct GeneralSubtree<'a> { pub base: GeneralName<'a>, // minimum: u32, // maximum: Option, } pub(crate) fn parse_nameconstraints(i: &[u8]) -> IResult<&[u8], NameConstraints, BerError> { fn parse_subtree(i: &[u8]) -> IResult<&[u8], GeneralSubtree, BerError> { parse_der_sequence_defined_g(|input, _| { map(parse_generalname, |base| GeneralSubtree { base })(input) })(i) } fn parse_subtrees(i: &[u8]) -> IResult<&[u8], Vec, BerError> { all_consuming(many1(complete(parse_subtree)))(i) } let (ret, named_constraints) = parse_der_sequence_defined_g(|input, _| { let (rem, permitted_subtrees) = opt(complete(parse_der_tagged_explicit_g(0, |input, _| { parse_subtrees(input) })))(input)?; let (rem, excluded_subtrees) = opt(complete(parse_der_tagged_explicit_g(1, |input, _| { parse_subtrees(input) })))(rem)?; let named_constraints = NameConstraints { permitted_subtrees, excluded_subtrees, }; Ok((rem, named_constraints)) })(i)?; Ok((ret, named_constraints)) } rusticata-x509-parser-a41eb37/src/extensions/policymappings.rs000066400000000000000000000062611474637314500245330ustar00rootroot00000000000000use crate::error::{X509Error, X509Result}; use asn1_rs::{DerSequence, Error, FromDer, Oid}; use nom::{Err, IResult}; use std::collections::HashMap; #[derive(Clone, Debug, PartialEq, Eq)] pub struct PolicyMappings<'a> { pub mappings: Vec>, } impl<'a> FromDer<'a, X509Error> for PolicyMappings<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parse_policymappings(i).map_err(Err::convert) } } impl<'a> PolicyMappings<'a> { /// Returns a `HashMap` mapping `Oid` to the list of references to `Oid` /// /// If several names match the same `Oid`, they are merged in the same entry. pub fn as_hashmap(&self) -> HashMap, Vec<&Oid<'a>>> { // create the hashmap and merge entries with same OID let mut m: HashMap> = HashMap::new(); for desc in &self.mappings { let PolicyMapping { issuer_domain_policy: left, subject_domain_policy: right, } = desc; if let Some(l) = m.get_mut(left) { l.push(right); } else { m.insert(left.clone(), vec![right]); } } m } /// Returns a `HashMap` mapping `Oid` to the list of `Oid` (consuming the input) /// /// If several names match the same `Oid`, they are merged in the same entry. pub fn into_hashmap(self) -> HashMap, Vec>> { let mut l = self.mappings; // create the hashmap and merge entries with same OID let mut m: HashMap> = HashMap::new(); for mapping in l.drain(..) { let PolicyMapping { issuer_domain_policy: left, subject_domain_policy: right, } = mapping; if let Some(general_names) = m.get_mut(&left) { general_names.push(right); } else { m.insert(left, vec![right]); } } m } } #[derive(Clone, Debug, PartialEq, Eq, DerSequence)] pub struct PolicyMapping<'a> { pub issuer_domain_policy: Oid<'a>, pub subject_domain_policy: Oid<'a>, } impl<'a> PolicyMapping<'a> { pub const fn new(issuer_domain_policy: Oid<'a>, subject_domain_policy: Oid<'a>) -> Self { PolicyMapping { issuer_domain_policy, subject_domain_policy, } } } // PolicyMappings ::= SEQUENCE SIZE (1..MAX) OF SEQUENCE { // issuerDomainPolicy CertPolicyId, // subjectDomainPolicy CertPolicyId } pub(crate) fn parse_policymappings(i: &[u8]) -> IResult<&[u8], PolicyMappings, Error> { let (ret, pairs) = >::from_der(i)?; // let mut mappings: HashMap> = HashMap::new(); let mappings = pairs; // let mut mappings = Vec::new(); // for pair in pairs.iter() { // // XXX this should go to Validate // // if left.bytes() == oid!(raw 2.5.29.32.0) || right.bytes() == oid!(raw 2.5.29.32.0) { // // // mapping to or from anyPolicy is not allowed // // return Err(Err::Failure(BerError::InvalidTag)); // // } // mappings.push(PolicyMapping::new(left, right)); // } Ok((ret, PolicyMappings { mappings })) } rusticata-x509-parser-a41eb37/src/extensions/sct.rs000066400000000000000000000072551474637314500222720ustar00rootroot00000000000000//! Certificate transparency [RFC6962](https://datatracker.ietf.org/doc/html/rfc6962) //! //! Code borrowed from tls-parser crate (file ) use std::convert::TryInto; use asn1_rs::FromDer; use der_parser::error::BerError; use nom::bytes::streaming::take; use nom::combinator::{complete, map_parser}; use nom::multi::{length_data, many1}; use nom::number::streaming::{be_u16, be_u64, be_u8}; use nom::IResult; #[derive(Clone, Debug, PartialEq, Eq)] pub struct SignedCertificateTimestamp<'a> { pub version: CtVersion, pub id: CtLogID<'a>, pub timestamp: u64, pub extensions: CtExtensions<'a>, pub signature: DigitallySigned<'a>, } /// Certificate Transparency Version as defined in /// [RFC6962 Section 3.2](https://datatracker.ietf.org/doc/html/rfc6962#section-3.2) #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct CtVersion(pub u8); impl CtVersion { pub const V1: CtVersion = CtVersion(0); } /// LogID as defined in /// [RFC6962 Section 3.2](https://datatracker.ietf.org/doc/html/rfc6962#section-3.2) #[derive(Clone, Debug, PartialEq, Eq)] pub struct CtLogID<'a> { pub key_id: &'a [u8; 32], } /// CtExtensions as defined in /// [RFC6962 Section 3.2](https://datatracker.ietf.org/doc/html/rfc6962#section-3.2) #[derive(Clone, Debug, PartialEq, Eq)] pub struct CtExtensions<'a>(pub &'a [u8]); #[derive(Clone, Debug, PartialEq, Eq)] pub struct DigitallySigned<'a> { pub hash_alg_id: u8, pub sign_alg_id: u8, pub data: &'a [u8], } /// Parses a list of Signed Certificate Timestamp entries pub fn parse_ct_signed_certificate_timestamp_list( i: &[u8], ) -> IResult<&[u8], Vec, BerError> { // use nom::HexDisplay; // eprintln!("{}", i.to_hex(16)); let (rem, b) = <&[u8]>::from_der(i)?; let (b, sct_len) = be_u16(b)?; let (_, sct_list) = map_parser( take(sct_len as usize), many1(complete(parse_ct_signed_certificate_timestamp)), )(b)?; Ok((rem, sct_list)) } /// Parses as single Signed Certificate Timestamp entry pub fn parse_ct_signed_certificate_timestamp( i: &[u8], ) -> IResult<&[u8], SignedCertificateTimestamp, BerError> { map_parser( length_data(be_u16), parse_ct_signed_certificate_timestamp_content, )(i) } pub(crate) fn parse_ct_signed_certificate_timestamp_content( i: &[u8], ) -> IResult<&[u8], SignedCertificateTimestamp, BerError> { let (i, version) = be_u8(i)?; let (i, id) = parse_log_id(i)?; let (i, timestamp) = be_u64(i)?; let (i, extensions) = parse_ct_extensions(i)?; let (i, signature) = parse_digitally_signed(i)?; let sct = SignedCertificateTimestamp { version: CtVersion(version), id, timestamp, extensions, signature, }; Ok((i, sct)) } // Safety: cannot fail, take() returns exactly 32 bytes fn parse_log_id(i: &[u8]) -> IResult<&[u8], CtLogID, BerError> { let (i, key_id) = take(32usize)(i)?; Ok(( i, CtLogID { key_id: key_id .try_into() .expect("take(32) is in sync with key_id size"), }, )) } fn parse_ct_extensions(i: &[u8]) -> IResult<&[u8], CtExtensions, BerError> { let (i, ext_len) = be_u16(i)?; let (i, ext_data) = take(ext_len as usize)(i)?; Ok((i, CtExtensions(ext_data))) } fn parse_digitally_signed(i: &[u8]) -> IResult<&[u8], DigitallySigned, BerError> { let (i, hash_alg_id) = be_u8(i)?; let (i, sign_alg_id) = be_u8(i)?; let (i, data) = length_data(be_u16)(i)?; let signed = DigitallySigned { hash_alg_id, sign_alg_id, data, }; Ok((i, signed)) } rusticata-x509-parser-a41eb37/src/lib.rs000066400000000000000000000172601474637314500200450ustar00rootroot00000000000000//! [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE-MIT) //! [![Apache License 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](./LICENSE-APACHE) //! [![docs.rs](https://docs.rs/x509-parser/badge.svg)](https://docs.rs/x509-parser) //! [![crates.io](https://img.shields.io/crates/v/x509-parser.svg)](https://crates.io/crates/x509-parser) //! [![Download numbers](https://img.shields.io/crates/d/x509-parser.svg)](https://crates.io/crates/x509-parser) //! [![Github CI](https://github.com/rusticata/x509-parser/workflows/Continuous%20integration/badge.svg)](https://github.com/rusticata/x509-parser/actions) //! [![Minimum rustc version](https://img.shields.io/badge/rustc-1.67.1+-lightgray.svg)](#rust-version-requirements) //! //! # X.509 Parser //! //! A X.509 v3 ([RFC5280]) parser, implemented with the [nom](https://github.com/Geal/nom) //! parser combinator framework. //! //! It is written in pure Rust, fast, and makes extensive use of zero-copy. A lot of care is taken //! to ensure security and safety of this crate, including design (recursion limit, defensive //! programming), tests, and fuzzing. It also aims to be panic-free. //! //! The code is available on [Github](https://github.com/rusticata/x509-parser) //! and is part of the [Rusticata](https://github.com/rusticata) project. //! //! Certificates are usually encoded in two main formats: PEM (usually the most common format) or //! DER. A PEM-encoded certificate is a container, storing a DER object. See the //! [`pem`](pem/index.html) module for more documentation. //! //! To decode a DER-encoded certificate, the main parsing method is //! `X509Certificate::from_der` ( //! part of the [`FromDer`](prelude/trait.FromDer.html) trait //! ), which builds a //! [`X509Certificate`](certificate/struct.X509Certificate.html) object. //! //! An alternative method is to use [`X509CertificateParser`](certificate/struct.X509CertificateParser.html), //! which allows specifying parsing options (for example, not automatically parsing option contents). //! //! The returned objects for parsers follow the definitions of the RFC. This means that accessing //! fields is done by accessing struct members recursively. Some helper functions are provided, for //! example [`X509Certificate::issuer()`](certificate/struct.X509Certificate.html#method.issuer) returns the //! same as accessing `.tbs_certificate.issuer`. //! //! For PEM-encoded certificates, use the [`pem`](pem/index.html) module. //! //! This crate also provides visitor traits: [`X509CertificateVisitor`](crate::visitor::X509CertificateVisitor). //! //! # Examples //! //! Parsing a certificate in DER format: //! //! ```rust //! use x509_parser::prelude::*; //! //! static IGCA_DER: &[u8] = include_bytes!("../assets/IGC_A.der"); //! //! # fn main() { //! let res = X509Certificate::from_der(IGCA_DER); //! match res { //! Ok((rem, cert)) => { //! assert!(rem.is_empty()); //! // //! assert_eq!(cert.version(), X509Version::V3); //! }, //! _ => panic!("x509 parsing failed: {:?}", res), //! } //! # } //! ``` //! //! To parse a CRL and print information about revoked certificates: //! //! ```rust //! # use x509_parser::prelude::*; //! # //! # static DER: &[u8] = include_bytes!("../assets/example.crl"); //! # //! # fn main() { //! let res = CertificateRevocationList::from_der(DER); //! match res { //! Ok((_rem, crl)) => { //! for revoked in crl.iter_revoked_certificates() { //! println!("Revoked certificate serial: {}", revoked.raw_serial_as_string()); //! println!(" Reason: {}", revoked.reason_code().unwrap_or_default().1); //! } //! }, //! _ => panic!("CRL parsing failed: {:?}", res), //! } //! # } //! ``` //! //! See also `examples/print-cert.rs`. //! //! # Features //! //! - The `verify` feature adds support for (cryptographic) signature verification, based on `ring`. //! It adds the //! [`X509Certificate::verify_signature()`](certificate/struct.X509Certificate.html#method.verify_signature) //! to `X509Certificate`. //! //! ```rust //! # #[cfg(feature = "verify")] //! # use x509_parser::certificate::X509Certificate; //! /// Cryptographic signature verification: returns true if certificate was signed by issuer //! #[cfg(feature = "verify")] //! pub fn check_signature(cert: &X509Certificate<'_>, issuer: &X509Certificate<'_>) -> bool { //! let issuer_public_key = issuer.public_key(); //! cert //! .verify_signature(Some(issuer_public_key)) //! .is_ok() //! } //! ``` //! //! - The `validate` features add methods to run more validation functions on the certificate structure //! and values using the [`Validate`](validate/trait.Validate.html) trait. //! It does not validate any cryptographic parameter (see `verify` above). //! //! ## Rust version requirements //! //! `x509-parser` requires **Rustc version 1.67.1 or greater**, based on der-parser //! dependencies and for proc-macro attributes support. //! //! [RFC5280]: https://tools.ietf.org/html/rfc5280 #![deny(/*missing_docs,*/ unstable_features, unused_import_braces, unused_qualifications)] #![warn( missing_debug_implementations, /* missing_docs, rust_2018_idioms,*/ unreachable_pub )] #![forbid(unsafe_code)] #![deny(rustdoc::broken_intra_doc_links)] #![doc(test( no_crate_inject, attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) ))] #![cfg_attr(docsrs, feature(doc_cfg))] pub mod certificate; pub mod certification_request; pub mod cri_attributes; pub mod error; pub mod extensions; pub mod objects; pub mod pem; pub mod prelude; pub mod public_key; pub mod revocation_list; pub mod signature_algorithm; pub mod signature_value; pub mod time; pub mod utils; #[cfg(feature = "validate")] #[cfg_attr(docsrs, doc(cfg(feature = "validate")))] pub mod validate; #[cfg(feature = "verify")] #[cfg_attr(docsrs, doc(cfg(feature = "verify")))] pub mod verify; pub mod visitor; pub mod x509; // reexports pub use asn1_rs; pub use der_parser; pub use der_parser::num_bigint; pub use nom; pub use oid_registry; use asn1_rs::FromDer; use certificate::X509Certificate; use error::X509Result; use revocation_list::CertificateRevocationList; /// Parse a **DER-encoded** X.509 Certificate, and return the remaining of the input and the built /// object. /// /// /// This function is an alias to [X509Certificate::from_der](certificate::X509Certificate::from_der). See this function /// for more information. /// /// For PEM-encoded certificates, use the [`pem`](pem/index.html) module. #[inline] pub fn parse_x509_certificate(i: &[u8]) -> X509Result { X509Certificate::from_der(i) } /// Parse a DER-encoded X.509 v2 CRL, and return the remaining of the input and the built /// object. /// /// This function is an alias to [CertificateRevocationList::from_der](revocation_list::CertificateRevocationList::from_der). See this function /// for more information. #[inline] pub fn parse_x509_crl(i: &[u8]) -> X509Result { CertificateRevocationList::from_der(i) } /// Parse a DER-encoded X.509 Certificate, and return the remaining of the input and the built #[deprecated( since = "0.9.0", note = "please use `parse_x509_certificate` or `X509Certificate::from_der` instead" )] #[inline] pub fn parse_x509_der(i: &[u8]) -> X509Result { X509Certificate::from_der(i) } /// Parse a DER-encoded X.509 v2 CRL, and return the remaining of the input and the built /// object. #[deprecated( since = "0.9.0", note = "please use `parse_x509_crl` or `CertificateRevocationList::from_der` instead" )] #[inline] pub fn parse_crl_der(i: &[u8]) -> X509Result { CertificateRevocationList::from_der(i) } rusticata-x509-parser-a41eb37/src/objects.rs000066400000000000000000000060471474637314500207310ustar00rootroot00000000000000//! X.509 helper objects definitions and registry //! //! All OID objects and definitions are now stored in the [oid-registry](https://crates.io/crates/oid-registry) crate. //! //! This crate is re-exporting `oid-registry`, so to access the OID constants the //! `x509_parser::oid_oid_registry` namespace can be used (see example below). //! //! ## Example //! //! To get the short name for a given OID: //! //! ```rust //! use x509_parser::objects::*; //! use x509_parser::oid_registry::*; //! //! let oid = &OID_X509_COMMON_NAME; //! let sn = oid2sn(oid, oid_registry()); //! assert_eq!(sn, Ok("commonName")); //! ``` use crate::error::NidError; use asn1_rs::oid; use lazy_static::lazy_static; use oid_registry::*; use std::collections::HashMap; lazy_static! { static ref OID_REGISTRY: OidRegistry<'static> = { let mut reg = OidRegistry::default().with_all_crypto().with_x509(); // OIDs not in the default registry can be added here let entry = OidEntry::new("id-mgf1", "Mask Generator Function 1 (MGF1)"); reg.insert(oid! {1.2.840.113549.1.1.8}, entry); reg }; static ref ABBREV_MAP: HashMap, &'static str> = { let mut m = HashMap::new(); m.insert(OID_X509_COMMON_NAME, "CN"); m.insert(OID_X509_COUNTRY_NAME, "C"); m.insert(OID_X509_LOCALITY_NAME, "L"); m.insert(OID_X509_STATE_OR_PROVINCE_NAME, "ST"); m.insert(OID_X509_ORGANIZATION_NAME, "O"); m.insert(OID_X509_ORGANIZATIONAL_UNIT, "OU"); m.insert(OID_DOMAIN_COMPONENT, "DC"); m.insert(OID_PKCS9_EMAIL_ADDRESS, "Email"); m }; } /// Return the abbreviation (for ex. CN for Common Name), or if not found, the OID short name pub fn oid2abbrev<'a>(oid: &'a Oid, registry: &'a OidRegistry) -> Result<&'a str, NidError> { if let Some(abbrev) = ABBREV_MAP.get(oid) { return Ok(abbrev); } registry.get(oid).map(|entry| entry.sn()).ok_or(NidError) } /// Returns the short name corresponding to the OID pub fn oid2sn<'a>(oid: &'a Oid, registry: &'a OidRegistry) -> Result<&'a str, NidError> { registry.get(oid).map(|o| o.sn()).ok_or(NidError) } /// Returns the description corresponding to the OID pub fn oid2description<'a>(oid: &'a Oid, registry: &'a OidRegistry) -> Result<&'a str, NidError> { registry.get(oid).map(|o| o.description()).ok_or(NidError) } /// Return a reference to the default registry of known OIDs pub fn oid_registry() -> &'static OidRegistry<'static> { &OID_REGISTRY } #[cfg(test)] mod tests { use super::*; use der_parser::oid; // This test is meant to check syntax of pattern matching with OID objects #[test] fn test_oid_match() { let oid = oid!(1.2.840 .113549 .1 .1 .5); if oid == OID_PKCS1_SHA1WITHRSA { // ok } // matching is not possible with Cow constants in pattern, // see https://rust-lang.github.io/rfcs/1445-restrict-constants-in-patterns.html // // match oid { // OID_PKCS1_SHA1WITHRSA => (), // _ => (), // } } } rusticata-x509-parser-a41eb37/src/pem.rs000066400000000000000000000214141474637314500200540ustar00rootroot00000000000000//! Decoding functions for PEM-encoded data //! //! A PEM object is a container, which can store (amongst other formats) a public X.509 //! Certificate, or a CRL, etc. It contains only printable characters. //! PEM-encoded binary data is essentially a beginning and matching end tag that encloses //! base64-encoded binary data (see: //! ). //! //! # Examples //! //! To parse a certificate in PEM format, first create the `Pem` object, then decode //! contents: //! //! ```rust,no_run //! use x509_parser::pem::Pem; //! use x509_parser::x509::X509Version; //! //! static IGCA_PEM: &str = "../assets/IGC_A.pem"; //! //! # fn main() { //! let data = std::fs::read(IGCA_PEM).expect("Could not read file"); //! for pem in Pem::iter_from_buffer(&data) { //! let pem = pem.expect("Reading next PEM block failed"); //! let x509 = pem.parse_x509().expect("X.509: decoding DER failed"); //! assert_eq!(x509.tbs_certificate.version, X509Version::V3); //! } //! # } //! ``` //! //! This is the most direct method to parse PEM data. //! //! Another method to parse the certificate is to use `parse_x509_pem`: //! //! ```rust,no_run //! use x509_parser::pem::parse_x509_pem; //! use x509_parser::parse_x509_certificate; //! //! static IGCA_PEM: &[u8] = include_bytes!("../assets/IGC_A.pem"); //! //! # fn main() { //! let res = parse_x509_pem(IGCA_PEM); //! match res { //! Ok((rem, pem)) => { //! assert!(rem.is_empty()); //! // //! assert_eq!(pem.label, String::from("CERTIFICATE")); //! // //! let res_x509 = parse_x509_certificate(&pem.contents); //! assert!(res_x509.is_ok()); //! }, //! _ => panic!("PEM parsing failed: {:?}", res), //! } //! # } //! ``` //! //! Note that all methods require to store the `Pem` object in a variable, mainly because decoding //! the PEM object requires allocation of buffers, and that the lifetime of X.509 certificates will //! be bound to these buffers. use crate::certificate::X509Certificate; use crate::error::{PEMError, X509Error}; use crate::parse_x509_certificate; use nom::{Err, IResult}; use std::io::{BufRead, Cursor, Seek}; /// Representation of PEM data #[derive(Clone, PartialEq, Eq, Debug)] pub struct Pem { /// The PEM label pub label: String, /// The PEM decoded data pub contents: Vec, } #[deprecated(since = "0.8.3", note = "please use `parse_x509_pem` instead")] pub fn pem_to_der(i: &[u8]) -> IResult<&[u8], Pem, PEMError> { parse_x509_pem(i) } /// Read a PEM-encoded structure, and decode the base64 data /// /// Return a structure describing the PEM object: the enclosing tag, and the data. /// Allocates a new buffer for the decoded data. /// /// Note that only the *first* PEM block is decoded. To iterate all blocks from PEM data, /// use [`Pem::iter_from_buffer`]. /// /// For X.509 (`CERTIFICATE` tag), the data is a certificate, encoded in DER. To parse the /// certificate content, use `Pem::parse_x509` or `parse_x509_certificate`. pub fn parse_x509_pem(i: &[u8]) -> IResult<&'_ [u8], Pem, PEMError> { let reader = Cursor::new(i); let res = Pem::read(reader); match res { Ok((pem, bytes_read)) => Ok((&i[bytes_read..], pem)), Err(e) => Err(Err::Error(e)), } } impl Pem { /// Read the next PEM-encoded structure, and decode the base64 data /// /// Returns the certificate (encoded in DER) and the number of bytes read. /// Allocates a new buffer for the decoded data. /// /// Note that a PEM file can contain multiple PEM blocks. This function returns the /// *first* decoded object, starting from the current reader position. /// To get all objects, call this function repeatedly until `PEMError::MissingHeader` /// is returned. /// /// # Examples /// ``` /// let file = std::fs::File::open("assets/certificate.pem").unwrap(); /// let subject = x509_parser::pem::Pem::read(std::io::BufReader::new(file)) /// .unwrap().0 /// .parse_x509().unwrap() /// .tbs_certificate.subject.to_string(); /// assert_eq!(subject, "CN=lists.for-our.info"); /// ``` pub fn read(mut r: impl BufRead + Seek) -> Result<(Pem, usize), PEMError> { let mut line = String::new(); let label = loop { let num_bytes = r.read_line(&mut line)?; if num_bytes == 0 { // EOF return Err(PEMError::MissingHeader); } if !line.starts_with("-----BEGIN ") { line.clear(); continue; } let v: Vec<&str> = line.split("-----").collect(); if v.len() < 3 || !v[0].is_empty() { return Err(PEMError::InvalidHeader); } let label = v[1].strip_prefix("BEGIN ").ok_or(PEMError::InvalidHeader)?; break label; }; let label = label.split('-').next().ok_or(PEMError::InvalidHeader)?; let mut s = String::new(); loop { let mut l = String::new(); let num_bytes = r.read_line(&mut l)?; if num_bytes == 0 { return Err(PEMError::IncompletePEM); } if l.starts_with("-----END ") { // finished reading break; } s.push_str(l.trim_end()); } let contents = data_encoding::BASE64 .decode(s.as_bytes()) .or(Err(PEMError::Base64DecodeError))?; let pem = Pem { label: label.to_string(), contents, }; Ok((pem, r.stream_position()? as usize)) } /// Decode the PEM contents into a X.509 object pub fn parse_x509(&self) -> Result> { parse_x509_certificate(&self.contents).map(|(_, x509)| x509) } /// Returns an iterator over the PEM-encapsulated parts of a buffer /// /// Only the sections enclosed in blocks starting with `-----BEGIN xxx-----` /// and ending with `-----END xxx-----` will be considered. /// Lines before, between or after such blocks will be ignored. /// /// The iterator is fallible: `next()` returns a `Result` object. /// An error indicates a block is present but invalid. /// /// If the buffer does not contain any block, iterator will be empty. pub fn iter_from_buffer(i: &[u8]) -> PemIterator> { let reader = Cursor::new(i); PemIterator { reader } } /// Returns an iterator over the PEM-encapsulated parts of a reader /// /// Only the sections enclosed in blocks starting with `-----BEGIN xxx-----` /// and ending with `-----END xxx-----` will be considered. /// Lines before, between or after such blocks will be ignored. /// /// The iterator is fallible: `next()` returns a `Result` object. /// An error indicates a block is present but invalid. /// /// If the reader does not contain any block, iterator will be empty. pub fn iter_from_reader(reader: R) -> PemIterator { PemIterator { reader } } } /// Iterator over PEM-encapsulated blocks /// /// Only the sections enclosed in blocks starting with `-----BEGIN xxx-----` /// and ending with `-----END xxx-----` will be considered. /// Lines before, between or after such blocks will be ignored. /// /// The iterator is fallible: `next()` returns a `Result` object. /// An error indicates a block is present but invalid. /// /// If the buffer does not contain any block, iterator will be empty. #[allow(missing_debug_implementations)] pub struct PemIterator { reader: Reader, } impl Iterator for PemIterator { type Item = Result; fn next(&mut self) -> Option { if let Ok(&[]) = self.reader.fill_buf() { return None; } let reader = self.reader.by_ref(); let r = Pem::read(reader).map(|(pem, _)| pem); if let Err(PEMError::MissingHeader) = r { None } else { Some(r) } } } #[cfg(test)] mod tests { use super::*; #[test] fn read_pem_from_file() { let file = std::io::BufReader::new(std::fs::File::open("assets/certificate.pem").unwrap()); let subject = Pem::read(file) .unwrap() .0 .parse_x509() .unwrap() .tbs_certificate .subject .to_string(); assert_eq!(subject, "CN=lists.for-our.info"); } #[test] fn pem_multi_word_label() { const PEM_BYTES: &[u8] = b"-----BEGIN MULTI WORD LABEL-----\n-----END MULTI WORD LABEL-----"; let (_, pem) = parse_x509_pem(PEM_BYTES).expect("should parse pem"); assert_eq!(pem.label, "MULTI WORD LABEL"); } } rusticata-x509-parser-a41eb37/src/prelude.rs000066400000000000000000000007321474637314500207330ustar00rootroot00000000000000//! A "prelude" for users of the x509-parser crate. pub use crate::certificate::*; pub use crate::certification_request::*; pub use crate::cri_attributes::*; pub use crate::error::*; pub use crate::extensions::*; pub use crate::objects::*; pub use crate::pem::*; pub use crate::revocation_list::*; pub use crate::time::*; pub use crate::utils::*; #[cfg(feature = "validate")] pub use crate::validate::*; pub use crate::x509::*; pub use crate::*; pub use asn1_rs::FromDer; rusticata-x509-parser-a41eb37/src/public_key.rs000066400000000000000000000074231474637314500214250ustar00rootroot00000000000000use crate::error::*; use asn1_rs::FromDer; use der_parser::{ der::{parse_der_integer, parse_der_sequence_defined_g}, error::BerResult, }; /// Public Key value #[derive(Debug, PartialEq, Eq)] pub enum PublicKey<'a> { RSA(RSAPublicKey<'a>), EC(ECPoint<'a>), /// DSAPublicKey ::= INTEGER -- public key, Y (RFC 3279) DSA(&'a [u8]), /// GostR3410-94-PublicKey ::= OCTET STRING -- public key, Y (RFC 4491) GostR3410(&'a [u8]), /// GostR3410-2012-256-PublicKey ::= OCTET STRING (64), /// GostR3410-2012-512-PublicKey ::= OCTET STRING (128). (RFC 4491-bis) GostR3410_2012(&'a [u8]), Unknown(&'a [u8]), } impl PublicKey<'_> { /// Return the key size (in bits) or 0 pub fn key_size(&self) -> usize { match self { Self::EC(ec) => ec.key_size(), Self::RSA(rsa) => rsa.key_size(), Self::DSA(y) | Self::GostR3410(y) => y.len() * 8, _ => 0, } } } /// RSA public Key, defined in rfc3279 #[derive(Debug, PartialEq, Eq)] pub struct RSAPublicKey<'a> { /// Raw bytes of the modulus /// /// This possibly includes a leading 0 if the MSB is 1 pub modulus: &'a [u8], /// Raw bytes of the exponent /// /// This possibly includes a leading 0 if the MSB is 1 pub exponent: &'a [u8], } impl RSAPublicKey<'_> { /// Attempt to convert exponent to u64 /// /// Returns an error if integer is too large, empty, or negative pub fn try_exponent(&self) -> Result { let mut buf = [0u8; 8]; if self.exponent.is_empty() || self.exponent[0] & 0x80 != 0 || self.exponent.len() > 8 { return Err(X509Error::InvalidNumber); } buf[8_usize.saturating_sub(self.exponent.len())..].copy_from_slice(self.exponent); let int = ::from_be_bytes(buf); Ok(int) } /// Return the key size (in bits) or 0 pub fn key_size(&self) -> usize { if !self.modulus.is_empty() && self.modulus[0] & 0x80 == 0 { // XXX len must substract leading zeroes let modulus = &self.modulus[1..]; 8 * modulus.len() } else { 0 } } } // helper function to parse with error type BerError fn parse_rsa_key(bytes: &[u8]) -> BerResult { parse_der_sequence_defined_g(move |i, _| { let (i, obj_modulus) = parse_der_integer(i)?; let (i, obj_exponent) = parse_der_integer(i)?; let modulus = obj_modulus.as_slice()?; let exponent = obj_exponent.as_slice()?; let key = RSAPublicKey { modulus, exponent }; Ok((i, key)) })(bytes) } impl<'a> FromDer<'a, X509Error> for RSAPublicKey<'a> { fn from_der(bytes: &'a [u8]) -> X509Result<'a, Self> { parse_rsa_key(bytes).map_err(|_| nom::Err::Error(X509Error::InvalidSPKI)) } } /// Elliptic Curve point, as defined in [RFC5480](https://datatracker.ietf.org/doc/html/rfc5480) #[derive(Debug, PartialEq, Eq)] pub struct ECPoint<'a> { data: &'a [u8], } impl<'a> ECPoint<'a> { /// EC Point content (See Standards for Efficient Cryptography Group (SECG), "SEC1: Elliptic Curve Cryptography") pub fn data(&'a self) -> &'a [u8] { self.data } /// Return the key size (in bits) or 0 pub fn key_size(&self) -> usize { match self.data { [] => { // empty 0 } [4, rem @ ..] => { // uncompressed rem.len() * 8 / 2 } [2..=3, rem @ ..] => { // compressed rem.len() * 8 } _ => { // invalid 0 } } } } impl<'a> From<&'a [u8]> for ECPoint<'a> { fn from(data: &'a [u8]) -> Self { ECPoint { data } } } rusticata-x509-parser-a41eb37/src/revocation_list.rs000066400000000000000000000311171474637314500225000ustar00rootroot00000000000000use crate::error::{X509Error, X509Result}; use crate::extensions::*; use crate::time::ASN1Time; use crate::utils::format_serial; use crate::x509::{ parse_serial, parse_signature_value, AlgorithmIdentifier, ReasonCode, X509Name, X509Version, }; #[cfg(feature = "verify")] use crate::verify::verify_signature; #[cfg(feature = "verify")] use crate::x509::SubjectPublicKeyInfo; use asn1_rs::{BitString, FromDer}; use der_parser::der::*; use der_parser::num_bigint::BigUint; use nom::combinator::{all_consuming, complete, map, opt}; use nom::multi::many0; use nom::Offset; use oid_registry::*; use std::collections::HashMap; /// An X.509 v2 Certificate Revocation List (CRL). /// /// X.509 v2 CRLs are defined in [RFC5280](https://tools.ietf.org/html/rfc5280). /// /// # Example /// /// To parse a CRL and print information about revoked certificates: /// /// ```rust /// use x509_parser::prelude::FromDer; /// use x509_parser::revocation_list::CertificateRevocationList; /// /// # static DER: &'static [u8] = include_bytes!("../assets/example.crl"); /// # /// # fn main() { /// let res = CertificateRevocationList::from_der(DER); /// match res { /// Ok((_rem, crl)) => { /// for revoked in crl.iter_revoked_certificates() { /// println!("Revoked certificate serial: {}", revoked.raw_serial_as_string()); /// println!(" Reason: {}", revoked.reason_code().unwrap_or_default().1); /// } /// }, /// _ => panic!("CRL parsing failed: {:?}", res), /// } /// # } /// ``` #[derive(Clone, Debug)] pub struct CertificateRevocationList<'a> { pub tbs_cert_list: TbsCertList<'a>, pub signature_algorithm: AlgorithmIdentifier<'a>, pub signature_value: BitString<'a>, } impl<'a> CertificateRevocationList<'a> { /// Get the version of the encoded certificate pub fn version(&self) -> Option { self.tbs_cert_list.version } /// Get the certificate issuer. #[inline] pub fn issuer(&self) -> &X509Name { &self.tbs_cert_list.issuer } /// Get the date and time of the last (this) update. #[inline] pub fn last_update(&self) -> ASN1Time { self.tbs_cert_list.this_update } /// Get the date and time of the next update, if present. #[inline] pub fn next_update(&self) -> Option { self.tbs_cert_list.next_update } /// Return an iterator over the `RevokedCertificate` objects pub fn iter_revoked_certificates(&self) -> impl Iterator> { self.tbs_cert_list.revoked_certificates.iter() } /// Get the CRL extensions. #[inline] pub fn extensions(&self) -> &[X509Extension] { &self.tbs_cert_list.extensions } /// Get the CRL number, if present /// /// Note that the returned value is a `BigUint`, because of the following RFC specification: ///
    /// Given the requirements above, CRL numbers can be expected to contain long integers.  CRL
    /// verifiers MUST be able to handle CRLNumber values up to 20 octets.  Conformant CRL issuers
    /// MUST NOT use CRLNumber values longer than 20 octets.
    /// 
pub fn crl_number(&self) -> Option<&BigUint> { self.extensions() .iter() .find(|&ext| ext.oid == OID_X509_EXT_CRL_NUMBER) .and_then(|ext| match ext.parsed_extension { ParsedExtension::CRLNumber(ref num) => Some(num), _ => None, }) } /// Verify the cryptographic signature of this certificate revocation list /// /// `public_key` is the public key of the **signer**. /// /// Not all algorithms are supported, this function is limited to what `ring` supports. #[cfg(feature = "verify")] #[cfg_attr(docsrs, doc(cfg(feature = "verify")))] pub fn verify_signature(&self, public_key: &SubjectPublicKeyInfo) -> Result<(), X509Error> { verify_signature( public_key, &self.signature_algorithm, &self.signature_value, self.tbs_cert_list.raw, ) } } ///
/// CertificateList  ::=  SEQUENCE  {
///      tbsCertList          TBSCertList,
///      signatureAlgorithm   AlgorithmIdentifier,
///      signatureValue       BIT STRING  }
/// 
impl<'a> FromDer<'a, X509Error> for CertificateRevocationList<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parse_der_sequence_defined_g(|i, _| { let (i, tbs_cert_list) = TbsCertList::from_der(i)?; let (i, signature_algorithm) = AlgorithmIdentifier::from_der(i)?; let (i, signature_value) = parse_signature_value(i)?; let crl = CertificateRevocationList { tbs_cert_list, signature_algorithm, signature_value, }; Ok((i, crl)) })(i) } } /// The sequence TBSCertList contains information about the certificates that have /// been revoked by the CA that issued the CRL. /// /// RFC5280 definition: /// ///
/// TBSCertList  ::=  SEQUENCE  {
///         version                 Version OPTIONAL,
///                                      -- if present, MUST be v2
///         signature               AlgorithmIdentifier,
///         issuer                  Name,
///         thisUpdate              Time,
///         nextUpdate              Time OPTIONAL,
///         revokedCertificates     SEQUENCE OF SEQUENCE  {
///             userCertificate         CertificateSerialNumber,
///             revocationDate          Time,
///             crlEntryExtensions      Extensions OPTIONAL
///                                      -- if present, version MUST be v2
///                                   } OPTIONAL,
///         crlExtensions           [0]  EXPLICIT Extensions OPTIONAL
///                                      -- if present, version MUST be v2
///                             }
/// 
#[derive(Clone, Debug, PartialEq)] pub struct TbsCertList<'a> { pub version: Option, pub signature: AlgorithmIdentifier<'a>, pub issuer: X509Name<'a>, pub this_update: ASN1Time, pub next_update: Option, pub revoked_certificates: Vec>, extensions: Vec>, pub(crate) raw: &'a [u8], } impl TbsCertList<'_> { /// Returns the certificate extensions #[inline] pub fn extensions(&self) -> &[X509Extension] { &self.extensions } /// Returns an iterator over the certificate extensions #[inline] pub fn iter_extensions(&self) -> impl Iterator { self.extensions.iter() } /// Searches for an extension with the given `Oid`. /// /// Note: if there are several extensions with the same `Oid`, the first one is returned. pub fn find_extension(&self, oid: &Oid) -> Option<&X509Extension> { self.extensions.iter().find(|&ext| ext.oid == *oid) } /// Builds and returns a map of extensions. /// /// If an extension is present twice, this will fail and return `DuplicateExtensions`. pub fn extensions_map(&self) -> Result, X509Error> { self.extensions .iter() .try_fold(HashMap::new(), |mut m, ext| { if m.contains_key(&ext.oid) { return Err(X509Error::DuplicateExtensions); } m.insert(ext.oid.clone(), ext); Ok(m) }) } } impl AsRef<[u8]> for TbsCertList<'_> { fn as_ref(&self) -> &[u8] { self.raw } } impl<'a> FromDer<'a, X509Error> for TbsCertList<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { let start_i = i; parse_der_sequence_defined_g(move |i, _| { let (i, version) = opt(map(parse_der_u32, X509Version))(i).or(Err(X509Error::InvalidVersion))?; let (i, signature) = AlgorithmIdentifier::from_der(i)?; let (i, issuer) = X509Name::from_der(i)?; let (i, this_update) = ASN1Time::from_der(i)?; let (i, next_update) = ASN1Time::from_der_opt(i)?; let (i, revoked_certificates) = opt(complete(parse_revoked_certificates))(i)?; let (i, extensions) = parse_extensions(i, Tag(0))?; let len = start_i.offset(i); let tbs = TbsCertList { version, signature, issuer, this_update, next_update, revoked_certificates: revoked_certificates.unwrap_or_default(), extensions, raw: &start_i[..len], }; Ok((i, tbs)) })(i) } } #[derive(Clone, Debug, PartialEq)] pub struct RevokedCertificate<'a> { /// The Serial number of the revoked certificate pub user_certificate: BigUint, /// The date on which the revocation occurred is specified. pub revocation_date: ASN1Time, /// Additional information about revocation extensions: Vec>, pub(crate) raw_serial: &'a [u8], } impl RevokedCertificate<'_> { /// Return the serial number of the revoked certificate pub fn serial(&self) -> &BigUint { &self.user_certificate } /// Get the CRL entry extensions. #[inline] pub fn extensions(&self) -> &[X509Extension] { &self.extensions } /// Returns an iterator over the CRL entry extensions #[inline] pub fn iter_extensions(&self) -> impl Iterator { self.extensions.iter() } /// Searches for a CRL entry extension with the given `Oid`. /// /// Note: if there are several extensions with the same `Oid`, the first one is returned. pub fn find_extension(&self, oid: &Oid) -> Option<&X509Extension> { self.extensions.iter().find(|&ext| ext.oid == *oid) } /// Builds and returns a map of CRL entry extensions. /// /// If an extension is present twice, this will fail and return `DuplicateExtensions`. pub fn extensions_map(&self) -> Result, X509Error> { self.extensions .iter() .try_fold(HashMap::new(), |mut m, ext| { if m.contains_key(&ext.oid) { return Err(X509Error::DuplicateExtensions); } m.insert(ext.oid.clone(), ext); Ok(m) }) } /// Get the raw bytes of the certificate serial number pub fn raw_serial(&self) -> &[u8] { self.raw_serial } /// Get a formatted string of the certificate serial number, separated by ':' pub fn raw_serial_as_string(&self) -> String { format_serial(self.raw_serial) } /// Get the code identifying the reason for the revocation, if present pub fn reason_code(&self) -> Option<(bool, ReasonCode)> { self.find_extension(&OID_X509_EXT_REASON_CODE) .and_then(|ext| match ext.parsed_extension { ParsedExtension::ReasonCode(code) => Some((ext.critical, code)), _ => None, }) } /// Get the invalidity date, if present /// /// The invalidity date is the date on which it is known or suspected that the private /// key was compromised or that the certificate otherwise became invalid. pub fn invalidity_date(&self) -> Option<(bool, ASN1Time)> { self.find_extension(&OID_X509_EXT_INVALIDITY_DATE) .and_then(|ext| match ext.parsed_extension { ParsedExtension::InvalidityDate(date) => Some((ext.critical, date)), _ => None, }) } } // revokedCertificates SEQUENCE OF SEQUENCE { // userCertificate CertificateSerialNumber, // revocationDate Time, // crlEntryExtensions Extensions OPTIONAL // -- if present, MUST be v2 // } OPTIONAL, impl<'a> FromDer<'a, X509Error> for RevokedCertificate<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parse_der_sequence_defined_g(|i, _| { let (i, (raw_serial, user_certificate)) = parse_serial(i)?; let (i, revocation_date) = ASN1Time::from_der(i)?; let (i, extensions) = opt(complete(parse_extension_sequence))(i)?; let revoked = RevokedCertificate { user_certificate, revocation_date, extensions: extensions.unwrap_or_default(), raw_serial, }; Ok((i, revoked)) })(i) } } fn parse_revoked_certificates(i: &[u8]) -> X509Result> { parse_der_sequence_defined_g(|a, _| { all_consuming(many0(complete(RevokedCertificate::from_der)))(a) })(i) } rusticata-x509-parser-a41eb37/src/signature_algorithm.rs000066400000000000000000000274371474637314500233550ustar00rootroot00000000000000use crate::error::X509Error; use crate::x509::AlgorithmIdentifier; use asn1_rs::{ oid, Any, CheckDerConstraints, Class, DerAutoDerive, Error, FromDer, OptTaggedExplicit, OptTaggedParser, Tag, }; use core::convert::TryFrom; use oid_registry::*; #[allow(non_camel_case_types)] #[derive(Debug, PartialEq)] pub enum SignatureAlgorithm<'a> { RSA, RSASSA_PSS(Box>), RSAAES_OAEP(Box>), DSA, ECDSA, ED25519, } impl<'a, 'b> TryFrom<&'b AlgorithmIdentifier<'a>> for SignatureAlgorithm<'a> { type Error = X509Error; fn try_from(value: &'b AlgorithmIdentifier<'a>) -> Result { if value.algorithm.starts_with(&oid! {1.2.840.113549.1.1}) { // children of PKCS1 are all RSA // test if RSASSA-PSS if value.algorithm == OID_PKCS1_RSASSAPSS { let params = match value.parameters.as_ref() { Some(any) => any, None => return Err(X509Error::InvalidSignatureValue), }; let params = RsaSsaPssParams::try_from(params) .map_err(|_| X509Error::InvalidSignatureValue)?; Ok(SignatureAlgorithm::RSASSA_PSS(Box::new(params))) } else { // rfc3279#section-2.2.1: the parameters component of that type SHALL be // the ASN.1 type NULL // We could enforce presence of NULL, but that would make a strict parser // so it would best go to a verifier. Ok(SignatureAlgorithm::RSA) } } else if test_ecdsa_oid(&value.algorithm) { // parameter should be NULL - see above Ok(SignatureAlgorithm::ECDSA) } else if value.algorithm.starts_with(&oid! {1.2.840.10040.4}) { // parameter should be NULL - see above Ok(SignatureAlgorithm::DSA) } else if value.algorithm == OID_SIG_ED25519 { Ok(SignatureAlgorithm::ED25519) } else if value.algorithm == oid! {1.2.840.113549.1.1.7} { let params = match value.parameters.as_ref() { Some(any) => any, None => return Err(X509Error::InvalidSignatureValue), }; let params = RsaAesOaepParams::try_from(params).map_err(|_| X509Error::InvalidSignatureValue)?; Ok(SignatureAlgorithm::RSAAES_OAEP(Box::new(params))) } else { if cfg!(debug_assertions) { // TODO: remove debug eprintln!("bad Signature AlgorithmIdentifier: {}", value.algorithm); } Err(X509Error::InvalidSignatureValue) } } } #[inline] fn test_ecdsa_oid(oid: &Oid) -> bool { // test if oid is a child from {ansi-x962 signatures} oid.starts_with(&oid! {1.2.840.10045.4}) } // RSASSA-PSS public keys [RFC4055](https://www.rfc-editor.org/rfc/rfc4055.html) // RSASSA-PSS-params ::= SEQUENCE { // hashAlgorithm [0] HashAlgorithm DEFAULT // sha1Identifier, // maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT // mgf1SHA1Identifier, // saltLength [2] INTEGER DEFAULT 20, // trailerField [3] INTEGER DEFAULT 1 } #[derive(Debug, PartialEq)] pub struct RsaSsaPssParams<'a> { hash_alg: Option>, mask_gen_algorithm: Option>, salt_length: Option, trailer_field: Option, } impl<'a> RsaSsaPssParams<'a> { /// Get a reference to the rsa ssa pss params's hash algorithm. pub fn hash_algorithm(&self) -> Option<&AlgorithmIdentifier> { self.hash_alg.as_ref() } /// Return the hash algorithm OID, or SHA1 if absent (RFC4055) pub fn hash_algorithm_oid(&self) -> &'a Oid { const SHA1: &Oid = &OID_HASH_SHA1; self.hash_alg .as_ref() .map(|alg| &alg.algorithm) .unwrap_or(SHA1) } /// Get a reference to the rsa ssa pss params's mask generation algorithm. pub fn mask_gen_algorithm_raw(&self) -> Option<&AlgorithmIdentifier> { self.mask_gen_algorithm.as_ref() } /// Get the rsa ssa pss params's mask generation algorithm. /// /// If the algorithm encoding is invalid, raise an error `InvalidAlgorithmIdentifier` pub fn mask_gen_algorithm(&self) -> Result { match self.mask_gen_algorithm.as_ref() { Some(alg) => { let (_, hash) = alg .parameters() .and_then(|any| Oid::from_der(any.data).ok()) .ok_or(X509Error::InvalidAlgorithmIdentifier)?; Ok(MaskGenAlgorithm::new(alg.algorithm.clone(), hash)) } _ => { Ok(MaskGenAlgorithm::new( oid! {1.2.840.113549.1.1.8}, // id-mgf1 OID_HASH_SHA1, )) } } } /// Return the salt length pub fn salt_length(&self) -> u32 { self.salt_length.unwrap_or(20) } /// Return the trailer field (value must be `1` according to RFC4055) pub fn trailer_field(&self) -> u32 { self.trailer_field.unwrap_or(1) } } impl<'a> TryFrom> for RsaSsaPssParams<'a> { type Error = X509Error; fn try_from(value: Any<'a>) -> Result { Self::try_from(&value) } } impl<'a, 'b> TryFrom<&'b Any<'a>> for RsaSsaPssParams<'a> { type Error = X509Error; fn try_from(value: &'b Any<'a>) -> Result { value.tag().assert_eq(Tag::Sequence)?; let i = &value.data; // let (i, hash_alg) = OptTaggedExplicit::<_, X509Error, 0>::from_der(i)?; let (i, hash_alg) = OptTaggedParser::new(Class::ContextSpecific, Tag(0)) .parse_der(i, |_, inner| AlgorithmIdentifier::from_der(inner))?; // let (i, mask_gen_algorithm) = OptTaggedExplicit::<_, Error, 1>::from_der(i)?; let (i, mask_gen_algorithm) = OptTaggedParser::new(Class::ContextSpecific, Tag(1)) .parse_der(i, |_, inner| AlgorithmIdentifier::from_der(inner))?; let (i, salt_length) = OptTaggedExplicit::<_, Error, 2>::from_der(i)?; let (_, trailer_field) = OptTaggedExplicit::<_, Error, 3>::from_der(i)?; let params = RsaSsaPssParams { hash_alg, mask_gen_algorithm, salt_length: salt_length.map(|t| t.into_inner()), trailer_field: trailer_field.map(|t| t.into_inner()), }; Ok(params) } } impl CheckDerConstraints for RsaSsaPssParams<'_> { fn check_constraints(any: &Any) -> asn1_rs::Result<()> { any.header.assert_constructed()?; Ok(()) } } impl DerAutoDerive for RsaSsaPssParams<'_> {} #[derive(Debug, PartialEq, Eq)] pub struct MaskGenAlgorithm<'a, 'b> { pub mgf: Oid<'a>, pub hash: Oid<'b>, } impl<'a, 'b> MaskGenAlgorithm<'a, 'b> { pub const fn new(mgf: Oid<'a>, hash: Oid<'b>) -> Self { Self { mgf, hash } } } // RSAAES-OAEP public keys [RFC8017](https://www.rfc-editor.org/rfc/rfc8017.html) // RSAES-OAEP-params ::= SEQUENCE { // hashFunc [0] AlgorithmIdentifier DEFAULT // sha1Identifier, // maskGenFunc [1] AlgorithmIdentifier DEFAULT // mgf1SHA1Identifier, // pSourceFunc [2] AlgorithmIdentifier DEFAULT // pSpecifiedEmptyIdentifier } // // pSpecifiedEmptyIdentifier AlgorithmIdentifier ::= // { id-pSpecified, nullOctetString } // // nullOctetString OCTET STRING (SIZE (0)) ::= { ''H } #[derive(Debug, PartialEq)] pub struct RsaAesOaepParams<'a> { hash_alg: Option>, mask_gen_alg: Option>, p_source_alg: Option>, } impl<'a> RsaAesOaepParams<'a> { pub const EMPTY: &'static AlgorithmIdentifier<'static> = &AlgorithmIdentifier::new( oid! {1.2.840.113549.1.1.9}, // id-pSpecified None, ); /// Get a reference to the rsa aes oaep params's hash algorithm. pub fn hash_algorithm(&self) -> Option<&AlgorithmIdentifier> { self.hash_alg.as_ref() } /// Return the hash algorithm OID, or SHA1 if absent (RFC4055) pub fn hash_algorithm_oid(&self) -> &'a Oid { const SHA1: &Oid = &OID_HASH_SHA1; self.hash_alg .as_ref() .map(|alg| &alg.algorithm) .unwrap_or(SHA1) } /// Get a reference to the rsa ssa pss params's mask generation algorithm. pub fn mask_gen_algorithm_raw(&self) -> Option<&AlgorithmIdentifier> { self.mask_gen_alg.as_ref() } /// Get the rsa ssa pss params's mask generation algorithm. /// /// If the algorithm encoding is invalid, raise an error `InvalidAlgorithmIdentifier` pub fn mask_gen_algorithm(&self) -> Result { match self.mask_gen_alg.as_ref() { Some(alg) => { let (_, hash) = alg .parameters() .and_then(|any| Oid::from_der(any.data).ok()) .ok_or(X509Error::InvalidAlgorithmIdentifier)?; Ok(MaskGenAlgorithm::new(alg.algorithm.clone(), hash)) } _ => { Ok(MaskGenAlgorithm::new( oid! {1.2.840.113549.1.1.8}, // id-mgf1 OID_HASH_SHA1, )) } } } /// Return the pSourceFunc algorithm pub fn p_source_alg(&'a self) -> &'a AlgorithmIdentifier<'a> { self.p_source_alg.as_ref().unwrap_or(Self::EMPTY) } } impl<'a> TryFrom> for RsaAesOaepParams<'a> { type Error = X509Error; fn try_from(value: Any<'a>) -> Result { Self::try_from(&value) } } // hashFunc [0] AlgorithmIdentifier DEFAULT // sha1Identifier, // maskGenFunc [1] AlgorithmIdentifier DEFAULT // mgf1SHA1Identifier, // pSourceFunc [2] AlgorithmIdentifier DEFAULT // pSpecifiedEmptyIdentifier } impl<'a, 'b> TryFrom<&'b Any<'a>> for RsaAesOaepParams<'a> { type Error = X509Error; fn try_from(value: &'b Any<'a>) -> Result { value.tag().assert_eq(Tag::Sequence)?; let i = &value.data; // let (i, hash_alg) = OptTaggedExplicit::<_, X509Error, 0>::from_der(i)?; let (i, hash_alg) = OptTaggedParser::new(Class::ContextSpecific, Tag(0)) .parse_der(i, |_, inner| AlgorithmIdentifier::from_der(inner))?; // let (i, mask_gen_algorithm) = OptTaggedExplicit::<_, Error, 1>::from_der(i)?; let (i, mask_gen_alg) = OptTaggedParser::new(Class::ContextSpecific, Tag(1)) .parse_der(i, |_, inner| AlgorithmIdentifier::from_der(inner))?; let (_, p_source_alg) = OptTaggedParser::new(Class::ContextSpecific, Tag(2)) .parse_der(i, |_, inner| AlgorithmIdentifier::from_der(inner))?; let params = RsaAesOaepParams { hash_alg, mask_gen_alg, p_source_alg, }; Ok(params) } } impl CheckDerConstraints for RsaAesOaepParams<'_> { fn check_constraints(any: &Any) -> asn1_rs::Result<()> { any.header.assert_constructed()?; Ok(()) } } impl DerAutoDerive for RsaAesOaepParams<'_> {} // ECC subject public key information [RFC5480](https://datatracker.ietf.org/doc/rfc5480/) // ECParameters ::= CHOICE { // namedCurve OBJECT IDENTIFIER // -- implicitCurve NULL // -- specifiedCurve SpecifiedECDomain // } // -- implicitCurve and specifiedCurve MUST NOT be used in PKIX. // -- Details for SpecifiedECDomain can be found in [X9.62]. // -- Any future additions to this CHOICE should be coordinated // -- with ANSI X9. rusticata-x509-parser-a41eb37/src/signature_value.rs000066400000000000000000000004331474637314500224660ustar00rootroot00000000000000use asn1_rs::{DerSequence, Integer}; /// ECDSA Signature Value (RFC3279) // Ecdsa-Sig-Value ::= SEQUENCE { // r INTEGER, // s INTEGER } #[derive(Debug, PartialEq, Eq, DerSequence)] pub struct EcdsaSigValue<'a> { pub r: Integer<'a>, pub s: Integer<'a>, } rusticata-x509-parser-a41eb37/src/time.rs000066400000000000000000000151431474637314500202330ustar00rootroot00000000000000use asn1_rs::nom::Err; use asn1_rs::{Error, FromDer, GeneralizedTime, Header, ParseResult, UtcTime}; use der_parser::ber::{Tag, MAX_OBJECT_SIZE}; use std::fmt; use std::ops::{Add, Sub}; use time::macros::format_description; use time::{Duration, OffsetDateTime}; use crate::error::{X509Error, X509Result}; /// An ASN.1 timestamp. #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] pub struct ASN1Time { time: OffsetDateTime, generalized: bool, } impl ASN1Time { pub(crate) fn from_der_opt(i: &[u8]) -> X509Result> { if i.is_empty() { return Ok((i, None)); } match parse_choice_of_time(i) { Ok((rem, time)) => Ok((rem, Some(time))), Err(Err::Error(Error::InvalidTag)) | Err(Err::Error(Error::UnexpectedTag { .. })) => { Ok((i, None)) } Err(_) => Err(Err::Error(X509Error::InvalidDate)), } } #[inline] pub const fn new(dt: OffsetDateTime) -> Self { let generalized = dt.year() > 2049; Self { time: dt, generalized, } } #[inline] pub const fn new_generalized(dt: OffsetDateTime) -> Self { Self { time: dt, generalized: true, } } #[inline] pub const fn new_utc(dt: OffsetDateTime) -> Self { Self { time: dt, generalized: false, } } #[inline] pub const fn to_datetime(&self) -> OffsetDateTime { self.time } /// Makes a new `ASN1Time` from the number of non-leap seconds since Epoch pub fn from_timestamp(secs: i64) -> Result { let dt = OffsetDateTime::from_unix_timestamp(secs).map_err(|_| X509Error::InvalidDate)?; Ok(ASN1Time::new(dt)) } /// Returns the number of non-leap seconds since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp"). #[inline] pub fn timestamp(&self) -> i64 { self.time.unix_timestamp() } /// Returns a `ASN1Time` which corresponds to the current date. #[inline] pub fn now() -> Self { ASN1Time::new(OffsetDateTime::now_utc()) } /// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`. /// /// Conversion to RFC2822 date can fail if date cannot be represented in this format, /// for example if year < 1900. /// /// For an infallible conversion to string, use `.to_string()`. #[inline] pub fn to_rfc2822(self) -> Result { self.time .format(&time::format_description::well_known::Rfc2822) .map_err(|e| e.to_string()) } /// Return `true` if date is encoded as UTCTime /// /// According to RFC 5280, dates though year 2049 should be encoded as UTCTime, and /// GeneralizedTime after 2029. #[inline] pub const fn is_utctime(&self) -> bool { !self.generalized } /// Return `true` if date is encoded as GeneralizedTime /// /// According to RFC 5280, dates though year 2049 should be encoded as UTCTime, and /// GeneralizedTime after 2029. #[inline] pub const fn is_generalizedtime(&self) -> bool { self.generalized } } impl FromDer<'_, X509Error> for ASN1Time { fn from_der(i: &[u8]) -> X509Result { let (rem, time) = parse_choice_of_time(i).map_err(|_| X509Error::InvalidDate)?; Ok((rem, time)) } } pub(crate) fn parse_choice_of_time(i: &[u8]) -> ParseResult { if let Ok((rem, t)) = UtcTime::from_der(i) { let dt = t.utc_adjusted_datetime()?; return Ok((rem, ASN1Time::new_utc(dt))); } if let Ok((rem, t)) = GeneralizedTime::from_der(i) { let dt = t.utc_datetime()?; return Ok((rem, ASN1Time::new_generalized(dt))); } parse_malformed_date(i) } // allow relaxed parsing of UTCTime (ex: 370116130016+0000) fn parse_malformed_date(i: &[u8]) -> ParseResult { #[allow(clippy::trivially_copy_pass_by_ref)] // fn check_char(b: &u8) -> bool { // (0x20 <= *b && *b <= 0x7f) || (*b == b'+') // } let (_rem, hdr) = Header::from_der(i)?; let len = hdr.length().definite()?; if len > MAX_OBJECT_SIZE { return Err(Err::Error(Error::InvalidLength)); } match hdr.tag() { Tag::UtcTime => { // // if we are in this function, the PrintableString could not be validated. // // Accept it without validating charset, because some tools do not respect the charset // // restrictions (for ex. they use '*' while explicingly disallowed) // let (rem, data) = take(len as usize)(rem)?; // if !data.iter().all(check_char) { // return Err(nom::Err::Error(BerError::BerValueError)); // } // let s = std::str::from_utf8(data).map_err(|_| BerError::BerValueError)?; // let content = BerObjectContent::UTCTime(s); // let obj = DerObject::from_header_and_content(hdr, content); // Ok((rem, obj)) Err(Err::Error(Error::BerValueError)) } _ => Err(Err::Error(Error::unexpected_tag(None, hdr.tag()))), } } impl fmt::Display for ASN1Time { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let format = format_description!("[month repr:short] [day padding:space] [hour]:[minute]:[second] [year padding:none] [offset_hour sign:mandatory]:[offset_minute]"); let s = self .time .format(format) .unwrap_or_else(|e| format!("Invalid date: {}", e)); f.write_str(&s) } } impl Add for ASN1Time { type Output = Option; #[inline] fn add(self, rhs: Duration) -> Option { Some(ASN1Time::new(self.time + rhs)) } } impl Sub for ASN1Time { type Output = Option; #[inline] fn sub(self, rhs: ASN1Time) -> Option { if self.time > rhs.time { Some(self.time - rhs.time) } else { None } } } impl From for ASN1Time { fn from(dt: OffsetDateTime) -> Self { ASN1Time::new(dt) } } #[cfg(test)] mod tests { use time::macros::datetime; use super::ASN1Time; #[test] fn test_time_to_string() { let d = datetime!(1 - 1 - 1 12:34:56 UTC); let t = ASN1Time::from(d); assert_eq!(t.to_string(), "Jan 1 12:34:56 1 +00:00".to_string()); } #[test] fn test_nonrfc2822_date() { // test year < 1900 let d = datetime!(1 - 1 - 1 00:00:00 UTC); let t = ASN1Time::from(d); assert!(t.to_rfc2822().is_err()); } } rusticata-x509-parser-a41eb37/src/utils.rs000066400000000000000000000007041474637314500204320ustar00rootroot00000000000000/// Formats a slice to a colon-separated hex string (for ex `01:02:ff:ff`) pub fn format_serial(i: &[u8]) -> String { let mut s = i.iter().fold(String::with_capacity(3 * i.len()), |a, b| { a + &format!("{:02x}:", b) }); s.pop(); s } #[cfg(test)] mod tests { use super::*; #[test] fn test_format_serial() { let b: &[u8] = &[1, 2, 3, 4, 0xff]; assert_eq!("01:02:03:04:ff", format_serial(b)); } } rusticata-x509-parser-a41eb37/src/validate/000077500000000000000000000000001474637314500205145ustar00rootroot00000000000000rusticata-x509-parser-a41eb37/src/validate/certificate.rs000066400000000000000000000006171474637314500233500ustar00rootroot00000000000000use crate::certificate::*; use crate::validate::*; #[derive(Debug)] pub struct X509CertificateValidator; impl<'a> Validator<'a> for X509CertificateValidator { type Item = X509Certificate<'a>; fn validate(&self, item: &'a Self::Item, l: &'_ mut L) -> bool { let mut res = true; res &= X509ExtensionsValidator.validate(&item.extensions(), l); res } } rusticata-x509-parser-a41eb37/src/validate/extensions.rs000066400000000000000000000105171474637314500232650ustar00rootroot00000000000000use crate::extensions::*; use crate::validate::*; use std::collections::HashSet; // extra-pedantic checks const WARN_SHOULD_BE_CRITICAL: bool = false; macro_rules! test_critical { (MUST $ext:ident, $l:ident, $name:expr) => { if !$ext.critical { $l.err(&format!("Extension {} MUST be critical, but is not", $name)); } }; (MUST NOT $ext:ident, $l:ident, $name:expr) => { if $ext.critical { $l.err(&format!("Extension {} MUST NOT be critical, but is", $name)); } }; (SHOULD $ext:ident, $l:ident, $name:expr) => { if WARN_SHOULD_BE_CRITICAL && !$ext.critical { $l.warn(&format!( "Extension {} SHOULD be critical, but is not", $name )); } }; (SHOULD NOT $ext:ident, $l:ident, $name:expr) => { if WARN_SHOULD_BE_CRITICAL && $ext.critical { $l.warn(&format!( "Extension {} SHOULD NOT be critical, but is", $name )); } }; } #[derive(Debug)] pub struct X509ExtensionsValidator; impl<'a> Validator<'a> for X509ExtensionsValidator { type Item = &'a [X509Extension<'a>]; fn validate(&self, item: &'a Self::Item, l: &'_ mut L) -> bool { let mut res = true; // check for duplicate extensions { let mut m = HashSet::new(); for ext in item.iter() { if m.contains(&ext.oid) { l.err(&format!("Duplicate extension {}", ext.oid)); res = false; } else { m.insert(ext.oid.clone()); } } } for ext in item.iter() { // specific extension checks match ext.parsed_extension() { ParsedExtension::AuthorityKeyIdentifier(aki) => { // Conforming CAs MUST mark this extension as non-critical test_critical!(MUST NOT ext, l, "AKI"); // issuer or serial is present must be either both present or both absent if aki.authority_cert_issuer.is_some() ^ aki.authority_cert_serial.is_some() { l.warn("AKI: only one of Issuer and Serial is present"); } } ParsedExtension::CertificatePolicies(policies) => { // A certificate policy OID MUST NOT appear more than once in a // certificate policies extension. let mut policy_oids = HashSet::new(); for policy_info in policies { if policy_oids.contains(&policy_info.policy_id) { l.err(&format!( "Certificate Policies: duplicate policy {}", policy_info.policy_id )); res = false; } else { policy_oids.insert(policy_info.policy_id.clone()); } } } ParsedExtension::KeyUsage(ku) => { // SHOULD be critical test_critical!(SHOULD ext, l, "KeyUsage"); // When the keyUsage extension appears in a certificate, at least one of the bits // MUST be set to 1. if ku.flags == 0 { l.err("KeyUsage: all flags are set to 0"); } } ParsedExtension::SubjectAlternativeName(san) => { // SHOULD be non-critical test_critical!(SHOULD NOT ext, l, "SubjectAltName"); for name in &san.general_names { match name { GeneralName::DNSName(ref s) | GeneralName::RFC822Name(ref s) => { // should be an ia5string if !s.as_bytes().iter().all(u8::is_ascii) { l.warn(&format!("Invalid charset in 'SAN' entry '{}'", s)); } } _ => (), } } } _ => (), } } res } } rusticata-x509-parser-a41eb37/src/validate/loggers.rs000066400000000000000000000033061474637314500225260ustar00rootroot00000000000000pub trait Logger { fn warn(&mut self, message: &str); fn err(&mut self, message: &str); } /// Simple Logger for [`Validator`](crate::validate::Validator) trait, storing messages in `Vec` #[derive(Debug, Default)] pub struct VecLogger { warnings: Vec, errors: Vec, } impl VecLogger { /// Get stored warnings pub fn warnings(&self) -> &[String] { &self.warnings } /// Get stored errors pub fn errors(&self) -> &[String] { &self.errors } } impl Logger for VecLogger { fn warn(&mut self, message: &str) { self.warnings.push(message.to_owned()) } fn err(&mut self, message: &str) { self.errors.push(message.to_owned()) } } /// Simple Logger for [`Validator`](crate::validate::Validator) trait, printing messages to `stderr` #[derive(Debug, Default)] pub struct StderrLogger; impl Logger for StderrLogger { fn warn(&mut self, message: &str) { eprintln!("[W] {}", message); } fn err(&mut self, message: &str) { eprintln!("[E] {}", message); } } /// Simple Logger for [`Validator`](crate::validate::Validator) trait, using closures for `warn`/`err`. #[derive(Debug, Default)] pub struct CallbackLogger where W: FnMut(&str), E: FnMut(&str), { warn: W, err: E, } impl CallbackLogger where W: FnMut(&str), E: FnMut(&str), { pub fn new(warn: W, err: E) -> Self { CallbackLogger { warn, err } } } impl Logger for CallbackLogger where W: FnMut(&str), E: FnMut(&str), { fn warn(&mut self, message: &str) { (self.warn)(message); } fn err(&mut self, message: &str) { (self.err)(message); } } rusticata-x509-parser-a41eb37/src/validate/mod.rs000066400000000000000000000162451474637314500216510ustar00rootroot00000000000000mod certificate; mod extensions; mod loggers; mod name; mod structure; use std::marker::PhantomData; pub use certificate::*; pub use extensions::*; pub use loggers::*; pub use name::*; pub use structure::*; /// Trait for validating item (for ex. validate X.509 structure) /// /// # Examples /// /// Using callbacks: /// /// ``` /// use x509_parser::certificate::X509Certificate; /// # #[allow(deprecated)] /// use x509_parser::validate::Validate; /// # #[allow(deprecated)] /// #[cfg(feature = "validate")] /// fn validate_certificate(x509: &X509Certificate<'_>) -> Result<(), &'static str> { /// println!(" Subject: {}", x509.subject()); /// // validate and print warnings and errors to stderr /// let ok = x509.validate( /// |msg| { /// eprintln!(" [W] {}", msg); /// }, /// |msg| { /// eprintln!(" [E] {}", msg); /// }, /// ); /// print!("Structure validation status: "); /// if ok { /// println!("Ok"); /// Ok(()) /// } else { /// println!("FAIL"); /// Err("validation failed") /// } /// } /// ``` /// /// Collecting warnings and errors to `Vec`: /// /// ``` /// use x509_parser::certificate::X509Certificate; /// # #[allow(deprecated)] /// use x509_parser::validate::Validate; /// /// # #[allow(deprecated)] /// #[cfg(feature = "validate")] /// fn validate_certificate(x509: &X509Certificate<'_>) -> Result<(), &'static str> { /// println!(" Subject: {}", x509.subject()); /// // validate and print warnings and errors to stderr /// let (ok, warnings, errors) = x509.validate_to_vec(); /// print!("Structure validation status: "); /// if ok { /// println!("Ok"); /// } else { /// println!("FAIL"); /// } /// for warning in &warnings { /// eprintln!(" [W] {}", warning); /// } /// for error in &errors { /// eprintln!(" [E] {}", error); /// } /// println!(); /// if !errors.is_empty() { /// return Err("validation failed"); /// } /// Ok(()) /// } /// ``` #[deprecated(since = "0.13.0", note = "please use `X509StructureValidator` instead")] pub trait Validate { /// Attempts to validate current item. /// /// Returns `true` if item was validated. /// /// Call `warn()` if a non-fatal error was encountered, and `err()` /// if the error is fatal. These fucntions receive a description of the error. fn validate(&self, warn: W, err: E) -> bool where W: FnMut(&str), E: FnMut(&str); /// Attempts to validate current item, storing warning and errors in `Vec`. /// /// Returns the validation result (`true` if validated), the list of warnings, /// and the list of errors. fn validate_to_vec(&self) -> (bool, Vec, Vec) { let mut warn_list = Vec::new(); let mut err_list = Vec::new(); let res = self.validate( |s| warn_list.push(s.to_owned()), |s| err_list.push(s.to_owned()), ); (res, warn_list, err_list) } } /// Trait for build item validators (for ex. validate X.509 structure) /// /// See [`X509StructureValidator`] for a default implementation, validating the /// DER structure of a X.509 Certificate. /// /// See implementors of the [`Logger`] trait for methods to collect or handle warnings and errors. /// /// # Examples /// /// Collecting warnings and errors to `Vec`: /// /// ``` /// use x509_parser::certificate::X509Certificate; /// use x509_parser::validate::*; /// /// # #[allow(deprecated)] /// #[cfg(feature = "validate")] /// fn validate_certificate(x509: &X509Certificate<'_>) -> Result<(), &'static str> { /// let mut logger = VecLogger::default(); /// println!(" Subject: {}", x509.subject()); /// // validate and print warnings and errors to stderr /// let ok = X509StructureValidator.validate(&x509, &mut logger); /// print!("Structure validation status: "); /// if ok { /// println!("Ok"); /// } else { /// println!("FAIL"); /// } /// for warning in logger.warnings() { /// eprintln!(" [W] {}", warning); /// } /// for error in logger.errors() { /// eprintln!(" [E] {}", error); /// } /// println!(); /// if !logger.errors().is_empty() { /// return Err("validation failed"); /// } /// Ok(()) /// } /// ``` pub trait Validator<'a> { /// The item to validate type Item; /// Attempts to validate current item. /// /// Returns `true` if item was validated. /// /// Call `l.warn()` if a non-fatal error was encountered, and `l.err()` /// if the error is fatal. These functions receive a description of the error. fn validate(&self, item: &'a Self::Item, l: &'_ mut L) -> bool; fn chain(self, v2: V2) -> ChainValidator<'a, Self, V2, Self::Item> where Self: Sized, V2: Validator<'a, Item = Self::Item>, { ChainValidator { v1: self, v2, _p: PhantomData, } } } #[derive(Debug)] pub struct ChainValidator<'a, A, B, I> where A: Validator<'a, Item = I>, B: Validator<'a, Item = I>, { v1: A, v2: B, _p: PhantomData<&'a ()>, } impl<'a, A, B, I> Validator<'a> for ChainValidator<'a, A, B, I> where A: Validator<'a, Item = I>, B: Validator<'a, Item = I>, { type Item = I; fn validate(&'_ self, item: &'a Self::Item, l: &'_ mut L) -> bool { self.v1.validate(item, l) & self.v2.validate(item, l) } } #[allow(deprecated)] #[cfg(test)] mod tests { use crate::validate::*; struct V1 { a: u32, } impl Validate for V1 { fn validate(&self, mut warn: W, _err: E) -> bool where W: FnMut(&str), E: FnMut(&str), { if self.a > 10 { warn("a is greater than 10"); } true } } struct V1Validator; impl<'a> Validator<'a> for V1Validator { type Item = V1; fn validate(&self, item: &'a Self::Item, l: &'_ mut L) -> bool { if item.a > 10 { l.warn("a is greater than 10"); } true } } #[test] fn validate_warn() { let v1 = V1 { a: 1 }; let (res, warn, err) = v1.validate_to_vec(); assert!(res); assert!(warn.is_empty()); assert!(err.is_empty()); // same, with one warning let v20 = V1 { a: 20 }; let (res, warn, err) = v20.validate_to_vec(); assert!(res); assert_eq!(warn, vec!["a is greater than 10".to_string()]); assert!(err.is_empty()); } #[test] fn validator_warn() { let mut logger = VecLogger::default(); let v1 = V1 { a: 1 }; let res = V1Validator.validate(&v1, &mut logger); assert!(res); assert!(logger.warnings().is_empty()); assert!(logger.errors().is_empty()); // same, with one warning let v20 = V1 { a: 20 }; let res = V1Validator.validate(&v20, &mut logger); assert!(res); assert_eq!(logger.warnings(), &["a is greater than 10".to_string()]); assert!(logger.errors().is_empty()); } } rusticata-x509-parser-a41eb37/src/validate/name.rs000066400000000000000000000017331474637314500220060ustar00rootroot00000000000000use crate::validate::*; use crate::x509::*; use asn1_rs::Tag; #[derive(Debug)] pub struct X509NameStructureValidator; impl<'a> Validator<'a> for X509NameStructureValidator { type Item = X509Name<'a>; fn validate(&self, item: &'a Self::Item, l: &'_ mut L) -> bool { let res = true; // subject/issuer: verify charsets // - wildcards in PrintableString // - non-IA5 in IA5String for attr in item.iter_attributes() { match attr.attr_value().tag() { Tag::PrintableString | Tag::Ia5String => { let b = attr.attr_value().as_bytes(); if !b.iter().all(u8::is_ascii) { l.warn(&format!( "Invalid charset in X.509 Name, component {}", attr.attr_type() )); } } _ => (), } } res } } rusticata-x509-parser-a41eb37/src/validate/structure.rs000066400000000000000000000154101474637314500231230ustar00rootroot00000000000000use super::{Logger, Validator, X509NameStructureValidator}; use crate::certificate::*; use crate::extensions::{GeneralName, ParsedExtension}; use crate::public_key::PublicKey; use crate::x509::{SubjectPublicKeyInfo, X509Version}; /// Default X.509 structure validator for `X509Certificate` /// /// This [`Validator`] iterates the X.509 Certificate fields, and verifies the /// DER encoding and structure: /// - numbers with wrong encoding/sign (for ex. serial number) /// - strings with characters not allowed in DER type (for ex. '*' in `PrintableString`) /// /// # Examples /// /// Validate structure, collect warnings and errors to a `Vec`: /// /// ``` /// use x509_parser::certificate::X509Certificate; /// use x509_parser::validate::*; /// /// # #[allow(deprecated)] /// #[cfg(feature = "validate")] /// fn validate_certificate(x509: &X509Certificate<'_>) -> Result<(), &'static str> { /// let mut logger = VecLogger::default(); /// println!(" Subject: {}", x509.subject()); /// // validate and print warnings and errors to stderr /// let ok = X509StructureValidator.validate(&x509, &mut logger); /// print!("Structure validation status: "); /// if ok { /// println!("Ok"); /// } else { /// println!("FAIL"); /// } /// for warning in logger.warnings() { /// eprintln!(" [W] {}", warning); /// } /// for error in logger.errors() { /// eprintln!(" [E] {}", error); /// } /// println!(); /// if !logger.errors().is_empty() { /// return Err("validation failed"); /// } /// Ok(()) /// } /// ``` #[derive(Debug, Default)] pub struct X509StructureValidator; impl<'a> Validator<'a> for X509StructureValidator { type Item = X509Certificate<'a>; fn validate(&self, item: &'a Self::Item, l: &'_ mut L) -> bool { let mut res = true; res &= TbsCertificateStructureValidator.validate(&item.tbs_certificate, l); res } } /// Default X.509 structure validator for `TbsCertificate` #[derive(Debug, Default)] pub struct TbsCertificateStructureValidator; impl<'a> Validator<'a> for TbsCertificateStructureValidator { type Item = TbsCertificate<'a>; fn validate(&self, item: &'a Self::Item, l: &'_ mut L) -> bool { let mut res = true; // version must be 0, 1 or 2 if item.version.0 >= 3 { l.err("Invalid version"); res = false; } let b = item.raw_serial(); if b.is_empty() { l.err("Serial is empty"); res = false; } else { // check MSB of serial if b[0] & 0x80 != 0 { l.warn("Serial number is negative"); } // check leading zeroes in serial if b.len() > 1 && b[0] == 0 && b[1] & 0x80 == 0 { l.warn("Leading zeroes in serial number"); } } // subject/issuer: verify charsets res &= X509NameStructureValidator.validate(&item.subject, l); res &= X509NameStructureValidator.validate(&item.issuer, l); // subject public key res &= X509PublicKeyValidator.validate(&item.subject_pki, l); // validity: dates <= 2049 must use UTCTime, >= 2050 must use GeneralizedTime let validity = item.validity(); let year_notbefore = validity.not_before.to_datetime().year(); if year_notbefore <= 2049 { if !validity.not_before.is_utctime() { l.warn("year <= 2049 should use UTCTime (notBefore)"); } } else if !validity.not_before.is_generalizedtime() { l.warn("year >= 2050 should use GeneralizedTime (notBefore)"); } let year_notafter = validity.not_after.to_datetime().year(); if year_notafter <= 2049 { if !validity.not_after.is_utctime() { l.warn("year <= 2049 should use UTCTime (notAfter)"); } } else if !validity.not_after.is_generalizedtime() { l.warn("year >= 2050 should use GeneralizedTime (notAfter)"); } if item.version == X509Version::V1 { // unique identifiers: version must 2 or 3 if item.issuer_uid.is_some() { l.warn("issuerUniqueID present but version 1"); } if item.subject_uid.is_some() { l.warn("subjectUniqueID present but version 1"); } } // extensions require v3 if !item.extensions().is_empty() && item.version != X509Version::V3 { l.err("Extensions present but version is not 3"); res = false; } // check for parse errors or unsupported extensions for ext in item.extensions() { if let ParsedExtension::UnsupportedExtension { .. } = &ext.parsed_extension { l.warn(&format!("Unsupported extension {}", ext.oid)); } if let ParsedExtension::ParseError { error } = &ext.parsed_extension { l.err(&format!("Parse error in extension {}: {}", ext.oid, error)); res = false; } } // check extensions for ext in item.extensions() { // specific extension checks // SAN if let ParsedExtension::SubjectAlternativeName(san) = ext.parsed_extension() { for name in &san.general_names { match name { GeneralName::DNSName(ref s) | GeneralName::RFC822Name(ref s) => { // should be an ia5string if !s.as_bytes().iter().all(u8::is_ascii) { l.warn(&format!("Invalid charset in 'SAN' entry '{}'", s)); } } _ => (), } } } } res } } #[derive(Debug, Default)] pub struct X509PublicKeyValidator; impl<'a> Validator<'a> for X509PublicKeyValidator { type Item = SubjectPublicKeyInfo<'a>; fn validate(&self, item: &'a Self::Item, l: &'_ mut L) -> bool { let mut res = true; // res &= TbsCertificateStructureValidator.validate(&item.tbs_certificate, l); match item.parsed() { Ok(PublicKey::RSA(rsa)) => { if rsa.modulus[0] & 0x80 != 0 { l.warn("Public key: (RSA) modulus is negative"); } if rsa.exponent[0] & 0x80 != 0 { l.warn("Public key: (RSA) exponent is negative"); } } Ok(PublicKey::Unknown(_b)) => { l.warn("Unknown public key type"); } Ok(_) => {} Err(_) => { l.err("Invalid public key"); res = false; } } res } } rusticata-x509-parser-a41eb37/src/verify.rs000066400000000000000000000105761474637314500206060ustar00rootroot00000000000000use crate::prelude::*; use crate::signature_algorithm::RsaSsaPssParams; use asn1_rs::{Any, BitString}; use oid_registry::{ OID_EC_P256, OID_NIST_EC_P384, OID_NIST_HASH_SHA256, OID_NIST_HASH_SHA384, OID_NIST_HASH_SHA512, OID_PKCS1_RSASSAPSS, OID_PKCS1_SHA1WITHRSA, OID_PKCS1_SHA256WITHRSA, OID_PKCS1_SHA384WITHRSA, OID_PKCS1_SHA512WITHRSA, OID_SHA1_WITH_RSA, OID_SIG_ECDSA_WITH_SHA256, OID_SIG_ECDSA_WITH_SHA384, OID_SIG_ED25519, }; use std::convert::TryFrom; /// Verify the cryptographic signature of the raw data (can be a certificate, a CRL or a CSR). /// /// `public_key` is the public key of the **signer**. /// /// Not all algorithms are supported, this function is limited to what `ring` supports. pub fn verify_signature( public_key: &SubjectPublicKeyInfo, signature_algorithm: &AlgorithmIdentifier, signature_value: &BitString, raw_data: &[u8], ) -> Result<(), X509Error> { use ring::signature; let AlgorithmIdentifier { algorithm: signature_algorithm, parameters: signature_algorithm_parameters, } = &signature_algorithm; // identify verification algorithm let verification_alg: &dyn signature::VerificationAlgorithm = if *signature_algorithm == OID_PKCS1_SHA1WITHRSA || *signature_algorithm == OID_SHA1_WITH_RSA { &signature::RSA_PKCS1_1024_8192_SHA1_FOR_LEGACY_USE_ONLY } else if *signature_algorithm == OID_PKCS1_SHA256WITHRSA { &signature::RSA_PKCS1_2048_8192_SHA256 } else if *signature_algorithm == OID_PKCS1_SHA384WITHRSA { &signature::RSA_PKCS1_2048_8192_SHA384 } else if *signature_algorithm == OID_PKCS1_SHA512WITHRSA { &signature::RSA_PKCS1_2048_8192_SHA512 } else if *signature_algorithm == OID_PKCS1_RSASSAPSS { get_rsa_pss_verification_algo(signature_algorithm_parameters) .ok_or(X509Error::SignatureUnsupportedAlgorithm)? } else if *signature_algorithm == OID_SIG_ECDSA_WITH_SHA256 { get_ec_curve_sha(&public_key.algorithm, 256) .ok_or(X509Error::SignatureUnsupportedAlgorithm)? } else if *signature_algorithm == OID_SIG_ECDSA_WITH_SHA384 { get_ec_curve_sha(&public_key.algorithm, 384) .ok_or(X509Error::SignatureUnsupportedAlgorithm)? } else if *signature_algorithm == OID_SIG_ED25519 { &signature::ED25519 } else { return Err(X509Error::SignatureUnsupportedAlgorithm); }; // get public key let key = signature::UnparsedPublicKey::new(verification_alg, &public_key.subject_public_key.data); // verify signature key.verify(raw_data, &signature_value.data) .or(Err(X509Error::SignatureVerificationError)) } /// Find the verification algorithm for the given EC curve and SHA digest size /// /// Not all algorithms are supported, we are limited to what `ring` supports. fn get_ec_curve_sha( pubkey_alg: &AlgorithmIdentifier, sha_len: usize, ) -> Option<&'static dyn ring::signature::VerificationAlgorithm> { use ring::signature; let curve_oid = pubkey_alg.parameters.as_ref()?.as_oid().ok()?; // let curve_oid = pubkey_alg.parameters.as_ref()?.as_oid().ok()?; if curve_oid == OID_EC_P256 { match sha_len { 256 => Some(&signature::ECDSA_P256_SHA256_ASN1), 384 => Some(&signature::ECDSA_P256_SHA384_ASN1), _ => None, } } else if curve_oid == OID_NIST_EC_P384 { match sha_len { 256 => Some(&signature::ECDSA_P384_SHA256_ASN1), 384 => Some(&signature::ECDSA_P384_SHA384_ASN1), _ => None, } } else { None } } /// Find the verification algorithm for the given RSA-PSS parameters /// /// Not all algorithms are supported, we are limited to what `ring` supports. /// Notably, the SHA-1 hash algorithm is not supported. fn get_rsa_pss_verification_algo( params: &Option, ) -> Option<&'static dyn ring::signature::VerificationAlgorithm> { use ring::signature; let params = params.as_ref()?; let params = RsaSsaPssParams::try_from(params).ok()?; let hash_algo = params.hash_algorithm_oid(); if *hash_algo == OID_NIST_HASH_SHA256 { Some(&signature::RSA_PSS_2048_8192_SHA256) } else if *hash_algo == OID_NIST_HASH_SHA384 { Some(&signature::RSA_PSS_2048_8192_SHA384) } else if *hash_algo == OID_NIST_HASH_SHA512 { Some(&signature::RSA_PSS_2048_8192_SHA512) } else { None } } rusticata-x509-parser-a41eb37/src/visitor/000077500000000000000000000000001474637314500204225ustar00rootroot00000000000000rusticata-x509-parser-a41eb37/src/visitor/certificate_visitor.rs000066400000000000000000000303241474637314500250330ustar00rootroot00000000000000use asn1_rs::BitString; use oid_registry::*; use crate::certificate::*; use crate::extensions::*; use crate::x509::*; /// Visitor pattern for [`X509Certificate`] /// /// # Extensions /// /// Visitor methods are provided for extensions, both in a generic way (receiving a [`X509Extension`] /// object) and in a specific way for standard extensions (for ex, `visit_extension_aki` receives a /// [`AuthorityKeyIdentifier`]). /// /// For a specific method to be called, the extension OID must be correct and the extension must be /// successfully parsed as the specific type. /// /// A specific method can be called multiple times, if the extension is present multiple times. /// /// Extension parsing methods are redundant. This is not a problem because default methods do nothing, /// but if a trait implementation provides several `visit_extension...` methods it must be aware /// that it will visit the same extension multiple times. /// /// # Example /// /// ```rust /// use x509_parser::prelude::*; /// use x509_parser::visitor::X509CertificateVisitor; /// #[derive(Debug, Default)] /// struct SubjectIssuerVisitor { /// issuer: String, /// subject: String, /// is_ca: bool, /// } /// /// impl X509CertificateVisitor for SubjectIssuerVisitor { /// fn visit_issuer(&mut self, name: &X509Name<'_>) { /// self.issuer = name.to_string(); /// } /// /// fn visit_subject(&mut self, name: &X509Name<'_>) { /// self.subject = name.to_string(); /// } /// /// fn visit_extension_basic_constraints(&mut self, bc: &BasicConstraints) { /// self.is_ca = bc.ca; /// } /// } /// ``` pub trait X509CertificateVisitor { /// Run the provided visitor (`self`) over the [`X509Certificate`] object fn walk(&mut self, x509: &X509Certificate) where Self: Sized, { x509.walk(self); } /// Invoked for the "TBSCertificate" field of the X.509 Certificate, before visiting children fn visit_tbs_certificate(&mut self, _tbs: &TbsCertificate) {} /// Invoked for the "signatureAlgorithm" field of the X.509 Certificate /// /// Note: this is the "signatureAlgorithm" in the "Certificate" sequence. According to the /// specifications, it should be equal to "signature" field from the "TBSCertificate" sequence. fn visit_signature_algorithm(&mut self, _algorithm: &AlgorithmIdentifier) {} /// Invoked for the "signatureValue" field of the TBSCertificate fn visit_signature_value(&mut self, _signature: &BitString) {} /// Invoked for the "version" field of the TBSCertificate fn visit_version(&mut self, _version: &X509Version) {} /// Invoked for the "serialNumber" field of the TBSCertificate fn visit_serial_number(&mut self, _serial: &[u8]) {} /// Invoked for the "signature" field of the TBSCertificate /// /// Note: this is the "signature" field from the "TBSCertificate" sequence. According to the /// specifications, it should be equal to "signatureAlgorithm" in the "Certificate" sequence. fn visit_tbs_signature_algorithm(&mut self, _algorithm: &AlgorithmIdentifier) {} /// Invoked for the "issuer" field of the TBSCertificate fn visit_issuer(&mut self, _name: &X509Name) {} /// Invoked for the "validity" field of the TBSCertificate fn visit_validity(&mut self, _validity: &Validity) {} /// Invoked for the "subject" field of the TBSCertificate fn visit_subject(&mut self, _name: &X509Name) {} /// Invoked for the "subjectPublicKeyInfo" field of the TBSCertificate fn visit_subject_public_key_info(&mut self, _subject_pki: &SubjectPublicKeyInfo) {} /// Invoked for the "issuerUniqueID" field of the TBSCertificate fn visit_issuer_unique_id(&mut self, _id: Option<&UniqueIdentifier>) {} /// Invoked for the "subjectUniqueID" field of the TBSCertificate fn visit_subject_unique_id(&mut self, _id: Option<&UniqueIdentifier>) {} /// Invoked for extensions, before visiting children fn pre_visit_extensions(&mut self, _extensions: &[X509Extension]) {} /// Invoked for any extension that appear in the X.509 Certificate /// /// Note: this method may be redundant with any other extension visitor method fn visit_extension(&mut self, _extension: &X509Extension) {} /// Invoked for extensions, after visiting children fn post_visit_extensions(&mut self, _extensions: &[X509Extension]) {} /// Invoked for the "Authority Key Identifier" (if present) fn visit_extension_aki(&mut self, _aki: &AuthorityKeyIdentifier) {} /// Invoked for the "Subject Key Identifier" (if present) fn visit_extension_ski(&mut self, _id: &KeyIdentifier) {} /// Invoked for the "Key Usage" (if present) fn visit_extension_key_usage(&mut self, _usage: &KeyUsage) {} /// Invoked for the "Certificate Policies" (if present) fn visit_extension_certificate_policies(&mut self, _policies: &CertificatePolicies) {} /// Invoked for the "Subject Alternative Name" (if present) fn visit_extension_subject_alternative_name(&mut self, _san: &SubjectAlternativeName) {} /// Invoked for the "Issuer Alternative Name" (if present) fn visit_extension_issuer_alternative_name(&mut self, _ian: &IssuerAlternativeName) {} /// Invoked for the "Basic Constraints" (if present) fn visit_extension_basic_constraints(&mut self, _bc: &BasicConstraints) {} /// Invoked for the "Name Constraints" (if present) fn visit_extension_name_constraints(&mut self, _constraints: &NameConstraints) {} /// Invoked for the "Policy Constraints" (if present) fn visit_extension_policy_constraints(&mut self, _constraints: &PolicyConstraints) {} /// Invoked for the "Extended Key Usage" (if present) fn visit_extension_extended_key_usage(&mut self, _usage: &ExtendedKeyUsage) {} /// Invoked for the "CRL Distribution Points" (if present) fn visit_extension_crl_distribution_points(&mut self, _crl: &CRLDistributionPoints) {} /// Invoked for the "Inhibit anyPolicy" (if present) fn visit_extension_inhibit_anypolicy(&mut self, _policy: &InhibitAnyPolicy) {} /// Invoked for the "Authority Information Access" (if present) fn visit_extension_authority_information_access(&mut self, _info: &AuthorityInfoAccess) {} /// Invoked for the "Signed Certificate Timestamp" (SCT) (if present) fn visit_extension_sct(&mut self, _sct: &[SignedCertificateTimestamp]) {} } impl X509Certificate<'_> { /// Run the provided [`X509CertificateVisitor`] over the X.509 Certificate (`self`) pub fn walk(&self, visitor: &mut V) { visitor.visit_tbs_certificate(&self.tbs_certificate); self.tbs_certificate.walk(visitor); visitor.visit_signature_algorithm(&self.signature_algorithm); visitor.visit_signature_value(&self.signature_value); } } impl TbsCertificate<'_> { /// Run the provided `visitor` over the [`TbsCertificate`] object pub fn walk(&self, visitor: &mut V) { visitor.visit_version(&self.version); visitor.visit_serial_number(self.raw_serial()); visitor.visit_tbs_signature_algorithm(&self.signature); visitor.visit_issuer(&self.issuer); visitor.visit_validity(&self.validity); visitor.visit_subject(&self.subject); visitor.visit_subject_public_key_info(&self.subject_pki); visitor.visit_issuer_unique_id(self.issuer_uid.as_ref()); visitor.visit_subject_unique_id(self.subject_uid.as_ref()); visitor.pre_visit_extensions(self.extensions()); for extension in self.extensions() { visitor.visit_extension(extension); if extension.oid == OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER { if let ParsedExtension::AuthorityKeyIdentifier(aki) = &extension.parsed_extension { visitor.visit_extension_aki(aki); } } else if extension.oid == OID_X509_EXT_SUBJECT_KEY_IDENTIFIER { if let ParsedExtension::SubjectKeyIdentifier(id) = &extension.parsed_extension { visitor.visit_extension_ski(id); } } else if extension.oid == OID_X509_EXT_KEY_USAGE { if let ParsedExtension::KeyUsage(usage) = &extension.parsed_extension { visitor.visit_extension_key_usage(usage); } } else if extension.oid == OID_X509_EXT_CERTIFICATE_POLICIES { if let ParsedExtension::CertificatePolicies(policies) = &extension.parsed_extension { visitor.visit_extension_certificate_policies(policies); } } else if extension.oid == OID_X509_EXT_SUBJECT_ALT_NAME { if let ParsedExtension::SubjectAlternativeName(san) = &extension.parsed_extension { visitor.visit_extension_subject_alternative_name(san); } } else if extension.oid == OID_X509_EXT_ISSUER_ALT_NAME { if let ParsedExtension::IssuerAlternativeName(ian) = &extension.parsed_extension { visitor.visit_extension_issuer_alternative_name(ian); } } else if extension.oid == OID_X509_EXT_BASIC_CONSTRAINTS { if let ParsedExtension::BasicConstraints(bc) = &extension.parsed_extension { visitor.visit_extension_basic_constraints(bc); } } else if extension.oid == OID_X509_EXT_NAME_CONSTRAINTS { if let ParsedExtension::NameConstraints(constraints) = &extension.parsed_extension { visitor.visit_extension_name_constraints(constraints); } } else if extension.oid == OID_X509_EXT_POLICY_CONSTRAINTS { if let ParsedExtension::PolicyConstraints(constraints) = &extension.parsed_extension { visitor.visit_extension_policy_constraints(constraints); } } else if extension.oid == OID_X509_EXT_EXTENDED_KEY_USAGE { if let ParsedExtension::ExtendedKeyUsage(usage) = &extension.parsed_extension { visitor.visit_extension_extended_key_usage(usage); } } else if extension.oid == OID_X509_EXT_CRL_DISTRIBUTION_POINTS { if let ParsedExtension::CRLDistributionPoints(crl) = &extension.parsed_extension { visitor.visit_extension_crl_distribution_points(crl); } } else if extension.oid == OID_X509_EXT_INHIBITANT_ANY_POLICY { if let ParsedExtension::InhibitAnyPolicy(policy) = &extension.parsed_extension { visitor.visit_extension_inhibit_anypolicy(policy); } } else if extension.oid == OID_PKIX_AUTHORITY_INFO_ACCESS { if let ParsedExtension::AuthorityInfoAccess(info) = &extension.parsed_extension { visitor.visit_extension_authority_information_access(info); } } else if extension.oid == OID_CT_LIST_SCT { if let ParsedExtension::SCT(sct) = &extension.parsed_extension { visitor.visit_extension_sct(sct); } } } visitor.post_visit_extensions(self.extensions()); } } #[cfg(test)] mod tests { use super::*; use crate::FromDer; static IGCA_DER: &[u8] = include_bytes!("../../assets/IGC_A.der"); #[test] fn visitor_certificate() { #[derive(Debug, Default)] struct SubjectIssuerVisitor { issuer: String, subject: String, is_ca: bool, } impl X509CertificateVisitor for SubjectIssuerVisitor { fn visit_issuer(&mut self, name: &X509Name) { self.issuer = name.to_string(); } fn visit_subject(&mut self, name: &X509Name) { self.subject = name.to_string(); } fn visit_extension_basic_constraints(&mut self, bc: &BasicConstraints) { self.is_ca = bc.ca; } } let mut visitor = SubjectIssuerVisitor::default(); let (_, x509) = X509Certificate::from_der(IGCA_DER).unwrap(); x509.walk(&mut visitor); assert!(!visitor.issuer.is_empty()); assert!(visitor.is_ca); assert_eq!(&visitor.issuer, &visitor.subject); } } rusticata-x509-parser-a41eb37/src/visitor/crl_visitor.rs000066400000000000000000000223451474637314500233350ustar00rootroot00000000000000use asn1_rs::BitString; use der_parser::num_bigint::BigUint; use oid_registry::*; use crate::extensions::*; use crate::revocation_list::*; use crate::time::ASN1Time; use crate::x509::*; /// Visitor pattern for [`CertificateRevocationList`] /// /// # Extensions /// /// Visitor methods are provided for extensions, both in a generic way (receiving a [`X509Extension`] /// object) and in a specific way for standard extensions (for ex, `visit_extension_aki` receives a /// [`AuthorityKeyIdentifier`]). /// /// For a specific method to be called, the extension OID must be correct and the extension must be /// successfully parsed as the specific type. /// /// A specific method can be called multiple times, if the extension is present multiple times. /// /// Extension parsing methods are redundant. This is not a problem because default methods do nothing, /// but if a trait implementation provides several `visit_extension...` methods it must be aware /// that it will visit the same extension multiple times. /// /// # Example /// /// ```rust /// use der_parser::num_bigint::BigUint; /// use x509_parser::prelude::*; /// use x509_parser::visitor::CertificateRevocationListVisitor; /// #[derive(Debug, Default)] /// struct RevokedCertsVisitor { /// certificates: Vec, /// } /// /// impl CertificateRevocationListVisitor for RevokedCertsVisitor { /// fn visit_revoked_certificate(&mut self, certificate: &RevokedCertificate<'_>) { /// self.certificates.push(certificate.user_certificate.clone()); /// } /// } /// ``` pub trait CertificateRevocationListVisitor { /// Run the provided visitor (`self`) over the Certificate Revocation List fn walk(&mut self, crl: &CertificateRevocationList) where Self: Sized, { crl.walk(self); } /// Invoked for the "tbsCertList" field of the Certificate Revocation List, before visiting children fn visit_tbs_cert_list(&mut self, _tbs: &TbsCertList) {} /// Invoked for the "signatureAlgorithm" field of the Certificate Revocation List /// /// Note: this is the "signatureAlgorithm" in the "CertificateList" sequence. According to the /// specifications, it should be equal to "signature" field from the "TBSCertificate" sequence. fn visit_signature_algorithm(&mut self, _algorithm: &AlgorithmIdentifier) {} /// Invoked for the "signatureValue" field of the TBSCertList fn visit_signature_value(&mut self, _signature: &BitString) {} /// Invoked for the "version" field of the TBSCertList fn visit_version(&mut self, _version: Option<&X509Version>) {} /// Invoked for the "signature" field of the TBSCertList /// /// Note: this is the "signature" field from the "TBSCertList" sequence. According to the /// specifications, it should be equal to "signatureAlgorithm" in the "CertificateList" sequence. fn visit_tbs_signature_algorithm(&mut self, _algorithm: &AlgorithmIdentifier) {} /// Invoked for the "issuer" field of the TBSCertList fn visit_issuer(&mut self, _name: &X509Name) {} /// Invoked for the "thisUpdate" field of the TBSCertList fn visit_this_update(&mut self, _time: &ASN1Time) {} /// Invoked for the "nextUpdate" field of the TBSCertList fn visit_next_update(&mut self, _time: Option<&ASN1Time>) {} /// Invoked for revoked certificate that appear in the TBSCertList fn visit_revoked_certificates(&mut self, _certificate: &[RevokedCertificate]) {} /// Invoked for any revoked certificates that appear in the TBSCertList /// /// Note: this function is redundant with `visit_revoked_certificates` fn visit_revoked_certificate(&mut self, _certificate: &RevokedCertificate) {} /// Invoked for extensions, before visiting children fn pre_visit_extensions(&mut self, _extensions: &[X509Extension]) {} /// Invoked for any extension that appear in the TBSCertList /// /// Note: this method may be redundant with any other extension visitor method fn visit_extension(&mut self, _extension: &X509Extension) {} /// Invoked for extensions, after visiting children fn post_visit_extensions(&mut self, _extensions: &[X509Extension]) {} /// Invoked for the "Authority Key Identifier" (if present) fn visit_extension_aki(&mut self, _aki: &AuthorityKeyIdentifier) {} /// Invoked for the "Issuer Alternative Name" (if present) fn visit_extension_issuer_alternative_name(&mut self, _ian: &IssuerAlternativeName) {} /// Invoked for the "CRL Number" (if present) fn visit_extension_crl_number(&mut self, _number: &BigUint) {} /// Invoked for the "Issuing Distribution Point" (if present) fn visit_extension_issuing_distribution_point(&mut self, _dp: &IssuingDistributionPoint) {} /// Invoked for the "Authority Information Access" (if present) fn visit_extension_authority_information_access(&mut self, _info: &AuthorityInfoAccess) {} /// Invoked for the "Reason Code" (if present) fn visit_extension_reason_code(&mut self, _code: &ReasonCode) {} /// Invoked for the "Invalidity Date" (if present) fn visit_extension_invalidity_date(&mut self, _time: &ASN1Time) {} /// Invoked for the "Signed Certificate Timestamp" (SCT) (if present) fn visit_extension_sct(&mut self, _sct: &[SignedCertificateTimestamp]) {} } impl CertificateRevocationList<'_> { /// Run the provided [`CertificateRevocationListVisitor`] over the Certificate Revocation List (`self`) pub fn walk(&self, visitor: &mut V) { visitor.visit_tbs_cert_list(&self.tbs_cert_list); self.tbs_cert_list.walk(visitor); visitor.visit_signature_algorithm(&self.signature_algorithm); visitor.visit_signature_value(&self.signature_value); } } impl TbsCertList<'_> { /// Run the provided `visitor` over the [`TbsCertList`] object pub fn walk(&self, visitor: &mut V) { visitor.visit_version(self.version.as_ref()); visitor.visit_tbs_signature_algorithm(&self.signature); visitor.visit_issuer(&self.issuer); visitor.visit_this_update(&self.this_update); visitor.visit_next_update(self.next_update.as_ref()); visitor.visit_revoked_certificates(&self.revoked_certificates); for certificate in &self.revoked_certificates { visitor.visit_revoked_certificate(certificate); } visitor.pre_visit_extensions(self.extensions()); for extension in self.extensions() { visitor.visit_extension(extension); if extension.oid == OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER { if let ParsedExtension::AuthorityKeyIdentifier(aki) = &extension.parsed_extension { visitor.visit_extension_aki(aki); } } else if extension.oid == OID_X509_EXT_ISSUER_ALT_NAME { if let ParsedExtension::IssuerAlternativeName(ian) = &extension.parsed_extension { visitor.visit_extension_issuer_alternative_name(ian); } } else if extension.oid == OID_X509_EXT_CRL_NUMBER { if let ParsedExtension::CRLNumber(number) = &extension.parsed_extension { visitor.visit_extension_crl_number(number); } } else if extension.oid == OID_X509_EXT_ISSUER_DISTRIBUTION_POINT { if let ParsedExtension::IssuingDistributionPoint(dp) = &extension.parsed_extension { visitor.visit_extension_issuing_distribution_point(dp); } } else if extension.oid == OID_PKIX_AUTHORITY_INFO_ACCESS { if let ParsedExtension::AuthorityInfoAccess(info) = &extension.parsed_extension { visitor.visit_extension_authority_information_access(info); } } else if extension.oid == OID_X509_EXT_REASON_CODE { if let ParsedExtension::ReasonCode(code) = &extension.parsed_extension { visitor.visit_extension_reason_code(code); } } else if extension.oid == OID_X509_EXT_INVALIDITY_DATE { if let ParsedExtension::InvalidityDate(time) = &extension.parsed_extension { visitor.visit_extension_invalidity_date(time); } } else if extension.oid == OID_CT_LIST_SCT { if let ParsedExtension::SCT(sct) = &extension.parsed_extension { visitor.visit_extension_sct(sct); } } } visitor.post_visit_extensions(self.extensions()); } } #[cfg(test)] mod tests { use super::*; use crate::FromDer; static CRL: &[u8] = include_bytes!("../../assets/example.crl"); #[test] fn visitor_crl() { #[derive(Debug, Default)] struct RevokedCertsVisitor { certificates: Vec, } impl CertificateRevocationListVisitor for RevokedCertsVisitor { fn visit_revoked_certificate(&mut self, certificate: &RevokedCertificate) { self.certificates.push(certificate.user_certificate.clone()); } } let mut visitor = RevokedCertsVisitor::default(); let (_, crl) = CertificateRevocationList::from_der(CRL).unwrap(); crl.walk(&mut visitor); assert_eq!(visitor.certificates.len(), 5); } } rusticata-x509-parser-a41eb37/src/visitor/mod.rs000066400000000000000000000001431474637314500215450ustar00rootroot00000000000000mod certificate_visitor; mod crl_visitor; pub use certificate_visitor::*; pub use crl_visitor::*; rusticata-x509-parser-a41eb37/src/x509.rs000066400000000000000000000563121474637314500200050ustar00rootroot00000000000000//! X.509 objects and types //! //! Based on RFC5280 //! use crate::error::{X509Error, X509Result}; use crate::objects::*; use crate::public_key::*; use asn1_rs::{ Any, BitString, BmpString, DerSequence, FromBer, FromDer, OptTaggedParser, ParseResult, }; use core::convert::TryFrom; use data_encoding::HEXUPPER; use der_parser::ber::MAX_OBJECT_SIZE; use der_parser::der::*; use der_parser::error::*; use der_parser::num_bigint::BigUint; use der_parser::*; use nom::branch::alt; use nom::bytes::complete::take; use nom::combinator::{complete, map}; use nom::multi::{many0, many1}; use nom::{Err, Offset}; use oid_registry::*; use rusticata_macros::newtype_enum; use std::fmt; use std::iter::FromIterator; /// The version of the encoded certificate. /// /// When extensions are used, as expected in this profile, version MUST be 3 /// (value is `2`). If no extensions are present, but a UniqueIdentifier /// is present, the version SHOULD be 2 (value is `1`); however, the /// version MAY be 3. If only basic fields are present, the version /// SHOULD be 1 (the value is omitted from the certificate as the default /// value); however, the version MAY be 2 or 3. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct X509Version(pub u32); impl X509Version { /// Parse `[0]` EXPLICIT Version DEFAULT v1 pub(crate) fn from_der_tagged_0(i: &[u8]) -> X509Result { let (rem, opt_version) = OptTaggedParser::from(0) .parse_der(i, |_, data| Self::from_der(data)) .map_err(Err::convert)?; let version = opt_version.unwrap_or(X509Version::V1); Ok((rem, version)) } } // Version ::= INTEGER { v1(0), v2(1), v3(2) } impl<'a> FromDer<'a, X509Error> for X509Version { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { map(::from_der, X509Version)(i).map_err(|_| Err::Error(X509Error::InvalidVersion)) } } newtype_enum! { impl display X509Version { V1 = 0, V2 = 1, V3 = 2, } } /// A generic attribute type and value /// /// These objects are used as [`RelativeDistinguishedName`] components. #[derive(Clone, Debug, PartialEq)] pub struct AttributeTypeAndValue<'a> { attr_type: Oid<'a>, attr_value: Any<'a>, // ANY -- DEFINED BY AttributeType } impl<'a> AttributeTypeAndValue<'a> { /// Builds a new `AttributeTypeAndValue` #[inline] pub const fn new(attr_type: Oid<'a>, attr_value: Any<'a>) -> Self { AttributeTypeAndValue { attr_type, attr_value, } } /// Returns the attribute type #[inline] pub const fn attr_type(&self) -> &Oid<'a> { &self.attr_type } /// Returns the attribute value, as `ANY` #[inline] pub const fn attr_value(&self) -> &Any<'a> { &self.attr_value } /// Attempt to get the content as `str`. /// This can fail if the object does not contain a string type. /// /// Note: the [`TryFrom`] trait is implemented for `&str`, so this is /// equivalent to `attr.try_into()`. /// /// Only NumericString, PrintableString, UTF8String and IA5String /// are considered here. Other string types can be read using `as_slice`. #[inline] pub fn as_str(&self) -> Result<&'a str, X509Error> { // TODO: replace this with helper function, when it is added to asn1-rs match self.attr_value.tag() { Tag::NumericString | Tag::PrintableString | Tag::Utf8String | Tag::Ia5String => { let s = core::str::from_utf8(self.attr_value.data) .map_err(|_| X509Error::InvalidAttributes)?; Ok(s) } t => Err(X509Error::Der(Error::unexpected_tag(None, t))), } } /// Get the content as a slice. #[inline] pub fn as_slice(&self) -> &[u8] { self.attr_value.as_bytes() } } impl<'a, 'b> TryFrom<&'a AttributeTypeAndValue<'b>> for &'a str { type Error = X509Error; fn try_from(value: &'a AttributeTypeAndValue<'b>) -> Result { value.attr_value.as_str().map_err(|e| e.into()) } } impl<'a, 'b> From<&'a AttributeTypeAndValue<'b>> for &'a [u8] { fn from(value: &'a AttributeTypeAndValue<'b>) -> Self { value.as_slice() } } // AttributeTypeAndValue ::= SEQUENCE { // type AttributeType, // value AttributeValue } impl<'a> FromDer<'a, X509Error> for AttributeTypeAndValue<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parse_der_sequence_defined_g(|i, _| { let (i, attr_type) = Oid::from_der(i).or(Err(X509Error::InvalidX509Name))?; let (i, attr_value) = parse_attribute_value(i).or(Err(X509Error::InvalidX509Name))?; let attr = AttributeTypeAndValue::new(attr_type, attr_value); Ok((i, attr)) })(i) } } // AttributeValue ::= ANY -- DEFINED BY AttributeType #[inline] fn parse_attribute_value(i: &[u8]) -> ParseResult { alt((Any::from_der, parse_malformed_string))(i) } fn parse_malformed_string(i: &[u8]) -> ParseResult { let (rem, hdr) = Header::from_der(i)?; let len = hdr.length().definite()?; if len > MAX_OBJECT_SIZE { return Err(Err::Error(Error::InvalidLength)); } match hdr.tag() { Tag::PrintableString => { // if we are in this function, the PrintableString could not be validated. // Accept it without validating charset, because some tools do not respect the charset // restrictions (for ex. they use '*' while explicingly disallowed) let (rem, data) = take(len)(rem)?; // check valid encoding let _ = std::str::from_utf8(data).map_err(|_| Error::BerValueError)?; let obj = Any::new(hdr, data); Ok((rem, obj)) } t => Err(Err::Error(Error::unexpected_tag( Some(Tag::PrintableString), t, ))), } } /// A Relative Distinguished Name element. /// /// These objects are used as [`X509Name`] components. #[derive(Clone, Debug, PartialEq)] pub struct RelativeDistinguishedName<'a> { set: Vec>, } impl<'a> RelativeDistinguishedName<'a> { /// Builds a new `RelativeDistinguishedName` #[inline] pub const fn new(set: Vec>) -> Self { RelativeDistinguishedName { set } } /// Return an iterator over the components of this object pub fn iter(&self) -> impl Iterator> { self.set.iter() } } impl<'a> FromIterator> for RelativeDistinguishedName<'a> { fn from_iter>>(iter: T) -> Self { let set = iter.into_iter().collect(); RelativeDistinguishedName { set } } } impl<'a> FromDer<'a, X509Error> for RelativeDistinguishedName<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parse_der_set_defined_g(|i, _| { let (i, set) = many1(complete(AttributeTypeAndValue::from_der))(i)?; let rdn = RelativeDistinguishedName { set }; Ok((i, rdn)) })(i) } } #[derive(Clone, Debug, PartialEq)] pub struct SubjectPublicKeyInfo<'a> { pub algorithm: AlgorithmIdentifier<'a>, pub subject_public_key: BitString<'a>, /// A raw unparsed PKIX, ASN.1 DER form (see RFC 5280, Section 4.1). /// /// Note: use the [`Self::parsed()`] function to parse this object. pub raw: &'a [u8], } impl SubjectPublicKeyInfo<'_> { /// Attempt to parse the public key, and return the parsed version or an error pub fn parsed(&self) -> Result { let b = &self.subject_public_key.data; if self.algorithm.algorithm == OID_PKCS1_RSAENCRYPTION { let (_, key) = RSAPublicKey::from_der(b).map_err(|_| X509Error::InvalidSPKI)?; Ok(PublicKey::RSA(key)) } else if self.algorithm.algorithm == OID_KEY_TYPE_EC_PUBLIC_KEY { let key = ECPoint::from(b.as_ref()); Ok(PublicKey::EC(key)) } else if self.algorithm.algorithm == OID_KEY_TYPE_DSA { let s = parse_der_integer(b) .and_then(|(_, obj)| obj.as_slice().map_err(Err::Error)) .or(Err(X509Error::InvalidSPKI))?; Ok(PublicKey::DSA(s)) } else if self.algorithm.algorithm == OID_GOST_R3410_2001 { let (_, s) = <&[u8]>::from_der(b).or(Err(X509Error::InvalidSPKI))?; Ok(PublicKey::GostR3410(s)) } else if self.algorithm.algorithm == OID_KEY_TYPE_GOST_R3410_2012_256 || self.algorithm.algorithm == OID_KEY_TYPE_GOST_R3410_2012_512 { let (_, s) = <&[u8]>::from_der(b).or(Err(X509Error::InvalidSPKI))?; Ok(PublicKey::GostR3410_2012(s)) } else { Ok(PublicKey::Unknown(b)) } } } impl<'a> FromDer<'a, X509Error> for SubjectPublicKeyInfo<'a> { /// Parse the SubjectPublicKeyInfo struct portion of a DER-encoded X.509 Certificate fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { let start_i = i; parse_der_sequence_defined_g(move |i, _| { let (i, algorithm) = AlgorithmIdentifier::from_der(i)?; let (i, subject_public_key) = BitString::from_der(i).or(Err(X509Error::InvalidSPKI))?; let len = start_i.offset(i); let raw = &start_i[..len]; let spki = SubjectPublicKeyInfo { algorithm, subject_public_key, raw, }; Ok((i, spki)) })(i) } } /// Algorithm identifier /// /// An algorithm identifier is defined by the following ASN.1 structure: /// ///
/// AlgorithmIdentifier  ::=  SEQUENCE  {
///      algorithm               OBJECT IDENTIFIER,
///      parameters              ANY DEFINED BY algorithm OPTIONAL  }
/// 
/// /// The algorithm identifier is used to identify a cryptographic /// algorithm. The OBJECT IDENTIFIER component identifies the algorithm /// (such as DSA with SHA-1). The contents of the optional parameters /// field will vary according to the algorithm identified. #[derive(Clone, Debug, PartialEq, DerSequence)] #[error(X509Error)] pub struct AlgorithmIdentifier<'a> { #[map_err(|_| X509Error::InvalidAlgorithmIdentifier)] pub algorithm: Oid<'a>, #[optional] pub parameters: Option>, } impl<'a> AlgorithmIdentifier<'a> { /// Create a new `AlgorithmIdentifier` pub const fn new(algorithm: Oid<'a>, parameters: Option>) -> Self { Self { algorithm, parameters, } } /// Get the algorithm OID pub const fn oid(&'a self) -> &'a Oid<'a> { &self.algorithm } /// Get a reference to the algorithm parameters, if present pub const fn parameters(&'a self) -> Option<&'a Any<'a>> { self.parameters.as_ref() } } /// X.509 Name (as used in `Issuer` and `Subject` fields) /// /// The Name describes a hierarchical name composed of attributes, such /// as country name, and corresponding values, such as US. The type of /// the component AttributeValue is determined by the AttributeType; in /// general it will be a DirectoryString. #[derive(Clone, Debug, PartialEq)] pub struct X509Name<'a> { pub(crate) rdn_seq: Vec>, pub(crate) raw: &'a [u8], } impl fmt::Display for X509Name<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match x509name_to_string(&self.rdn_seq, oid_registry()) { Ok(o) => write!(f, "{}", o), Err(_) => write!(f, ""), } } } impl<'a> X509Name<'a> { /// Builds a new `X509Name` from the provided elements. #[inline] pub const fn new(rdn_seq: Vec>, raw: &'a [u8]) -> Self { X509Name { rdn_seq, raw } } /// Attempt to format the current name, using the given registry to convert OIDs to strings. /// /// Note: a default registry is provided with this crate, and is returned by the /// [`oid_registry()`] method. pub fn to_string_with_registry(&self, oid_registry: &OidRegistry) -> Result { x509name_to_string(&self.rdn_seq, oid_registry) } // Not using the AsRef trait, as that would not give back the full 'a lifetime pub fn as_raw(&self) -> &'a [u8] { self.raw } /// Return an iterator over the `RelativeDistinguishedName` components of the name pub fn iter(&self) -> impl Iterator> { self.rdn_seq.iter() } /// Return an iterator over the `RelativeDistinguishedName` components of the name pub fn iter_rdn(&self) -> impl Iterator> { self.rdn_seq.iter() } /// Return an iterator over the attribute types and values of the name pub fn iter_attributes(&self) -> impl Iterator> { self.rdn_seq.iter().flat_map(|rdn| rdn.set.iter()) } /// Return an iterator over the components identified by the given OID /// /// The type of the component AttributeValue is determined by the AttributeType; in /// general it will be a DirectoryString. /// /// Attributes with same OID may be present multiple times, so the returned object is /// an iterator. /// Expected number of objects in this iterator are /// - 0: not found /// - 1: present once (common case) /// - 2 or more: attribute is present multiple times pub fn iter_by_oid(&self, oid: &Oid<'a>) -> impl Iterator> { // this is necessary, otherwise rustc complains // that caller creates a temporary value for reference (for ex. // `self.iter_by_oid(&OID_X509_LOCALITY_NAME)` // ) let oid = oid.clone(); self.iter_attributes() .filter(move |obj| obj.attr_type == oid) } /// Return an iterator over the `CommonName` attributes of the X.509 Name. /// /// Returned iterator can be empty if there are no `CommonName` attributes. /// If you expect only one `CommonName` to be present, then using `next()` will /// get an `Option<&AttributeTypeAndValue>`. /// /// A common operation is to extract the `CommonName` as a string. /// /// ``` /// use x509_parser::x509::X509Name; /// /// fn get_first_cn_as_str<'a>(name: &'a X509Name<'_>) -> Option<&'a str> { /// name.iter_common_name() /// .next() /// .and_then(|cn| cn.as_str().ok()) /// } /// ``` /// /// Note that there are multiple reasons for failure or incorrect behavior, for ex. if /// the attribute is present multiple times, or is not a UTF-8 encoded string (it can be /// UTF-16, or even an OCTETSTRING according to the standard). pub fn iter_common_name(&self) -> impl Iterator> { self.iter_by_oid(&OID_X509_COMMON_NAME) } /// Return an iterator over the `Country` attributes of the X.509 Name. pub fn iter_country(&self) -> impl Iterator> { self.iter_by_oid(&OID_X509_COUNTRY_NAME) } /// Return an iterator over the `Organization` attributes of the X.509 Name. pub fn iter_organization(&self) -> impl Iterator> { self.iter_by_oid(&OID_X509_ORGANIZATION_NAME) } /// Return an iterator over the `OrganizationalUnit` attributes of the X.509 Name. pub fn iter_organizational_unit(&self) -> impl Iterator> { self.iter_by_oid(&OID_X509_ORGANIZATIONAL_UNIT) } /// Return an iterator over the `StateOrProvinceName` attributes of the X.509 Name. pub fn iter_state_or_province(&self) -> impl Iterator> { self.iter_by_oid(&OID_X509_STATE_OR_PROVINCE_NAME) } /// Return an iterator over the `Locality` attributes of the X.509 Name. pub fn iter_locality(&self) -> impl Iterator> { self.iter_by_oid(&OID_X509_LOCALITY_NAME) } /// Return an iterator over the `EmailAddress` attributes of the X.509 Name. pub fn iter_email(&self) -> impl Iterator> { self.iter_by_oid(&OID_PKCS9_EMAIL_ADDRESS) } } impl<'a> FromIterator> for X509Name<'a> { fn from_iter>>(iter: T) -> Self { let rdn_seq = iter.into_iter().collect(); X509Name { rdn_seq, raw: &[] } } } impl<'a> From> for Vec> { fn from(name: X509Name<'a>) -> Self { name.rdn_seq } } impl<'a> FromDer<'a, X509Error> for X509Name<'a> { /// Parse the X.501 type Name, used for ex in issuer and subject of a X.509 certificate fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { let start_i = i; parse_der_sequence_defined_g(move |i, _| { let (i, rdn_seq) = many0(complete(RelativeDistinguishedName::from_der))(i)?; let len = start_i.offset(i); let name = X509Name { rdn_seq, raw: &start_i[..len], }; Ok((i, name)) })(i) } } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct ReasonCode(pub u8); newtype_enum! { impl display ReasonCode { Unspecified = 0, KeyCompromise = 1, CACompromise = 2, AffiliationChanged = 3, Superseded = 4, CessationOfOperation = 5, CertificateHold = 6, // value 7 is not used RemoveFromCRL = 8, PrivilegeWithdrawn = 9, AACompromise = 10, } } impl Default for ReasonCode { fn default() -> Self { ReasonCode::Unspecified } } // Attempt to convert attribute to string. If type is not a string, return value is the hex // encoding of the attribute value fn attribute_value_to_string(attr: &Any, _attr_type: &Oid) -> Result { // TODO: replace this with helper function, when it is added to asn1-rs match attr.tag() { Tag::NumericString | Tag::VisibleString | Tag::PrintableString | Tag::GeneralString | Tag::ObjectDescriptor | Tag::GraphicString | Tag::T61String | Tag::VideotexString | Tag::Utf8String | Tag::Ia5String => { let s = core::str::from_utf8(attr.data).map_err(|_| X509Error::InvalidAttributes)?; Ok(s.to_owned()) } Tag::BmpString => { // TODO: remove this when a new release of asn1-rs removes the need to consume attr in try_from let any = attr.clone(); let s = BmpString::try_from(any).map_err(|_| X509Error::InvalidAttributes)?; Ok(s.string()) } _ => { // type is not a string, get slice and convert it to base64 Ok(HEXUPPER.encode(attr.as_bytes())) } } } /// Convert a DER representation of a X.509 name to a human-readable string /// /// RDNs are separated with "," /// Multiple RDNs are separated with "+" /// /// Attributes that cannot be represented by a string are hex-encoded fn x509name_to_string( rdn_seq: &[RelativeDistinguishedName], oid_registry: &OidRegistry, ) -> Result { rdn_seq.iter().try_fold(String::new(), |acc, rdn| { rdn.set .iter() .try_fold(String::new(), |acc2, attr| { let val_str = attribute_value_to_string(&attr.attr_value, &attr.attr_type)?; // look ABBREV, and if not found, use shortname let abbrev = match oid2abbrev(&attr.attr_type, oid_registry) { Ok(s) => String::from(s), _ => format!("{:?}", attr.attr_type), }; let rdn = format!("{}={}", abbrev, val_str); match acc2.len() { 0 => Ok(rdn), _ => Ok(acc2 + " + " + &rdn), } }) .map(|v| match acc.len() { 0 => v, _ => acc + ", " + &v, }) }) } pub(crate) fn parse_signature_value(i: &[u8]) -> X509Result { BitString::from_der(i).or(Err(Err::Error(X509Error::InvalidSignatureValue))) } pub(crate) fn parse_serial(i: &[u8]) -> X509Result<(&[u8], BigUint)> { let (rem, any) = Any::from_ber(i).map_err(|_| X509Error::InvalidSerial)?; // RFC 5280 4.1.2.2: "The serial number MUST be a positive integer" // however, many CAs do not respect this and send integers with MSB set, // so we do not use `as_biguint()` any.tag() .assert_eq(Tag::Integer) .map_err(|_| X509Error::InvalidSerial)?; let slice = any.data; let big = BigUint::from_bytes_be(slice); Ok((rem, (slice, big))) } #[cfg(test)] mod tests { use super::*; #[test] fn test_x509_version() { // correct version let data: &[u8] = &[0xa0, 0x03, 0x02, 0x01, 0x00]; let r = X509Version::from_der_tagged_0(data); assert!(r.is_ok()); // wrong tag let data: &[u8] = &[0xa1, 0x03, 0x02, 0x01, 0x00]; let r = X509Version::from_der_tagged_0(data); assert!(r.is_ok()); // short read let data: &[u8] = &[0xa0, 0x01]; let r = X509Version::from_der_tagged_0(data); assert!(r.is_err()); // short read with wrong tag let data: &[u8] = &[0xa1, 0x01]; let r = X509Version::from_der_tagged_0(data); assert!(r.is_err()); } #[test] fn test_x509_name() { let name = X509Name { rdn_seq: vec![ RelativeDistinguishedName { set: vec![AttributeTypeAndValue { attr_type: oid! {2.5.4.6}, // countryName attr_value: Any::from_tag_and_data(Tag::PrintableString, b"FR"), }], }, RelativeDistinguishedName { set: vec![AttributeTypeAndValue { attr_type: oid! {2.5.4.8}, // stateOrProvinceName attr_value: Any::from_tag_and_data(Tag::PrintableString, b"Some-State"), }], }, RelativeDistinguishedName { set: vec![AttributeTypeAndValue { attr_type: oid! {2.5.4.10}, // organizationName attr_value: Any::from_tag_and_data( Tag::PrintableString, b"Internet Widgits Pty Ltd", ), }], }, RelativeDistinguishedName { set: vec![ AttributeTypeAndValue { attr_type: oid! {2.5.4.3}, // CN attr_value: Any::from_tag_and_data(Tag::PrintableString, b"Test1"), }, AttributeTypeAndValue { attr_type: oid! {2.5.4.3}, // CN attr_value: Any::from_tag_and_data(Tag::PrintableString, b"Test2"), }, ], }, ], raw: &[], // incorrect, but enough for testing }; assert_eq!( name.to_string(), "C=FR, ST=Some-State, O=Internet Widgits Pty Ltd, CN=Test1 + CN=Test2" ); } } rusticata-x509-parser-a41eb37/tests/000077500000000000000000000000001474637314500172765ustar00rootroot00000000000000rusticata-x509-parser-a41eb37/tests/pem.rs000066400000000000000000000030231474637314500204230ustar00rootroot00000000000000use std::io::Cursor; use x509_parser::pem::{parse_x509_pem, Pem}; use x509_parser::{parse_x509_certificate, x509::X509Version}; static IGCA_PEM: &[u8] = include_bytes!("../assets/IGC_A.pem"); #[test] fn test_x509_parse_pem() { let (rem, pem) = parse_x509_pem(IGCA_PEM).expect("PEM parsing failed"); // println!("{:?}", pem); assert!(rem.is_empty()); assert_eq!(pem.label, String::from("CERTIFICATE")); // // now check that the content is indeed a certificate let (rem, crt) = parse_x509_certificate(&pem.contents).expect("X.509 parsing failed"); // println!("res: {:?}", res); assert!(rem.is_empty()); assert_eq!(crt.tbs_certificate.version, X509Version::V3); } #[test] fn test_pem_read() { let reader = Cursor::new(IGCA_PEM); let (pem, bytes_read) = Pem::read(reader).expect("Reading PEM failed"); // println!("{:?}", pem); assert_eq!(bytes_read, IGCA_PEM.len()); assert_eq!(pem.label, String::from("CERTIFICATE")); // // now check that the content is indeed a certificate let x509 = pem.parse_x509().expect("X.509: decoding DER failed"); assert_eq!(x509.tbs_certificate.version, X509Version::V3); } #[test] fn test_pem_not_pem() { let bytes = vec![0x1, 0x2, 0x3, 0x4, 0x5]; let reader = Cursor::new(bytes); let res = Pem::read(reader); assert!(res.is_err()); } static NO_END: &[u8] = include_bytes!("../assets/no_end.pem"); #[test] fn test_pem_no_end() { let reader = Cursor::new(NO_END); let res = Pem::read(reader); assert!(res.is_err()); } rusticata-x509-parser-a41eb37/tests/readcert.rs000066400000000000000000000362661474637314500214520ustar00rootroot00000000000000use ::time::macros::datetime; use ::time::OffsetDateTime; use der_parser::oid; use nom::Parser; use oid_registry::*; use std::collections::HashMap; use x509_parser::prelude::*; static IGCA_DER: &[u8] = include_bytes!("../assets/IGC_A.der"); static NO_EXTENSIONS_DER: &[u8] = include_bytes!("../assets/no_extensions.der"); static V1: &[u8] = include_bytes!("../assets/v1.der"); static CRL_DER: &[u8] = include_bytes!("../assets/example.crl"); static EMPTY_CRL_DER: &[u8] = include_bytes!("../assets/empty.crl"); static MINIMAL_CRL_DER: &[u8] = include_bytes!("../assets/minimal.crl"); static DUPLICATE_VALUE_IN_AIA: &[u8] = include_bytes!("../assets/duplicate_value_in_authority_info_access.der"); static UNIQUE_IDS_DER: &[u8] = include_bytes!("../assets/unique_ids.der"); #[test] fn test_x509_parser() { let empty = &b""[..]; //assert_eq!(x509_parser(IGCA_DER), IResult::Done(empty, (1))); let res = parse_x509_certificate(IGCA_DER); // println!("res: {:?}", res); match res { Ok((e, cert)) => { assert_eq!(e, empty); // let tbs_cert = &cert.tbs_certificate; assert_eq!(tbs_cert.version, X509Version::V3); // let s = tbs_cert.raw_serial_as_string(); assert_eq!(&s, "39:11:45:10:94"); // let expected_subject = "C=FR, ST=France, L=Paris, O=PM/SGDN, OU=DCSSI, CN=IGC/A, Email=igca@sgdn.pm.gouv.fr"; assert_eq!(format!("{}", tbs_cert.subject), expected_subject); // let cn_list: Result, X509Error> = cert .subject() .iter_common_name() .map(|attr| attr.as_str()) .collect(); assert_eq!(cn_list, Ok(vec!["IGC/A"])); // let sig = &tbs_cert.signature; assert_eq!(sig.algorithm, oid! {1.2.840.113549.1.1.5}); // let expected_issuer = "C=FR, ST=France, L=Paris, O=PM/SGDN, OU=DCSSI, CN=IGC/A, Email=igca@sgdn.pm.gouv.fr"; assert_eq!(format!("{}", tbs_cert.issuer), expected_issuer); let expected_issuer_der = &IGCA_DER[35..171]; assert_eq!(tbs_cert.issuer.as_raw(), expected_issuer_der); // let sig_alg = &cert.signature_algorithm; assert_eq!(sig_alg.algorithm, OID_PKCS1_SHA1WITHRSA); // let not_before = &tbs_cert.validity.not_before; let not_after = &tbs_cert.validity.not_after; let nb = not_before.to_datetime(); let na = not_after.to_datetime(); assert_eq!(nb.year(), 2002); assert_eq!(nb.month() as u8, 12); assert_eq!(nb.day(), 13); assert_eq!(na.year(), 2020); assert_eq!(na.month() as u8, 10); assert_eq!(na.day(), 17); let policies = vec![PolicyInformation { policy_id: oid!(1.2.250 .1 .121 .1 .1 .1), policy_qualifiers: None, }]; let expected_extensions = vec![ X509Extension::new( oid!(2.5.29 .19), true, &[48, 3, 1, 1, 255], ParsedExtension::BasicConstraints(BasicConstraints { ca: true, path_len_constraint: None, }), ), X509Extension::new( oid!(2.5.29 .15), false, &[3, 2, 1, 70], ParsedExtension::KeyUsage(KeyUsage { flags: 98 }), ), X509Extension::new( oid!(2.5.29 .32), false, &[48, 12, 48, 10, 6, 8, 42, 129, 122, 1, 121, 1, 1, 1], ParsedExtension::CertificatePolicies(policies), ), X509Extension::new( oid!(2.5.29 .14), false, &[ 4, 20, 163, 5, 47, 24, 96, 80, 194, 137, 10, 221, 43, 33, 79, 255, 142, 78, 168, 48, 49, 54, ], ParsedExtension::SubjectKeyIdentifier(KeyIdentifier(&[ 163, 5, 47, 24, 96, 80, 194, 137, 10, 221, 43, 33, 79, 255, 142, 78, 168, 48, 49, 54, ])), ), X509Extension::new( oid!(2.5.29 .35), false, &[ 48, 22, 128, 20, 163, 5, 47, 24, 96, 80, 194, 137, 10, 221, 43, 33, 79, 255, 142, 78, 168, 48, 49, 54, ], ParsedExtension::AuthorityKeyIdentifier(AuthorityKeyIdentifier { key_identifier: Some(KeyIdentifier(&[ 163, 5, 47, 24, 96, 80, 194, 137, 10, 221, 43, 33, 79, 255, 142, 78, 168, 48, 49, 54, ])), authority_cert_issuer: None, authority_cert_serial: None, }), ), ]; assert_eq!(tbs_cert.extensions(), &expected_extensions as &[_]); // assert!(tbs_cert.is_ca()); // assert_eq!(tbs_cert.as_ref(), &IGCA_DER[4..(8 + 746)]); } _ => panic!("x509 parsing failed: {:?}", res), } } #[test] fn test_x509_no_extensions() { let empty = &b""[..]; let res = parse_x509_certificate(NO_EXTENSIONS_DER); match res { Ok((e, cert)) => { assert_eq!(e, empty); let tbs_cert = cert.tbs_certificate; assert_eq!(tbs_cert.version, X509Version::V3); assert_eq!(tbs_cert.extensions().len(), 0); } _ => panic!("x509 parsing failed: {:?}", res), } } #[test] fn test_parse_subject_public_key_info() { let res = SubjectPublicKeyInfo::from_der(&IGCA_DER[339..]) .expect("Parse public key info") .1; assert_eq!(res.algorithm.algorithm, OID_PKCS1_RSAENCRYPTION); let params = res.algorithm.parameters.expect("algorithm parameters"); assert_eq!(params.tag().0, 5); let spk = res.subject_public_key; println!("spk.data.len {}", spk.data.len()); assert_eq!(spk.data.len(), 270); } #[test] fn test_version_v1() { let (rem, cert) = parse_x509_certificate(V1).expect("Could not parse v1 certificate"); assert!(rem.is_empty()); assert_eq!(cert.version(), X509Version::V1); let tbs_cert = cert.tbs_certificate; assert_eq!(format!("{}", tbs_cert.subject), "CN=marquee"); assert_eq!(format!("{}", tbs_cert.issuer), "CN=marquee"); } #[test] fn test_crl_parse() { match parse_x509_crl(CRL_DER) { Ok((e, cert)) => { assert!(e.is_empty()); let tbs_cert_list = cert.tbs_cert_list; assert_eq!(tbs_cert_list.version, Some(X509Version::V2)); let sig = &tbs_cert_list.signature; assert_eq!(sig.algorithm, OID_PKCS1_SHA1WITHRSA); let expected_issuer = "O=Sample Signer Organization, OU=Sample Signer Unit, CN=Sample Signer Cert"; assert_eq!(format!("{}", tbs_cert_list.issuer), expected_issuer); let sig_alg = &cert.signature_algorithm; assert_eq!(sig_alg.algorithm, OID_PKCS1_SHA1WITHRSA); let this_update = tbs_cert_list.this_update; let next_update = tbs_cert_list.next_update.unwrap(); let tu = OffsetDateTime::from_unix_timestamp(this_update.timestamp()).unwrap(); let nu = OffsetDateTime::from_unix_timestamp(next_update.timestamp()).unwrap(); assert_eq!(tu.year(), 2013); assert_eq!(tu.month() as u8, 2); assert_eq!(tu.day(), 18); assert_eq!(nu.year(), 2013); assert_eq!(nu.month() as u8, 2); assert_eq!(nu.day(), 18); let revocation_date = datetime! {2013-02-18 10:22:12 UTC}; let invalidity_date = datetime! {2013-02-18 10:22:00 UTC}; let revoked_certs = &tbs_cert_list.revoked_certificates; let revoked_cert_0 = &revoked_certs[0]; assert_eq!(*revoked_cert_0.serial(), 0x147947u32.into()); assert_eq!( revoked_cert_0.revocation_date.to_datetime(), revocation_date ); let expected_extensions = vec![ X509Extension::new( oid!(2.5.29 .21), false, &[10, 1, 3], ParsedExtension::ReasonCode(ReasonCode::AffiliationChanged), ), X509Extension::new( oid!(2.5.29 .24), false, &[ 24, 15, 50, 48, 49, 51, 48, 50, 49, 56, 49, 48, 50, 50, 48, 48, 90, ], ParsedExtension::InvalidityDate(invalidity_date.into()), ), ]; assert_eq!(revoked_cert_0.extensions(), &expected_extensions as &[_]); assert_eq!(revoked_certs.len(), 5); assert_eq!(revoked_certs[4].user_certificate, 1_341_771_u32.into()); let expected_extensions = vec![ X509Extension::new( oid!(2.5.29 .35), false, &[ 48, 22, 128, 20, 190, 18, 1, 204, 170, 234, 17, 128, 218, 46, 173, 178, 234, 199, 181, 251, 159, 249, 173, 52, ], ParsedExtension::AuthorityKeyIdentifier(AuthorityKeyIdentifier { key_identifier: Some(KeyIdentifier(&[ 190, 18, 1, 204, 170, 234, 17, 128, 218, 46, 173, 178, 234, 199, 181, 251, 159, 249, 173, 52, ])), authority_cert_issuer: None, authority_cert_serial: None, }), ), X509Extension::new( oid!(2.5.29 .20), false, &[2, 1, 3], ParsedExtension::CRLNumber(3u32.into()), ), ]; assert_eq!(tbs_cert_list.extensions(), &expected_extensions as &[_]); assert_eq!(tbs_cert_list.as_ref(), &CRL_DER[4..(4 + 4 + 508)]); } err => panic!("x509 parsing failed: {:?}", err), } } #[test] fn test_crl_parse_empty() { match parse_x509_crl(EMPTY_CRL_DER) { Ok((e, cert)) => { assert!(e.is_empty()); assert!(cert.tbs_cert_list.revoked_certificates.is_empty()); let expected_extensions = vec![ X509Extension::new( oid!(2.5.29 .20), false, &[2, 1, 2], ParsedExtension::CRLNumber(2u32.into()), ), X509Extension::new( OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER, false, &[ 48, 22, 128, 20, 34, 101, 12, 214, 90, 157, 52, 137, 243, 131, 180, 149, 82, 191, 80, 27, 57, 39, 6, 172, ], ParsedExtension::AuthorityKeyIdentifier(AuthorityKeyIdentifier { key_identifier: Some(KeyIdentifier(&[ 34, 101, 12, 214, 90, 157, 52, 137, 243, 131, 180, 149, 82, 191, 80, 27, 57, 39, 6, 172, ])), authority_cert_issuer: None, authority_cert_serial: None, }), ), ]; assert_eq!(cert.extensions(), &expected_extensions as &[_]); assert_eq!( cert.tbs_cert_list.as_ref(), &EMPTY_CRL_DER[4..(4 + 3 + 200)] ); } err => panic!("x509 parsing failed: {:?}", err), } } #[test] fn test_crl_parse_minimal() { match parse_x509_crl(MINIMAL_CRL_DER) { Ok((e, crl)) => { assert!(e.is_empty()); let revocation_date = datetime! {1970-01-01 00:00:00 UTC}; let revoked_certificates = &crl.tbs_cert_list.revoked_certificates; assert_eq!(revoked_certificates.len(), 1); let revoked_cert_0 = &revoked_certificates[0]; assert_eq!(*revoked_cert_0.serial(), 42u32.into()); assert_eq!( revoked_cert_0.revocation_date.to_datetime(), revocation_date ); assert!(revoked_cert_0.extensions().is_empty()); assert!(crl.tbs_cert_list.extensions().is_empty()); assert_eq!(crl.tbs_cert_list.as_ref(), &MINIMAL_CRL_DER[4..(4 + 79)]); } err => panic!("x509 parsing failed: {:?}", err), } } #[test] fn test_duplicate_authority_info_access() { match parse_x509_certificate(DUPLICATE_VALUE_IN_AIA) { Ok((_, cert)) => { let extension = cert .tbs_certificate .get_extension_unique(&OID_PKIX_AUTHORITY_INFO_ACCESS) .expect("could not get AIA") .expect("no AIA found"); let mut accessdescs = HashMap::new(); let ca_issuers = vec![ &GeneralName::URI("http://cdp1.pca.dfn.de/dfn-ca-global-g2/pub/cacert/cacert.crt"), &GeneralName::URI("http://cdp2.pca.dfn.de/dfn-ca-global-g2/pub/cacert/cacert.crt"), ]; let ocsp = vec![&GeneralName::URI("http://ocsp.pca.dfn.de/OCSP-Server/OCSP")]; accessdescs.insert(OID_PKIX_ACCESS_DESCRIPTOR_CA_ISSUERS, ca_issuers); accessdescs.insert(OID_PKIX_ACCESS_DESCRIPTOR_OCSP, ocsp); if let ParsedExtension::AuthorityInfoAccess(aia) = extension.parsed_extension() { let h = aia.as_hashmap(); assert_eq!(h, accessdescs); } else { panic!("Wrong extension type parsed"); } } err => panic!("x509 parsing failed: {:?}", err), } } #[test] fn test_x509_parser_no_ext() { let mut parser = X509CertificateParser::new().with_deep_parse_extensions(false); let (_, x509) = parser.parse(IGCA_DER).expect("parsing failed"); for ext in x509.extensions() { assert_eq!(ext.parsed_extension(), &ParsedExtension::Unparsed); } } #[test] fn test_tbscert_unique_identifiers() { let mut parser = X509CertificateParser::new().with_deep_parse_extensions(false); let (_, x509) = parser.parse(UNIQUE_IDS_DER).expect("parsing failed"); assert_eq!( &x509 .tbs_certificate .issuer_uid .expect("missing issuer uid") .0 .as_ref(), &[ 0x30, 0x16, 0x80, 0x14, 0xc5, 0x78, 0x84, 0xb8, 0xc, 0x6e, 0x8c, 0x4c, 0xce, 0xb9, 0x94, 0x6f, 0x98, 0xfc, 0xf3, 0x8a, 0x54, 0xb1, 0x80, 0xe0 ] ); assert_eq!( &x509 .tbs_certificate .subject_uid .expect("missing subject uid") .0 .as_ref(), &[ 0x4, 0x14, 0xdf, 0x13, 0xac, 0x69, 0x14, 0x90, 0x62, 0xdb, 0x3d, 0xe9, 0xb4, 0x56, 0xe6, 0xa6, 0x90, 0x26, 0xbf, 0x2c, 0xef, 0x81 ] ); } rusticata-x509-parser-a41eb37/tests/readcrl.rs000066400000000000000000000075621474637314500212720ustar00rootroot00000000000000use x509_parser::prelude::*; #[cfg(feature = "verify")] #[test] fn read_crl_verify() { const CA_DATA: &[u8] = include_bytes!("../assets/ca_minimalcrl.der"); const CRL_DATA: &[u8] = include_bytes!("../assets/minimal.crl"); let (_, x509_ca) = X509Certificate::from_der(CA_DATA).expect("could not parse certificate"); let (_, crl) = parse_x509_crl(CRL_DATA).expect("could not parse revocation list"); let res = crl.verify_signature(&x509_ca.tbs_certificate.subject_pki); eprintln!("Verification: {:?}", res); assert!(res.is_ok()); } fn crl_idp<'a>(crl: &'a CertificateRevocationList) -> &'a IssuingDistributionPoint<'a> { let crl_idp = crl .tbs_cert_list .find_extension(&oid_registry::OID_X509_EXT_ISSUER_DISTRIBUTION_POINT) .expect("missing IDP extension"); match crl_idp.parsed_extension() { ParsedExtension::IssuingDistributionPoint(crl_idp) => crl_idp, _ => panic!("wrong extension type"), } } #[test] fn read_minimal_crl_idp() { const CRL_DATA: &[u8] = include_bytes!("../assets/crl-idp/minimal.der"); let (_, crl) = parse_x509_crl(CRL_DATA).expect("could not parse revocation list"); let crl_idp = crl_idp(&crl); let dp = crl_idp .distribution_point .as_ref() .expect("missing distribution point"); let full_name = match dp { DistributionPointName::FullName(full_name) => full_name, DistributionPointName::NameRelativeToCRLIssuer(_) => { panic!("wrong distribution point name type") } }; assert_eq!(full_name.len(), 1); let uri = match full_name.first().unwrap() { GeneralName::URI(uri) => *uri, _ => panic!("wrong general name type"), }; assert_eq!(uri, "http://crl.trustcor.ca/sub/dv-ssl-rsa-s-0.crl"); assert!(!crl_idp.only_contains_user_certs); assert!(!crl_idp.only_contains_ca_certs); assert!(crl_idp.only_some_reasons.is_none()); assert!(!crl_idp.only_contains_attribute_certs); } #[test] fn test_only_user_crl_idp() { const CRL_DATA: &[u8] = include_bytes!("../assets/crl-idp/only_user_certs.der"); let (_, crl) = parse_x509_crl(CRL_DATA).expect("could not parse revocation list"); let crl_idp = crl_idp(&crl); assert!(crl_idp.only_contains_user_certs); assert!(!crl_idp.only_contains_ca_certs); assert!(crl_idp.only_some_reasons.is_none()); assert!(!crl_idp.only_contains_attribute_certs); } #[test] fn test_only_ca_crl_idp() { const CRL_DATA: &[u8] = include_bytes!("../assets/crl-idp/only_ca_certs.der"); let (_, crl) = parse_x509_crl(CRL_DATA).expect("could not parse revocation list"); let crl_idp = crl_idp(&crl); assert!(!crl_idp.only_contains_user_certs); assert!(crl_idp.only_contains_ca_certs); assert!(crl_idp.only_some_reasons.is_none()); assert!(!crl_idp.only_contains_attribute_certs); } #[test] fn test_only_some_reasons_crl_idp() { const CRL_DATA: &[u8] = include_bytes!("../assets/crl-idp/only_some_reasons.der"); let (_, crl) = parse_x509_crl(CRL_DATA).expect("could not parse revocation list"); let crl_idp = crl_idp(&crl); assert!(!crl_idp.only_contains_user_certs); assert!(!crl_idp.only_contains_ca_certs); assert!(!crl_idp.only_contains_attribute_certs); let reasons = crl_idp .only_some_reasons .as_ref() .expect("missing only_some_reasons"); assert!(reasons.key_compromise()); assert!(reasons.affilation_changed()); } #[test] fn test_only_attribute_cers_crl_idp() { const CRL_DATA: &[u8] = include_bytes!("../assets/crl-idp/only_attribute_certs.der"); let (_, crl) = parse_x509_crl(CRL_DATA).expect("could not parse revocation list"); let crl_idp = crl_idp(&crl); assert!(!crl_idp.only_contains_user_certs); assert!(!crl_idp.only_contains_ca_certs); assert!(crl_idp.only_some_reasons.is_none()); assert!(crl_idp.only_contains_attribute_certs); } rusticata-x509-parser-a41eb37/tests/readcsr.rs000066400000000000000000000113061474637314500212700ustar00rootroot00000000000000use asn1_rs::Set; use oid_registry::{ OID_PKCS1_SHA256WITHRSA, OID_PKCS9_CHALLENGE_PASSWORD, OID_SIG_ECDSA_WITH_SHA256, OID_X509_COMMON_NAME, }; use x509_parser::prelude::*; const CSR_DATA_EMPTY_ATTRIB: &[u8] = include_bytes!("../assets/csr-empty-attributes.csr"); const CSR_DATA: &[u8] = include_bytes!("../assets/test.csr"); const CSR_CHALLENGE_PASSWORD: &[u8] = include_bytes!("../assets/csr-challenge-password.pem"); #[test] fn read_csr_empty_attrib() { let (rem, csr) = X509CertificationRequest::from_der(CSR_DATA_EMPTY_ATTRIB).expect("could not parse CSR"); assert!(rem.is_empty()); let cri = &csr.certification_request_info; assert_eq!(cri.version, X509Version(0)); assert_eq!(cri.attributes().len(), 0); assert_eq!(csr.signature_algorithm.algorithm, OID_PKCS1_SHA256WITHRSA); } #[test] fn read_csr_with_san() { let der = pem::parse_x509_pem(CSR_DATA).unwrap().1; let (rem, csr) = X509CertificationRequest::from_der(&der.contents).expect("could not parse CSR"); assert!(rem.is_empty()); let cri = &csr.certification_request_info; assert_eq!(cri.version, X509Version(0)); assert_eq!(cri.attributes().len(), 1); assert_eq!(csr.signature_algorithm.algorithm, OID_SIG_ECDSA_WITH_SHA256); let mut rdns = cri.subject.iter(); let rdn = rdns.next().unwrap(); let first = rdn.iter().next().unwrap(); assert_eq!(first.attr_type(), &OID_X509_COMMON_NAME); assert_eq!(first.as_str().unwrap(), "test.rusticata.fr"); let expected: &[u8] = &[ 4, 195, 245, 126, 177, 113, 192, 146, 215, 136, 181, 58, 82, 138, 142, 61, 253, 245, 185, 192, 166, 216, 218, 145, 219, 42, 169, 112, 122, 58, 91, 184, 150, 37, 237, 245, 59, 54, 44, 210, 44, 207, 218, 167, 148, 189, 210, 159, 207, 103, 233, 1, 187, 134, 137, 24, 240, 188, 223, 135, 215, 71, 80, 64, 65, ]; assert_eq!(cri.subject_pki.subject_public_key.data, expected); let mut extensions = csr.requested_extensions().unwrap(); match extensions.next().unwrap() { ParsedExtension::SubjectAlternativeName(san) => { let name = san.general_names.first().unwrap(); assert!(matches!(name, GeneralName::DNSName("test.rusticata.fr"))); } _ => unreachable!(), } } #[test] fn read_csr_with_challenge_password() { let der = pem::parse_x509_pem(CSR_CHALLENGE_PASSWORD).unwrap().1; let (rem, csr) = X509CertificationRequest::from_der(&der.contents) .expect("Could not parse CSR with challenge password"); assert!(rem.is_empty()); let cri = &csr.certification_request_info; assert_eq!(cri.version, X509Version(0)); assert_eq!(cri.attributes().len(), 2); let challenge_password_attr = csr .certification_request_info .find_attribute(&OID_PKCS9_CHALLENGE_PASSWORD) .expect("Challenge password not found in CSR"); // 1. Check: Parse value let (rem, challenge_password_from_value) = Set::from_der_and_then(challenge_password_attr.value, String::from_der) .expect("Error parsing challenge password attribute"); assert_eq!(challenge_password_from_value, "A challenge password"); assert!(rem.is_empty()); // 2. Check: Get value directly from parsed attribute if let ParsedCriAttribute::ChallengePassword(challenge_password_from_parsed_attribute) = challenge_password_attr.parsed_attribute() { assert_eq!( challenge_password_from_parsed_attribute.0, "A challenge password" ); } else { panic!("Parsed attribute is not a challenge password"); } // Make sure we can read requested extensions let extensions = csr .requested_extensions() .expect("Didn't find requested extensions in CSR"); let mut found_san = false; for extension in extensions { if let ParsedExtension::SubjectAlternativeName(san) = extension { let name = san.general_names.get(2).unwrap(); assert!(matches!(name, GeneralName::DNSName("localhost"))); found_san = true; } } assert!(found_san); } #[cfg(feature = "verify")] #[test] fn read_csr_verify() { let der = pem::parse_x509_pem(CSR_DATA).unwrap().1; let (_, csr) = X509CertificationRequest::from_der(&der.contents).expect("could not parse CSR"); csr.verify_signature().unwrap(); let mut der = pem::parse_x509_pem(CSR_DATA).unwrap().1; assert_eq!(&der.contents[28..37], b"rusticata"); for (i, b) in b"foobarbaz".iter().enumerate() { der.contents[28 + i] = *b; } assert_eq!(&der.contents[28..37], b"foobarbaz"); let (_, csr) = X509CertificationRequest::from_der(&der.contents).expect("could not parse CSR"); csr.verify_signature().unwrap_err(); } rusticata-x509-parser-a41eb37/tests/run_all_fuzz_files.rs000066400000000000000000000020711474637314500235400ustar00rootroot00000000000000use std::fs::{self, DirEntry}; use x509_parser::parse_x509_certificate; const ARTIFACTS_DIR: &str = "fuzz/artifacts/fuzzer_script_1"; const CORPUS_DIR: &str = "fuzz/corpus/fuzzer_script_1"; #[test] fn run_all_fuzz_files() { parse_dir(ARTIFACTS_DIR); parse_dir(CORPUS_DIR); } fn parse_dir(name: &str) { match fs::read_dir(name) { Ok(dir_entries) => { dir_entries.for_each(|entry| { let _ = entry.as_ref().map(parse_file); }); } Err(_) => eprintln!("fuzzer corpus/artifacts not found - ignoring test"), } } fn parse_file(entry: &DirEntry) -> std::io::Result<()> { let path = entry.path(); // println!("{:?}", entry.path()); let data = fs::read(path).unwrap(); let _ = parse_x509_certificate(&data); Ok(()) } #[test] #[ignore = "placeholder for specific tests"] fn run_fuzz_candidate() { const CANDIDATE: &str = "fuzz/corpus/fuzzer_script_1/bd0096a63b9979d64763915a342a59af9dc281fb"; let data = fs::read(CANDIDATE).unwrap(); let _ = parse_x509_certificate(&data); } rusticata-x509-parser-a41eb37/tests/test01.rs000066400000000000000000000011371474637314500207660ustar00rootroot00000000000000use nom::bytes::complete::take; #[test] fn test01() { 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"; let _ = x509_parser::parse_x509_certificate(data); } fn parser02(input: &[u8]) -> nom::IResult<&[u8], ()> { let (_hdr, input) = take(1_usize)(input)?; let (_data, input) = take(18_446_744_073_709_551_615_usize)(input)?; Ok((input, ())) } #[test] fn test02() { 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"; let _ = parser02(data); } rusticata-x509-parser-a41eb37/tests/verify.rs000066400000000000000000000051001474637314500211440ustar00rootroot00000000000000#![cfg(feature = "verify")] use x509_parser::parse_x509_certificate; static CA_DER: &[u8] = include_bytes!("../assets/IGC_A.der"); static CA_LETSENCRYPT_X3: &[u8] = include_bytes!("../assets/lets-encrypt-x3-cross-signed.der"); static CERT_DER: &[u8] = include_bytes!("../assets/certificate.der"); #[test] fn test_signature_verification() { // for a root CA, verify self-signature let (_, x509_ca) = parse_x509_certificate(CA_DER).expect("could not parse certificate"); let res = x509_ca.verify_signature(None); eprintln!("Verification: {:?}", res); assert!(res.is_ok()); // for a standard certificate, first load the authority, then the certificate, and verify it let (_, x509_ca) = parse_x509_certificate(CA_LETSENCRYPT_X3).expect("could not parse certificate"); let (_, x509_cert) = parse_x509_certificate(CERT_DER).expect("could not parse certificate"); let res = x509_cert.verify_signature(Some(&x509_ca.tbs_certificate.subject_pki)); eprintln!("Verification: {:?}", res); assert!(res.is_ok()); } static ED25519_DER: &[u8] = include_bytes!("../assets/ed25519.der"); #[test] fn test_signature_verification_ed25519() { // this certificate is self-signed let (_, x509_ca) = parse_x509_certificate(ED25519_DER).expect("could not parse certificate"); let res = x509_ca.verify_signature(None); eprintln!("Verification: {:?}", res); assert!(res.is_ok()); } static RSA_PSS_SELF_SIGNED_SHA256: &[u8] = include_bytes!("../assets/rsa-pss/self_signed_sha256.der"); static RSA_PSS_SELF_SIGNED_SHA384: &[u8] = include_bytes!("../assets/rsa-pss/self_signed_sha384.der"); static RSA_PSS_SELF_SIGNED_SHA512: &[u8] = include_bytes!("../assets/rsa-pss/self_signed_sha512.der"); #[test] fn test_signature_verification_rsa_pss_sha256() { let (_, x509) = parse_x509_certificate(RSA_PSS_SELF_SIGNED_SHA256).expect("could not parse certificate"); let res = x509.verify_signature(None); eprintln!("Verification: {:?}", res); assert!(res.is_ok()); } #[test] fn test_signature_verification_rsa_pss_sha384() { let (_, x509) = parse_x509_certificate(RSA_PSS_SELF_SIGNED_SHA384).expect("could not parse certificate"); let res = x509.verify_signature(None); eprintln!("Verification: {:?}", res); assert!(res.is_ok()); } #[test] fn test_signature_verification_rsa_pss_sha512() { let (_, x509) = parse_x509_certificate(RSA_PSS_SELF_SIGNED_SHA512).expect("could not parse certificate"); let res = x509.verify_signature(None); eprintln!("Verification: {:?}", res); assert!(res.is_ok()); }