rcgen-0.11.3/.cargo_vcs_info.json0000644000000001360000000000100122140ustar { "git": { "sha1": "471943bec6348a7ecfab1c17c72e3fdefbeaa52b" }, "path_in_vcs": "" }rcgen-0.11.3/.editorconfig000064400000000000000000000002441046102023000134610ustar 00000000000000# top-most EditorConfig file root = true [*] indent_style = tab tab_width = 4 end_of_line=lf charset=utf-8 trim_trailing_whitespace=true insert_final_newline=true rcgen-0.11.3/.github/dependabot.yml000064400000000000000000000001621046102023000151730ustar 00000000000000version: 2 updates: - package-ecosystem: github-actions directory: "/" schedule: interval: weekly rcgen-0.11.3/.github/workflows/ci.yml000064400000000000000000000102031046102023000155130ustar 00000000000000name: ci on: push: pull_request: merge_group: schedule: - cron: '0 18 * * *' env: RUSTFLAGS: -D warnings jobs: rustfmt: name: Format runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v4 with: persist-credentials: false - name: Install rust toolchain uses: dtolnay/rust-toolchain@stable with: components: rustfmt - name: Check formatting run: cargo fmt -- --check clippy: name: Clippy runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v4 with: persist-credentials: false - name: Install rust toolchain uses: dtolnay/rust-toolchain@stable with: components: clippy - run: cargo clippy --all-features - run: cargo clippy --no-default-features rustdoc: name: Documentation runs-on: ubuntu-latest strategy: matrix: toolchain: [stable, beta, nightly, stable 7 months ago] steps: - name: Checkout sources uses: actions/checkout@v4 with: persist-credentials: false - name: Install rust toolchain uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.toolchain }} - name: cargo doc (all features) run: cargo doc --all --all-features --document-private-items env: RUSTDOCFLAGS: ${{ matrix.rust_channel == 'nightly' && '-Dwarnings --cfg=docsrs' || '-Dwarnings' }} build-windows: runs-on: windows-latest env: # botan doesn't build on windows if the source is # on a different drive than the artifacts # https://github.com/randombit/botan-rs/issues/82 BOTAN_CONFIGURE_LINK_METHOD: copy steps: - uses: actions/checkout@v4 with: persist-credentials: false - uses: egor-tensin/vs-shell@v2 with: arch: amd64 - name: Install Rust uses: dtolnay/rust-toolchain@master with: toolchain: stable - run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append - run: vcpkg install openssl:x64-windows-static-md - name: Run cargo check --all run: cargo check --all - name: Run the tests run: cargo test --all - name: Run the tests with x509-parser enabled run: cargo test --verbose --features x509-parser - name: Run the tests with no default features enabled run: cargo test --verbose --no-default-features build: strategy: matrix: os: [macOS-latest, ubuntu-latest] toolchain: [stable, beta, nightly, stable 7 months ago] exclude: - os: macOS-latest toolchain: beta - os: macOS-latest toolchain: nightly - os: macOS-latest toolchain: stable 7 months ago runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 with: persist-credentials: false - name: Install Rust uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.toolchain }} - name: Run cargo check --all run: cargo check --all - name: Run the tests run: cargo test --all - name: Run the tests with x509-parser enabled run: cargo test --verbose --features x509-parser - name: Run the tests with no default features enabled run: cargo test --verbose --no-default-features coverage: name: Measure coverage runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v4 with: persist-credentials: false - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - name: Install rust toolchain uses: dtolnay/rust-toolchain@stable with: components: llvm-tools - name: Measure coverage run: cargo llvm-cov --all-features --lcov --output-path ./lcov.info - name: Report to codecov.io uses: codecov/codecov-action@v3 with: token: ${{ secrets.CODECOV_TOKEN }} files: ./lcov.info fail_ci_if_error: false verbose: true rcgen-0.11.3/.gitignore000064400000000000000000000000401046102023000127660ustar 00000000000000/target **/*.rs.bk certs/ .idea rcgen-0.11.3/.rustfmt.toml000064400000000000000000000000631046102023000134620ustar 00000000000000hard_tabs = true match_block_trailing_comma = true rcgen-0.11.3/CHANGELOG.md000064400000000000000000000172461046102023000126270ustar 00000000000000 # Changes ## Release 0.11.3 - October 1, 2023 - Fix for import errors building without the optional `pem` feature. ## Release 0.11.2 - September 21, 2023 - `rcgen` has joined the umbrella of the [rustls](https://github.com/rustls) organization. - Support for retrieving signature algorithm from `KeyPair`s. Contributed by [tindzk](https://github.com/tindzk). - Fix for writing certificate signing requests (CSRs) with custom extensions from parameters without subject alternative names. - Support for certificate CRL distribution points extension. - Corrected OID for `ExtendedKeyUsagePurpose::Any`. Contributed by [jgallagher](https://github.com/jgallagher). - Support for creating certificate revocation lists (CRLs). ## Release 0.11.1 - June 17, 2023 - Make botan a dev-dependency again. Contributed by [mbrubeck](https://github.com/mbrubeck). ## Release 0.11.0 - June 15, 2023 - Parse IP-address subject alternative names. Contributed by [iamjpotts](https://github.com/iamjpotts). - Emit platform-apropriate line endings. Contributed by [frjonsen](https://github.com/frjonsen). - Support larger serial numbers. Contributed by [andrenth](https://github.com/andrenth). - Parse more certificate parameters. Contributed by [andrenth](https://github.com/andrenth). - Output `SanType::IpAddress` when calling `CertificateParams::new` or `generate_simple_self_signed`. Contributed by [rukai](https://github.com/rukai). - Update pem to 2.0. Contributed by [koushiro](https://github.com/koushiro). ## Release 0.10.0 - September 29, 2022 - Update x509-parser to 0.14. - Increase minimum supported Rust version to 1.58.1. - Update edition to 2021. - Change `IsCa` enum to have `NoCa` and `ExplicitNoCa` and `Ca(...)`. Contributed by [doraneko94](https://github.com/doraneko94). ## Release 0.9.4 - September 28, 2022 * yanked due to breaking API changes, see 0.10.0 instead. ## Release 0.9.3 - July 16, 2022 - Add a `KeyPair::serialized_der` function. Contributed by [jean-airoldie](https://github.com/jean-airoldie). ## Release 0.9.2 - February 21, 2022 - Update x509-parser to 0.13. Contributed by [matze](https://github.com/matze). ## Release 0.9.1 - February 9, 2022 - Change edition to 2018 in order to support Rust 1.53.0. ## Release 0.9.0 - February 2, 2022 - Add RemoteKeyError for usage by remote keys. - Support non utf8 strings. Contributed by [omjadas](https://github.com/omjadas). - Switch from chrono to time. Contributed by [connec](https://github.com/connec). - Update edition to 2021. ## Release 0.8.14 - October 14, 2021 - Update pem to 1.0. - Update x509-parser to 0.12. ## Release 0.8.13 - August 22, 2021 - Bugfix release to make Certificate `Send` and `Sync` again. ## Release 0.8.12 - August 22, 2021 - Use public key as default serial number. Contributed by [jpastuszek](https://github.com/jpastuszek). - Add support for `PKCS_RSA_SHA512` and `PKCS_RSA_SHA384` signature algorithms. - Add support for the keyUsage extension. Contributed by [jaredwolff](https://github.com/jaredwolff). - Ability to use remote keys. Contributed by [daxpedda](https://github.com/daxpedda). ## Release 0.8.11 - April 28, 2021 - Add getters for the criticality, content, and `oid_components` of a `CustomExtension` - Update yasna to 0.4 ## Release 0.8.10 - April 15, 2021 - Implement some additional traits for some of the types. Contributed by [zurborg](https://github.com/zurborg). - Adoption of intra-doc-links - Addition of the ability to zero key pairs. Contributed by [didier-wenzek](https://github.com/didier-wenzek). ## Release 0.8.9 - December 4, 2020 - Switch CI to Github Actions. - Strip nanos from `DateTime` as well. Contributed by [@trevor-crypto](https://github.com/trevor-crypto). ## Release 0.8.7 - December 1, 2020 - Turn `botan` back into a dev-dependency. Contributed by [@nthuemmel](https://github.com/nthuemmel). - Fix signing when CA uses different signing algorithm . Contributed by [@nthuemmel](https://github.com/nthuemmel). ## Release 0.8.6 - December 1, 2020 - Add `KeyPair::from_der` - Add botan based test to the testsuite - Update x509-parser to 0.9. Contributed by [@djc](https://github.com/djc). - Ability to create certificates from CSRs. Contributed by [@djc](https://github.com/djc). ## Release 0.8.5 - June 29, 2020 - Add some more `DnType`s: `OrganizationalUnitName`, `LocalityName`, `StateOrProvinceName` - Add `remove` function to `DistinguishedName` - Add ability to specify `NameConstraints` ## Release 0.8.4 - June 5, 2020 - Improve spec compliance in the `notBefore`/`notAfter` fields generated by using `UTCTime` if needed ## Release 0.8.3 - May 24, 2020 - Fix regression of `0.8.1` that generated standards non compliant CSRs and broke Go toolchain parsers. Contributed by [@thomastaylor312](https://github.com/thomastaylor312). ## Release 0.8.2 - May 18, 2020 - Disable `chrono` default features to get rid of time crate - Improve `openssl` tests to do a full handshake with the generated cert ## Release 0.8.1 - April 2, 2020 - Fix non-standard-compliant SubjectKeyIdentifier X.509v3 extension format - BasicConstraints X.509v3 extension is now marked as critical - Use RFC 7093 to calculate calculate subject key identifiers - Add option to insert AuthorityKeyIdentifier X.509v3 extension into non-self-signed certificates - Update to x509-parser 0.7 ## Release 0.8.0 - March 12, 2020 - Update to pem 0.7 - Correct number of nanoseconds per second. Contributed by [@samlich](https://github.com/samlich). - Adoption of the `non_exhaustive` feature in the API ## Release 0.7.0 - September 14, 2019 - Bugfix release for ip address subject alternative names. Turns out they aren't CIDR subnets after all :) ## Release 0.6.0 - September 12, 2019 - Support for email and cidr subnet (ip address) subject alternative names - Support for the extended key usage extension ## Release 0.5.1 - August 19, 2019 - Update to x509-parser 0.6 ## Release 0.5.0 - July 19, 2019 - Update to ring 0.16 and webpki 0.21 - Update to x509-parser 0.5 - Expose an API to get the raw public key of a key pair ## Release 0.4.1 - June 28, 2019 - Allow inspection of `DistinguishedName` via iterators and get functions - Fix a bug in `is_compatible` not saying false. Contributed by [@fzgregor](https://github.com/fzgregor). - Extend the public interface of `KeyPair`. Contributed by [@fzgregor](https://github.com/fzgregor). ## Release 0.4.0 - June 18, 2019 - Support for user supplied keypairs. Contributed by [@fzgregor](https://github.com/fzgregor). - Support for signing with user supplied CA certificates. Contributed by [@fzgregor](https://github.com/fzgregor). - Correct a bug with distinguished name serialization ([PR link](https://github.com/est31/rcgen/pull/13)). Contributed by [@fzgregor](https://github.com/fzgregor). - Addition of limited (no key generation) RSA support - Proper error handling with `Result` and our own Error type - Improvements of the testsuite ## Release 0.3.1 - June 6, 2019 - Ability to disable the dependency on the `pem` crate - Support for creating CSRs (Certificate Signing Requests). Contributed by [@djc](https://github.com/djc). - Ability to specify custom extensions for certificates - Ability to craft `acmeIdentifier` extensions - Update yasna to 0.3.0 ## Release 0.3.0 - May 18, 2019 - Support for CA certificate generation. Contributed by [@djc](https://github.com/djc). - Support for certificate signing. Contributed by [@djc](https://github.com/djc). - Support for ED25519 certificates - Support for SHA-384 certificates - API cleanups (Future proofing CertificateParams, public constant renames) ## Release 0.2.1 - April 26, 2019 - Updated to pem 0.6 ## Release 0.2 - January 10, 2019 - Updated to ring 0.14.0 ## Release 0.1 - January 7, 2019 Initial release. Ability to generate self-signed ECDSA keys. rcgen-0.11.3/Cargo.lock0000644000000531130000000000100101720ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "asn1-rs" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" dependencies = [ "asn1-rs-derive", "asn1-rs-impl", "displaydoc", "nom", "num-traits", "rusticata-macros", "thiserror", "time", ] [[package]] name = "asn1-rs-derive" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", "synstructure", ] [[package]] name = "asn1-rs-impl" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "base64ct" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bitflags" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "botan" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04675b6a2db12d8af38d5725134459b6972641165122bb72ee1d19c793b08bb1" dependencies = [ "botan-sys", ] [[package]] name = "botan-src" version = "0.30101.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aaaf0426c00371ec87f150daa40f3a8f149c6e8b9646d5fafa30888cacb2bc9f" [[package]] name = "botan-sys" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f49dde1b8ebd2996cc41c55c39f6ef8b54e38148d8973aeba0792b87b1621ca" dependencies = [ "botan-src", ] [[package]] name = "bumpalo" version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cc" version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "libc", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "const-oid" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "data-encoding" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "der" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ "const-oid", "pem-rfc7468", "zeroize", ] [[package]] name = "der-parser" version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" dependencies = [ "asn1-rs", "displaydoc", "nom", "num-bigint", "num-traits", "rusticata-macros", ] [[package]] name = "deranged" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "const-oid", "crypto-common", ] [[package]] name = "displaydoc" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", "syn 2.0.37", ] [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ "foreign-types-shared", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "getrandom" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "itoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" dependencies = [ "spin", ] [[package]] name = "libc" version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[package]] name = "libm" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[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.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-bigint-dig" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" dependencies = [ "byteorder", "lazy_static", "libm", "num-integer", "num-iter", "num-traits", "rand", "smallvec", "zeroize", ] [[package]] name = "num-integer" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", ] [[package]] name = "num-iter" version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", "libm", ] [[package]] name = "oid-registry" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" dependencies = [ "asn1-rs", ] [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl" version = "0.10.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", "once_cell", "openssl-macros", "openssl-sys", ] [[package]] name = "openssl-macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", "syn 2.0.37", ] [[package]] name = "openssl-sys" version = "0.9.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "pem" version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3163d2912b7c3b52d651a055f2c7eec9ba5cd22d26ef75b8dd3a59980b185923" dependencies = [ "base64", "serde", ] [[package]] name = "pem-rfc7468" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" dependencies = [ "base64ct", ] [[package]] name = "pkcs1" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ "der", "pkcs8", "spki", ] [[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", "spki", ] [[package]] name = "pkg-config" version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "rcgen" version = "0.11.3" dependencies = [ "botan", "openssl", "pem", "rand", "ring", "rsa", "rustls-webpki", "time", "x509-parser", "yasna", "zeroize", ] [[package]] name = "ring" version = "0.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" dependencies = [ "cc", "libc", "once_cell", "spin", "untrusted", "web-sys", "winapi", ] [[package]] name = "rsa" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ab43bb47d23c1a631b4b680199a45255dce26fa9ab2fa902581f624ff13e6a8" dependencies = [ "byteorder", "const-oid", "digest", "num-bigint-dig", "num-integer", "num-iter", "num-traits", "pkcs1", "pkcs8", "rand_core", "signature", "spki", "subtle", "zeroize", ] [[package]] name = "rusticata-macros" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" dependencies = [ "nom", ] [[package]] name = "rustls-webpki" version = "0.101.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" dependencies = [ "ring", "untrusted", ] [[package]] name = "serde" version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", "syn 2.0.37", ] [[package]] name = "signature" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" dependencies = [ "digest", "rand_core", ] [[package]] name = "smallvec" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spki" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" dependencies = [ "base64ct", "der", ] [[package]] name = "subtle" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "synstructure" version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", "unicode-xid", ] [[package]] name = "thiserror" version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", "syn 2.0.37", ] [[package]] name = "time" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" dependencies = [ "deranged", "itoa", "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.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-xid" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "untrusted" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn 2.0.37", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", "syn 2.0.37", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "x509-parser" version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7069fba5b66b9193bd2c5d3d4ff12b839118f6bcbef5328efafafb5395cf63da" dependencies = [ "asn1-rs", "data-encoding", "der-parser", "lazy_static", "nom", "oid-registry", "ring", "rusticata-macros", "thiserror", "time", ] [[package]] name = "yasna" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" dependencies = [ "time", ] [[package]] name = "zeroize" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" rcgen-0.11.3/Cargo.toml0000644000000035020000000000100102120ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "rcgen" version = "0.11.3" description = "Rust X.509 certificate generator" documentation = "https://docs.rs/rcgen" readme = "README.md" keywords = [ "mkcert", "ca", "certificate", ] license = "MIT OR Apache-2.0" repository = "https://github.com/est31/rcgen" [package.metadata.docs.rs] features = ["x509-parser"] [profile.dev.package.num-bigint-dig] opt-level = 3 [lib] name = "rcgen" path = "src/lib.rs" [[bin]] name = "rcgen" path = "src/main.rs" required-features = ["pem"] [[example]] name = "rsa-irc" required-features = ["pem"] [[example]] name = "rsa-irc-openssl" required-features = ["pem"] [dependencies.pem] version = "3.0.2" optional = true [dependencies.ring] version = "0.16" [dependencies.time] version = "0.3.6" default-features = false [dependencies.x509-parser] version = "0.15" features = ["verify"] optional = true [dependencies.yasna] version = "0.5.2" features = [ "time", "std", ] [dependencies.zeroize] version = "1.2" optional = true [dev-dependencies.botan] version = "0.10" features = ["vendored"] [dev-dependencies.openssl] version = "0.10" [dev-dependencies.rand] version = "0.8" [dev-dependencies.rsa] version = "0.9" [dev-dependencies.rustls-webpki] version = "0.101.0" features = ["std"] [dev-dependencies.x509-parser] version = "0.15" features = ["verify"] [features] default = ["pem"] rcgen-0.11.3/Cargo.toml.orig000064400000000000000000000025231046102023000136750ustar 00000000000000[package] name = "rcgen" version = "0.11.3" description = "Rust X.509 certificate generator" repository = "https://github.com/est31/rcgen" documentation = "https://docs.rs/rcgen" license = "MIT OR Apache-2.0" edition = "2021" readme = "README.md" keywords = ["mkcert", "ca", "certificate"] [lib] name = "rcgen" path = "src/lib.rs" [[bin]] name = "rcgen" path = "src/main.rs" required-features = ["pem"] [[example]] name = "rsa-irc" required-features = ["pem"] [[example]] name = "rsa-irc-openssl" required-features = ["pem"] [dependencies] yasna = { version = "0.5.2", features = ["time", "std"] } ring = "0.16" pem = { version = "3.0.2", optional = true } time = { version = "0.3.6", default-features = false } x509-parser = { version = "0.15", features = ["verify"], optional = true } zeroize = { version = "1.2", optional = true } [features] default = ["pem"] [package.metadata.docs.rs] features = ["x509-parser"] [dev-dependencies] openssl = "0.10" x509-parser = { version = "0.15", features = ["verify"] } rustls-webpki = { version = "0.101.0", features = ["std"] } botan = { version = "0.10", features = ["vendored"] } rand = "0.8" rsa = "0.9" # This greatly speeds up rsa key generation times # (only applies to the dev-dependency because cargo # ignores profile overrides for non leaf packages) [profile.dev.package.num-bigint-dig] opt-level = 3 rcgen-0.11.3/LICENSE000064400000000000000000000266261046102023000120250ustar 00000000000000Copyright (c) 2019-2022 est31 and contributors Licensed under MIT or Apache License 2.0, at your option. The full list of contributors can be obtained by looking at the VCS log (originally, this crate was git versioned, there you can do "git shortlog -sn" for this task). MIT License ----------- The MIT License (MIT) Copyright (c) 2019-2022 est31 and contributors 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. Apache License, version 2.0 --------------------------- 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 rcgen-0.11.3/README.md000064400000000000000000000035051046102023000122660ustar 00000000000000# rcgen [![docs](https://docs.rs/rcgen/badge.svg)](https://docs.rs/rcgen) [![crates.io](https://img.shields.io/crates/v/rcgen.svg)](https://crates.io/crates/rcgen) [![dependency status](https://deps.rs/repo/github/est31/rcgen/status.svg)](https://deps.rs/repo/github/est31/rcgen) Simple Rust library to generate X.509 certificates. ```Rust extern crate rcgen; use rcgen::generate_simple_self_signed; let subject_alt_names = vec!["hello.world.example".to_string(), "localhost".to_string()]; let cert = generate_simple_self_signed(subject_alt_names).unwrap(); // The certificate is now valid for localhost and the domain "hello.world.example" println!("{}", cert.serialize_pem().unwrap()); println!("{}", cert.serialize_private_key_pem()); ``` ## Trying it out with openssl You can do this: ``` cargo run openssl x509 -in certs/cert.pem -text -noout ``` For debugging, pasting the PEM formatted text to [this](https://lapo.it/asn1js/) service is very useful. ## Trying it out with quinn You can use rcgen together with the [quinn](https://github.com/djc/quinn) crate. The whole set of commands is: ``` cargo run cd ../quinn cargo run --example server -- --cert ../rcgen/certs/cert.pem --key ../rcgen/certs/key.pem ./ cargo run --example client -- --ca ../rcgen/certs/cert.der https://localhost:4433/README.md ``` ## MSRV The MSRV policy is to strive for supporting 7-month old Rust versions. ### License [license]: #license This crate is distributed under the terms of both the MIT license and the Apache License (Version 2.0), at your option. See [LICENSE](LICENSE) for details. #### License of your contributions 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. rcgen-0.11.3/codecov.yml000064400000000000000000000000411046102023000131440ustar 00000000000000comment: require_changes: true rcgen-0.11.3/examples/rsa-irc-openssl.rs000064400000000000000000000027051046102023000162150ustar 00000000000000#![allow(clippy::complexity, clippy::style, clippy::pedantic)] fn main() -> Result<(), Box> { use rcgen::{date_time_ymd, Certificate, CertificateParams, DistinguishedName}; use std::fs; let mut params: CertificateParams = Default::default(); params.not_before = date_time_ymd(2021, 05, 19); params.not_after = date_time_ymd(4096, 01, 01); params.distinguished_name = DistinguishedName::new(); params.alg = &rcgen::PKCS_RSA_SHA256; let pkey: openssl::pkey::PKey<_> = openssl::rsa::Rsa::generate(2048)?.try_into()?; let key_pair_pem = String::from_utf8(pkey.private_key_to_pem_pkcs8()?)?; let key_pair = rcgen::KeyPair::from_pem(&key_pair_pem)?; params.key_pair = Some(key_pair); let cert = Certificate::from_params(params)?; let pem_serialized = cert.serialize_pem()?; let pem = pem::parse(&pem_serialized)?; let der_serialized = pem.contents(); let hash = ring::digest::digest(&ring::digest::SHA512, &der_serialized); let hash_hex: String = hash.as_ref().iter().map(|b| format!("{b:02x}")).collect(); println!("sha-512 fingerprint: {hash_hex}"); println!("{pem_serialized}"); println!("{}", cert.serialize_private_key_pem()); std::fs::create_dir_all("certs/")?; fs::write("certs/cert.pem", &pem_serialized.as_bytes())?; fs::write("certs/cert.der", &der_serialized)?; fs::write( "certs/key.pem", &cert.serialize_private_key_pem().as_bytes(), )?; fs::write("certs/key.der", &cert.serialize_private_key_der())?; Ok(()) } rcgen-0.11.3/examples/rsa-irc.rs000064400000000000000000000030471046102023000145340ustar 00000000000000#![allow(clippy::complexity, clippy::style, clippy::pedantic)] fn main() -> Result<(), Box> { use rand::rngs::OsRng; use rsa::pkcs8::EncodePrivateKey; use rsa::RsaPrivateKey; use rcgen::{date_time_ymd, Certificate, CertificateParams, DistinguishedName}; use std::fs; let mut params: CertificateParams = Default::default(); params.not_before = date_time_ymd(2021, 05, 19); params.not_after = date_time_ymd(4096, 01, 01); params.distinguished_name = DistinguishedName::new(); params.alg = &rcgen::PKCS_RSA_SHA256; let mut rng = OsRng; let bits = 2048; let private_key = RsaPrivateKey::new(&mut rng, bits)?; let private_key_der = private_key.to_pkcs8_der()?; let key_pair = rcgen::KeyPair::try_from(private_key_der.as_bytes()).unwrap(); params.key_pair = Some(key_pair); let cert = Certificate::from_params(params)?; let pem_serialized = cert.serialize_pem()?; let pem = pem::parse(&pem_serialized)?; let der_serialized = pem.contents(); let hash = ring::digest::digest(&ring::digest::SHA512, &der_serialized); let hash_hex: String = hash.as_ref().iter().map(|b| format!("{:02x}", b)).collect(); println!("sha-512 fingerprint: {hash_hex}"); println!("{pem_serialized}"); println!("{}", cert.serialize_private_key_pem()); std::fs::create_dir_all("certs/")?; fs::write("certs/cert.pem", &pem_serialized.as_bytes())?; fs::write("certs/cert.der", &der_serialized)?; fs::write( "certs/key.pem", &cert.serialize_private_key_pem().as_bytes(), )?; fs::write("certs/key.der", &cert.serialize_private_key_der())?; Ok(()) } rcgen-0.11.3/src/crl.rs000064400000000000000000000334561046102023000127340ustar 00000000000000#[cfg(feature = "pem")] use pem::Pem; use time::OffsetDateTime; use yasna::DERWriter; use yasna::Tag; use crate::oid::*; #[cfg(feature = "pem")] use crate::ENCODE_CONFIG; use crate::{ write_distinguished_name, write_dt_utc_or_generalized, write_x509_authority_key_identifier, write_x509_extension, }; use crate::{ Certificate, KeyIdMethod, KeyUsagePurpose, RcgenError, SerialNumber, SignatureAlgorithm, }; /// A certificate revocation list (CRL) /// /// ## Example /// /// ``` /// extern crate rcgen; /// use rcgen::*; /// /// # fn main () { /// // Generate a CRL issuer. /// let mut issuer_params = CertificateParams::new(vec!["crl.issuer.example.com".to_string()]); /// issuer_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); /// issuer_params.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::DigitalSignature, KeyUsagePurpose::CrlSign]; /// let issuer = Certificate::from_params(issuer_params).unwrap(); /// // Describe a revoked certificate. /// let revoked_cert = RevokedCertParams{ /// serial_number: SerialNumber::from(9999), /// revocation_time: date_time_ymd(2024, 06, 17), /// reason_code: Some(RevocationReason::KeyCompromise), /// invalidity_date: None, /// }; /// // Create a CRL signed by the issuer, revoking revoked_cert. /// let crl = CertificateRevocationListParams{ /// this_update: date_time_ymd(2023, 06, 17), /// next_update: date_time_ymd(2024, 06, 17), /// crl_number: SerialNumber::from(1234), /// issuing_distribution_point: None, /// revoked_certs: vec![revoked_cert], /// alg: &PKCS_ECDSA_P256_SHA256, /// key_identifier_method: KeyIdMethod::Sha256, /// }; /// let crl = CertificateRevocationList::from_params(crl).unwrap(); ///# } pub struct CertificateRevocationList { params: CertificateRevocationListParams, } impl CertificateRevocationList { /// Generates a new certificate revocation list (CRL) from the given parameters. pub fn from_params(params: CertificateRevocationListParams) -> Result { if params.next_update.le(¶ms.this_update) { return Err(RcgenError::InvalidCrlNextUpdate); } Ok(Self { params }) } /// Returns the certificate revocation list (CRL) parameters. pub fn get_params(&self) -> &CertificateRevocationListParams { &self.params } /// Serializes the certificate revocation list (CRL) in binary DER format, signed with /// the issuing certificate authority's key. pub fn serialize_der_with_signer(&self, ca: &Certificate) -> Result, RcgenError> { if !ca.params.key_usages.is_empty() && !ca.params.key_usages.contains(&KeyUsagePurpose::CrlSign) { return Err(RcgenError::IssuerNotCrlSigner); } self.params.serialize_der_with_signer(ca) } /// Serializes the certificate revocation list (CRL) in ASCII PEM format, signed with /// the issuing certificate authority's key. #[cfg(feature = "pem")] pub fn serialize_pem_with_signer(&self, ca: &Certificate) -> Result { let contents = self.serialize_der_with_signer(ca)?; let p = Pem::new("X509 CRL", contents); Ok(pem::encode_config(&p, ENCODE_CONFIG)) } } /// A certificate revocation list (CRL) distribution point, to be included in a certificate's /// [distribution points extension](https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.13) or /// a CRL's [issuing distribution point extension](https://datatracker.ietf.org/doc/html/rfc5280#section-5.2.5) #[derive(Debug, PartialEq, Eq, Clone)] pub struct CrlDistributionPoint { /// One or more URI distribution point names, indicating a place the current CRL can /// be retrieved. When present, SHOULD include at least one LDAP or HTTP URI. pub uris: Vec, } impl CrlDistributionPoint { pub(crate) fn write_der(&self, writer: DERWriter) { // DistributionPoint SEQUENCE writer.write_sequence(|writer| { write_distribution_point_name_uris(writer.next(), &self.uris); }); } } fn write_distribution_point_name_uris<'a>( writer: DERWriter, uris: impl IntoIterator, ) { // distributionPoint DistributionPointName writer.write_tagged_implicit(Tag::context(0), |writer| { writer.write_sequence(|writer| { // fullName GeneralNames writer .next() .write_tagged_implicit(Tag::context(0), |writer| { // GeneralNames writer.write_sequence(|writer| { for uri in uris.into_iter() { // uniformResourceIdentifier [6] IA5String, writer .next() .write_tagged_implicit(Tag::context(6), |writer| { writer.write_ia5_string(uri) }); } }) }); }); }); } /// Identifies the reason a certificate was revoked. /// See RFC 5280 §5.3.1[^1] /// /// [^1] #[derive(Debug, Clone, Copy, Eq, PartialEq)] #[allow(missing_docs)] // Not much to add above the code name. pub enum RevocationReason { Unspecified = 0, KeyCompromise = 1, CaCompromise = 2, AffiliationChanged = 3, Superseded = 4, CessationOfOperation = 5, CertificateHold = 6, // 7 is not defined. RemoveFromCrl = 8, PrivilegeWithdrawn = 9, AaCompromise = 10, } /// Parameters used for certificate revocation list (CRL) generation pub struct CertificateRevocationListParams { /// Issue date of the CRL. pub this_update: OffsetDateTime, /// The date by which the next CRL will be issued. pub next_update: OffsetDateTime, /// A monotonically increasing sequence number for a given CRL scope and issuer. pub crl_number: SerialNumber, /// An optional CRL extension identifying the CRL distribution point and scope for a /// particular CRL as described in RFC 5280 Section 5.2.5[^1]. /// /// [^1]: pub issuing_distribution_point: Option, /// A list of zero or more parameters describing revoked certificates included in the CRL. pub revoked_certs: Vec, /// Signature algorithm to use when signing the serialized CRL. pub alg: &'static SignatureAlgorithm, /// Method to generate key identifiers from public keys /// /// Defaults to SHA-256. pub key_identifier_method: KeyIdMethod, } impl CertificateRevocationListParams { fn serialize_der_with_signer(&self, ca: &Certificate) -> Result, RcgenError> { yasna::try_construct_der(|writer| { // https://www.rfc-editor.org/rfc/rfc5280#section-5.1 writer.write_sequence(|writer| { let tbs_cert_list_serialized = yasna::try_construct_der(|writer| { self.write_crl(writer, ca)?; Ok::<(), RcgenError>(()) })?; // Write tbsCertList writer.next().write_der(&tbs_cert_list_serialized); // Write signatureAlgorithm ca.params.alg.write_alg_ident(writer.next()); // Write signature ca.key_pair.sign(&tbs_cert_list_serialized, writer.next())?; Ok(()) }) }) } fn write_crl(&self, writer: DERWriter, ca: &Certificate) -> Result<(), RcgenError> { writer.write_sequence(|writer| { // Write CRL version. // RFC 5280 §5.1.2.1: // This optional field describes the version of the encoded CRL. When // extensions are used, as required by this profile, this field MUST be // present and MUST specify version 2 (the integer value is 1). // RFC 5280 §5.2: // Conforming CRL issuers are REQUIRED to include the authority key // identifier (Section 5.2.1) and the CRL number (Section 5.2.3) // extensions in all CRLs issued. writer.next().write_u8(1); // Write algorithm identifier. // RFC 5280 §5.1.2.2: // This field MUST contain the same algorithm identifier as the // signatureAlgorithm field in the sequence CertificateList ca.params.alg.write_alg_ident(writer.next()); // Write issuer. // RFC 5280 §5.1.2.3: // The issuer field MUST contain a non-empty X.500 distinguished name (DN). write_distinguished_name(writer.next(), &ca.params.distinguished_name); // Write thisUpdate date. // RFC 5280 §5.1.2.4: // This field indicates the issue date of this CRL. thisUpdate may be // encoded as UTCTime or GeneralizedTime. write_dt_utc_or_generalized(writer.next(), self.this_update); // Write nextUpdate date. // While OPTIONAL in the ASN.1 module, RFC 5280 §5.1.2.5 says: // Conforming CRL issuers MUST include the nextUpdate field in all CRLs. write_dt_utc_or_generalized(writer.next(), self.next_update); // Write revokedCertificates. // RFC 5280 §5.1.2.6: // When there are no revoked certificates, the revoked certificates list // MUST be absent if !self.revoked_certs.is_empty() { writer.next().write_sequence(|writer| { for revoked_cert in &self.revoked_certs { revoked_cert.write_der(writer.next()); } }); } // Write crlExtensions. // RFC 5280 §5.1.2.7: // This field may only appear if the version is 2 (Section 5.1.2.1). If // present, this field is a sequence of one or more CRL extensions. // RFC 5280 §5.2: // Conforming CRL issuers are REQUIRED to include the authority key // identifier (Section 5.2.1) and the CRL number (Section 5.2.3) // extensions in all CRLs issued. writer.next().write_tagged(Tag::context(0), |writer| { writer.write_sequence(|writer| { // Write authority key identifier. write_x509_authority_key_identifier(writer.next(), ca); // Write CRL number. write_x509_extension(writer.next(), OID_CRL_NUMBER, false, |writer| { writer.write_bigint_bytes(self.crl_number.as_ref(), true); }); // Write issuing distribution point (if present). if let Some(issuing_distribution_point) = &self.issuing_distribution_point { write_x509_extension( writer.next(), OID_CRL_ISSUING_DISTRIBUTION_POINT, true, |writer| { issuing_distribution_point.write_der(writer); }, ); } }); }); Ok(()) }) } } /// A certificate revocation list (CRL) issuing distribution point, to be included in a CRL's /// [issuing distribution point extension](https://datatracker.ietf.org/doc/html/rfc5280#section-5.2.5). pub struct CrlIssuingDistributionPoint { /// The CRL's distribution point, containing a sequence of URIs the CRL can be retrieved from. pub distribution_point: CrlDistributionPoint, /// An optional description of the CRL's scope. If omitted, the CRL may contain /// both user certs and CA certs. pub scope: Option, } impl CrlIssuingDistributionPoint { fn write_der(&self, writer: DERWriter) { // IssuingDistributionPoint SEQUENCE writer.write_sequence(|writer| { // distributionPoint [0] DistributionPointName OPTIONAL write_distribution_point_name_uris(writer.next(), &self.distribution_point.uris); // -- at most one of onlyContainsUserCerts, onlyContainsCACerts, // -- and onlyContainsAttributeCerts may be set to TRUE. if let Some(scope) = self.scope { let tag = match scope { // onlyContainsUserCerts [1] BOOLEAN DEFAULT FALSE, CrlScope::UserCertsOnly => Tag::context(1), // onlyContainsCACerts [2] BOOLEAN DEFAULT FALSE, CrlScope::CaCertsOnly => Tag::context(2), }; writer.next().write_tagged_implicit(tag, |writer| { writer.write_bool(true); }); } }); } } /// Describes the scope of a CRL for an issuing distribution point extension. #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum CrlScope { /// The CRL contains only end-entity user certificates. UserCertsOnly, /// The CRL contains only CA certificates. CaCertsOnly, } /// Parameters used for describing a revoked certificate included in a [`CertificateRevocationList`]. pub struct RevokedCertParams { /// Serial number identifying the revoked certificate. pub serial_number: SerialNumber, /// The date at which the CA processed the revocation. pub revocation_time: OffsetDateTime, /// An optional reason code identifying why the certificate was revoked. pub reason_code: Option, /// An optional field describing the date on which it was known or suspected that the /// private key was compromised or the certificate otherwise became invalid. This date /// may be earlier than the [`RevokedCertParams::revocation_time`]. pub invalidity_date: Option, } impl RevokedCertParams { fn write_der(&self, writer: DERWriter) { writer.write_sequence(|writer| { // Write serial number. // RFC 5280 §4.1.2.2: // Certificate users MUST be able to handle serialNumber values up to 20 octets. // Conforming CAs MUST NOT use serialNumber values longer than 20 octets. // // Note: Non-conforming CAs may issue certificates with serial numbers // that are negative or zero. Certificate users SHOULD be prepared to // gracefully handle such certificates. writer .next() .write_bigint_bytes(self.serial_number.as_ref(), true); // Write revocation date. write_dt_utc_or_generalized(writer.next(), self.revocation_time); // Write extensions if applicable. // RFC 5280 §5.3: // Support for the CRL entry extensions defined in this specification is // optional for conforming CRL issuers and applications. However, CRL // issuers SHOULD include reason codes (Section 5.3.1) and invalidity // dates (Section 5.3.2) whenever this information is available. let has_reason_code = matches!(self.reason_code, Some(reason) if reason != RevocationReason::Unspecified); let has_invalidity_date = self.invalidity_date.is_some(); if has_reason_code || has_invalidity_date { writer.next().write_sequence(|writer| { // Write reason code if present. self.reason_code.map(|reason_code| { write_x509_extension(writer.next(), OID_CRL_REASONS, false, |writer| { writer.write_enum(reason_code as i64); }); }); // Write invalidity date if present. self.invalidity_date.map(|invalidity_date| { write_x509_extension( writer.next(), OID_CRL_INVALIDITY_DATE, false, |writer| { write_dt_utc_or_generalized(writer, invalidity_date); }, ) }); }); } }) } } rcgen-0.11.3/src/csr.rs000064400000000000000000000066261046102023000127420ustar 00000000000000#[cfg(feature = "x509-parser")] use crate::{DistinguishedName, SanType}; #[cfg(feature = "pem")] use pem::Pem; use std::hash::Hash; use crate::{Certificate, CertificateParams, PublicKeyData, RcgenError, SignatureAlgorithm}; /// A public key, extracted from a CSR #[derive(Debug, PartialEq, Eq, Hash)] pub struct PublicKey { raw: Vec, alg: &'static SignatureAlgorithm, } impl PublicKeyData for PublicKey { fn alg(&self) -> &SignatureAlgorithm { self.alg } fn raw_bytes(&self) -> &[u8] { &self.raw } } /// Data for a certificate signing request pub struct CertificateSigningRequest { /// Parameters for the certificate to be signed. pub params: CertificateParams, /// Public key to include in the certificate signing request. pub public_key: PublicKey, } impl CertificateSigningRequest { /// Parse a certificate signing request from the ASCII PEM format /// /// See [`from_der`](Self::from_der) for more details. #[cfg(all(feature = "pem", feature = "x509-parser"))] pub fn from_pem(pem_str: &str) -> Result { let csr = pem::parse(pem_str).or(Err(RcgenError::CouldNotParseCertificationRequest))?; Self::from_der(csr.contents()) } /// Parse a certificate signing request from DER-encoded bytes /// /// Currently, this only supports the `Subject Alternative Name` extension. /// On encountering other extensions, this function will return an error. #[cfg(feature = "x509-parser")] pub fn from_der(csr: &[u8]) -> Result { use x509_parser::prelude::FromDer; let csr = x509_parser::certification_request::X509CertificationRequest::from_der(csr) .map_err(|_| RcgenError::CouldNotParseCertificationRequest)? .1; csr.verify_signature() .map_err(|_| RcgenError::RingUnspecified)?; let alg_oid = csr .signature_algorithm .algorithm .iter() .ok_or(RcgenError::CouldNotParseCertificationRequest)? .collect::>(); let alg = SignatureAlgorithm::from_oid(&alg_oid)?; let info = &csr.certification_request_info; let mut params = CertificateParams::default(); params.alg = alg; params.distinguished_name = DistinguishedName::from_name(&info.subject)?; let raw = info.subject_pki.subject_public_key.data.to_vec(); if let Some(extensions) = csr.requested_extensions() { for ext in extensions { match ext { x509_parser::extensions::ParsedExtension::SubjectAlternativeName(san) => { for name in &san.general_names { params .subject_alt_names .push(SanType::try_from_general(name)?); } }, _ => return Err(RcgenError::UnsupportedExtension), } } } // Not yet handled: // * is_ca // * extended_key_usages // * name_constraints // and any other extensions. Ok(Self { params, public_key: PublicKey { alg, raw }, }) } /// Serializes the requested certificate, signed with another certificate's key, in binary DER format pub fn serialize_der_with_signer(&self, ca: &Certificate) -> Result, RcgenError> { self.params.serialize_der_with_signer(&self.public_key, ca) } /// Serializes the requested certificate, signed with another certificate's key, to the ASCII PEM format #[cfg(feature = "pem")] pub fn serialize_pem_with_signer(&self, ca: &Certificate) -> Result { let contents = self .params .serialize_der_with_signer(&self.public_key, ca)?; let p = Pem::new("CERTIFICATE", contents); Ok(pem::encode_config(&p, crate::ENCODE_CONFIG)) } } rcgen-0.11.3/src/error.rs000064400000000000000000000073101046102023000132730ustar 00000000000000use std::error::Error; use std::fmt; #[derive(Debug, PartialEq, Eq)] #[non_exhaustive] /// The error type of the rcgen crate pub enum RcgenError { /// The given certificate couldn't be parsed CouldNotParseCertificate, /// The given certificate signing request couldn't be parsed CouldNotParseCertificationRequest, /// The given key pair couldn't be parsed CouldNotParseKeyPair, #[cfg(feature = "x509-parser")] /// Invalid subject alternative name type InvalidNameType, /// An IP address was provided as a byte array, but the byte array was an invalid length. InvalidIpAddressOctetLength(usize), /// There is no support for generating /// keys for the given algorithm KeyGenerationUnavailable, #[cfg(feature = "x509-parser")] /// Unsupported extension requested in CSR UnsupportedExtension, /// The requested signature algorithm is not supported UnsupportedSignatureAlgorithm, /// Unspecified `ring` error RingUnspecified, /// The `ring` library rejected the key upon loading RingKeyRejected(&'static str), /// The provided certificate's signature algorithm /// is incompatible with the given key pair CertificateKeyPairMismatch, /// Time conversion related errors Time, #[cfg(feature = "pem")] /// Error from the pem crate PemError(pem::PemError), /// Error generated by a remote key operation RemoteKeyError, /// Unsupported field when generating a CSR UnsupportedInCsr, /// Invalid certificate revocation list (CRL) next update. InvalidCrlNextUpdate, /// CRL issuer specifies Key Usages that don't include cRLSign. IssuerNotCrlSigner, } impl fmt::Display for RcgenError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::RcgenError::*; match self { CouldNotParseCertificate => write!(f, "Could not parse certificate")?, CouldNotParseCertificationRequest => write!( f, "Could not parse certificate signing \ request" )?, CouldNotParseKeyPair => write!(f, "Could not parse key pair")?, #[cfg(feature = "x509-parser")] InvalidNameType => write!(f, "Invalid subject alternative name type")?, InvalidIpAddressOctetLength(actual) => { write!(f, "Invalid IP address octet length of {actual} bytes")? }, KeyGenerationUnavailable => write!( f, "There is no support for generating \ keys for the given algorithm" )?, UnsupportedSignatureAlgorithm => write!( f, "The requested signature algorithm \ is not supported" )?, #[cfg(feature = "x509-parser")] UnsupportedExtension => write!(f, "Unsupported extension requested in CSR")?, RingUnspecified => write!(f, "Unspecified ring error")?, RingKeyRejected(e) => write!(f, "Key rejected by ring: {}", e)?, CertificateKeyPairMismatch => write!( f, "The provided certificate's signature \ algorithm is incompatible with the given key pair" )?, Time => write!(f, "Time error")?, RemoteKeyError => write!(f, "Remote key error")?, #[cfg(feature = "pem")] PemError(e) => write!(f, "PEM error: {}", e)?, UnsupportedInCsr => write!(f, "Certificate parameter unsupported in CSR")?, InvalidCrlNextUpdate => write!(f, "Invalid CRL next update parameter")?, IssuerNotCrlSigner => write!( f, "CRL issuer must specify no key usage, or key usage including cRLSign" )?, }; Ok(()) } } impl Error for RcgenError {} impl From for RcgenError { fn from(_unspecified: ring::error::Unspecified) -> Self { RcgenError::RingUnspecified } } impl From for RcgenError { fn from(err: ring::error::KeyRejected) -> Self { RcgenError::RingKeyRejected(err.description_()) } } #[cfg(feature = "pem")] impl From for RcgenError { fn from(e: pem::PemError) -> Self { RcgenError::PemError(e) } } rcgen-0.11.3/src/key_pair.rs000064400000000000000000000304411046102023000137460ustar 00000000000000#[cfg(feature = "pem")] use pem::Pem; use ring::rand::SystemRandom; use ring::signature::KeyPair as RingKeyPair; use ring::signature::{self, EcdsaKeyPair, Ed25519KeyPair, RsaEncoding, RsaKeyPair}; use std::convert::TryFrom; use std::fmt; use yasna::DERWriter; use crate::sign_algo::algo::*; use crate::sign_algo::SignAlgo; #[cfg(feature = "pem")] use crate::ENCODE_CONFIG; use crate::{RcgenError, SignatureAlgorithm}; /// A key pair vairant #[allow(clippy::large_enum_variant)] pub(crate) enum KeyPairKind { /// A Ecdsa key pair Ec(EcdsaKeyPair), /// A Ed25519 key pair Ed(Ed25519KeyPair), /// A RSA key pair Rsa(RsaKeyPair, &'static dyn RsaEncoding), /// A remote key pair Remote(Box), } impl fmt::Debug for KeyPairKind { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Ec(key_pair) => write!(f, "{:?}", key_pair), Self::Ed(key_pair) => write!(f, "{:?}", key_pair), Self::Rsa(key_pair, _) => write!(f, "{:?}", key_pair), Self::Remote(_) => write!(f, "Box"), } } } /// A key pair used to sign certificates and CSRs /// /// Note that ring, the underlying library to handle RSA keys /// requires them to be in a special format, meaning that /// `openssl genrsa` doesn't work. See ring's [documentation](ring::signature::RsaKeyPair::from_pkcs8) /// for how to generate RSA keys in the wanted format /// and conversion between the formats. #[derive(Debug)] pub struct KeyPair { pub(crate) kind: KeyPairKind, pub(crate) alg: &'static SignatureAlgorithm, pub(crate) serialized_der: Vec, } impl KeyPair { /// Parses the key pair from the DER format /// /// Equivalent to using the [`TryFrom`] implementation. pub fn from_der(der: &[u8]) -> Result { Ok(der.try_into()?) } /// Returns the key pair's signature algorithm pub fn algorithm(&self) -> &'static SignatureAlgorithm { self.alg } /// Parses the key pair from the ASCII PEM format #[cfg(feature = "pem")] pub fn from_pem(pem_str: &str) -> Result { let private_key = pem::parse(pem_str)?; let private_key_der: &[_] = private_key.contents(); Ok(private_key_der.try_into()?) } /// Obtains the key pair from a raw public key and a remote private key pub fn from_remote(key_pair: Box) -> Result { Ok(Self { alg: key_pair.algorithm(), kind: KeyPairKind::Remote(key_pair), serialized_der: Vec::new(), }) } /// Obtains the key pair from a DER formatted key /// using the specified [`SignatureAlgorithm`] /// /// Same as [from_pem_and_sign_algo](Self::from_pem_and_sign_algo). #[cfg(feature = "pem")] pub fn from_pem_and_sign_algo( pem_str: &str, alg: &'static SignatureAlgorithm, ) -> Result { let private_key = pem::parse(pem_str)?; let private_key_der: &[_] = private_key.contents(); Ok(Self::from_der_and_sign_algo(private_key_der, alg)?) } /// Obtains the key pair from a DER formatted key /// using the specified [`SignatureAlgorithm`] /// /// Usually, calling this function is not neccessary and you can just call /// [`from_der`](Self::from_der) instead. That function will try to figure /// out a fitting [`SignatureAlgorithm`] for the given /// key pair. However, sometimes multiple signature algorithms fit for the /// same der key. In that instance, you can use this function to precisely /// specify the `SignatureAlgorithm`. pub fn from_der_and_sign_algo( pkcs8: &[u8], alg: &'static SignatureAlgorithm, ) -> Result { let pkcs8_vec = pkcs8.to_vec(); let kind = if alg == &PKCS_ED25519 { KeyPairKind::Ed(Ed25519KeyPair::from_pkcs8_maybe_unchecked(pkcs8)?) } else if alg == &PKCS_ECDSA_P256_SHA256 { KeyPairKind::Ec(EcdsaKeyPair::from_pkcs8( &signature::ECDSA_P256_SHA256_ASN1_SIGNING, pkcs8, )?) } else if alg == &PKCS_ECDSA_P384_SHA384 { KeyPairKind::Ec(EcdsaKeyPair::from_pkcs8( &signature::ECDSA_P384_SHA384_ASN1_SIGNING, pkcs8, )?) } else if alg == &PKCS_RSA_SHA256 { let rsakp = RsaKeyPair::from_pkcs8(pkcs8)?; KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA256) } else if alg == &PKCS_RSA_SHA384 { let rsakp = RsaKeyPair::from_pkcs8(pkcs8)?; KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA384) } else if alg == &PKCS_RSA_SHA512 { let rsakp = RsaKeyPair::from_pkcs8(pkcs8)?; KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA512) } else if alg == &PKCS_RSA_PSS_SHA256 { let rsakp = RsaKeyPair::from_pkcs8(pkcs8)?; KeyPairKind::Rsa(rsakp, &signature::RSA_PSS_SHA256) } else { panic!("Unknown SignatureAlgorithm specified!"); }; Ok(KeyPair { kind, alg, serialized_der: pkcs8_vec, }) } pub(crate) fn from_raw( pkcs8: &[u8], ) -> Result<(KeyPairKind, &'static SignatureAlgorithm), RcgenError> { let (kind, alg) = if let Ok(edkp) = Ed25519KeyPair::from_pkcs8_maybe_unchecked(pkcs8) { (KeyPairKind::Ed(edkp), &PKCS_ED25519) } else if let Ok(eckp) = EcdsaKeyPair::from_pkcs8(&signature::ECDSA_P256_SHA256_ASN1_SIGNING, pkcs8) { (KeyPairKind::Ec(eckp), &PKCS_ECDSA_P256_SHA256) } else if let Ok(eckp) = EcdsaKeyPair::from_pkcs8(&signature::ECDSA_P384_SHA384_ASN1_SIGNING, pkcs8) { (KeyPairKind::Ec(eckp), &PKCS_ECDSA_P384_SHA384) } else if let Ok(rsakp) = RsaKeyPair::from_pkcs8(pkcs8) { ( KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA256), &PKCS_RSA_SHA256, ) } else { return Err(RcgenError::CouldNotParseKeyPair); }; Ok((kind, alg)) } } /// A private key that is not directly accessible, but can be used to sign messages /// /// Trait objects based on this trait can be passed to the [`KeyPair::from_remote`] function for generating certificates /// from a remote and raw private key, for example an HSM. pub trait RemoteKeyPair { /// Returns the public key of this key pair in the binary format as in [`KeyPair::public_key_raw`] fn public_key(&self) -> &[u8]; /// Signs `msg` using the selected algorithm fn sign(&self, msg: &[u8]) -> Result, RcgenError>; /// Reveals the algorithm to be used when calling `sign()` fn algorithm(&self) -> &'static SignatureAlgorithm; } impl TryFrom<&[u8]> for KeyPair { type Error = RcgenError; fn try_from(pkcs8: &[u8]) -> Result { let (kind, alg) = KeyPair::from_raw(pkcs8)?; Ok(KeyPair { kind, alg, serialized_der: pkcs8.to_vec(), }) } } impl TryFrom> for KeyPair { type Error = RcgenError; fn try_from(pkcs8: Vec) -> Result { let (kind, alg) = KeyPair::from_raw(pkcs8.as_slice())?; Ok(KeyPair { kind, alg, serialized_der: pkcs8, }) } } impl KeyPair { /// Generate a new random key pair for the specified signature algorithm pub fn generate(alg: &'static SignatureAlgorithm) -> Result { let system_random = SystemRandom::new(); match alg.sign_alg { SignAlgo::EcDsa(sign_alg) => { let key_pair_doc = EcdsaKeyPair::generate_pkcs8(sign_alg, &system_random)?; let key_pair_serialized = key_pair_doc.as_ref().to_vec(); let key_pair = EcdsaKeyPair::from_pkcs8(&sign_alg, &&key_pair_doc.as_ref()).unwrap(); Ok(KeyPair { kind: KeyPairKind::Ec(key_pair), alg, serialized_der: key_pair_serialized, }) }, SignAlgo::EdDsa(_sign_alg) => { let key_pair_doc = Ed25519KeyPair::generate_pkcs8(&system_random)?; let key_pair_serialized = key_pair_doc.as_ref().to_vec(); let key_pair = Ed25519KeyPair::from_pkcs8(&&key_pair_doc.as_ref()).unwrap(); Ok(KeyPair { kind: KeyPairKind::Ed(key_pair), alg, serialized_der: key_pair_serialized, }) }, // Ring doesn't have RSA key generation yet: // https://github.com/briansmith/ring/issues/219 // https://github.com/briansmith/ring/pull/733 SignAlgo::Rsa() => Err(RcgenError::KeyGenerationUnavailable), } } /// Get the raw public key of this key pair /// /// The key is in raw format, as how [`ring::signature::KeyPair::public_key`] /// would output, and how [`ring::signature::UnparsedPublicKey::verify`] /// would accept. pub fn public_key_raw(&self) -> &[u8] { self.raw_bytes() } /// Check if this key pair can be used with the given signature algorithm pub fn is_compatible(&self, signature_algorithm: &SignatureAlgorithm) -> bool { self.alg == signature_algorithm } /// Returns (possibly multiple) compatible [`SignatureAlgorithm`]'s /// that the key can be used with pub fn compatible_algs(&self) -> impl Iterator { std::iter::once(self.alg) } pub(crate) fn sign(&self, msg: &[u8], writer: DERWriter) -> Result<(), RcgenError> { match &self.kind { KeyPairKind::Ec(kp) => { let system_random = SystemRandom::new(); let signature = kp.sign(&system_random, msg)?; let sig = &signature.as_ref(); writer.write_bitvec_bytes(&sig, &sig.len() * 8); }, KeyPairKind::Ed(kp) => { let signature = kp.sign(msg); let sig = &signature.as_ref(); writer.write_bitvec_bytes(&sig, &sig.len() * 8); }, KeyPairKind::Rsa(kp, padding_alg) => { let system_random = SystemRandom::new(); let mut signature = vec![0; kp.public_modulus_len()]; kp.sign(*padding_alg, &system_random, msg, &mut signature)?; let sig = &signature.as_ref(); writer.write_bitvec_bytes(&sig, &sig.len() * 8); }, KeyPairKind::Remote(kp) => { let signature = kp.sign(msg)?; writer.write_bitvec_bytes(&signature, &signature.len() * 8); }, } Ok(()) } /// Return the key pair's public key in DER format /// /// The key is formatted according to the SubjectPublicKeyInfo struct of /// X.509. /// See [RFC 5280 section 4.1](https://tools.ietf.org/html/rfc5280#section-4.1). pub fn public_key_der(&self) -> Vec { yasna::construct_der(|writer| self.serialize_public_key_der(writer)) } /// Return the key pair's public key in PEM format /// /// The returned string can be interpreted with `openssl pkey --inform PEM -pubout -pubin -text` #[cfg(feature = "pem")] pub fn public_key_pem(&self) -> String { let contents = self.public_key_der(); let p = Pem::new("PUBLIC KEY", contents); pem::encode_config(&p, ENCODE_CONFIG) } /// Serializes the key pair (including the private key) in PKCS#8 format in DER /// /// Panics if called on a remote key pair. pub fn serialize_der(&self) -> Vec { if let KeyPairKind::Remote(_) = self.kind { panic!("Serializing a remote key pair is not supported") } self.serialized_der.clone() } /// Returns a reference to the serialized key pair (including the private key) /// in PKCS#8 format in DER /// /// Panics if called on a remote key pair. pub fn serialized_der(&self) -> &[u8] { if let KeyPairKind::Remote(_) = self.kind { panic!("Serializing a remote key pair is not supported") } &self.serialized_der } /// Access the remote key pair if it is a remote one pub fn as_remote(&self) -> Option<&(dyn RemoteKeyPair + Send + Sync)> { if let KeyPairKind::Remote(remote) = &self.kind { Some(remote.as_ref()) } else { None } } /// Serializes the key pair (including the private key) in PKCS#8 format in PEM #[cfg(feature = "pem")] pub fn serialize_pem(&self) -> String { let contents = self.serialize_der(); let p = Pem::new("PRIVATE KEY", contents); pem::encode_config(&p, ENCODE_CONFIG) } } impl PublicKeyData for KeyPair { fn alg(&self) -> &SignatureAlgorithm { self.alg } fn raw_bytes(&self) -> &[u8] { match &self.kind { KeyPairKind::Ec(kp) => kp.public_key().as_ref(), KeyPairKind::Ed(kp) => kp.public_key().as_ref(), KeyPairKind::Rsa(kp, _) => kp.public_key().as_ref(), KeyPairKind::Remote(kp) => kp.public_key(), } } } pub(crate) trait PublicKeyData { fn alg(&self) -> &SignatureAlgorithm; fn raw_bytes(&self) -> &[u8]; fn serialize_public_key_der(&self, writer: DERWriter) { writer.write_sequence(|writer| { self.alg().write_oids_sign_alg(writer.next()); let pk = self.raw_bytes(); writer.next().write_bitvec_bytes(&pk, pk.len() * 8); }) } } #[cfg(test)] mod test { use super::*; use ring::rand::SystemRandom; use ring::signature::{EcdsaKeyPair, ECDSA_P256_SHA256_FIXED_SIGNING}; #[test] fn test_algorithm() { let rng = SystemRandom::new(); let pkcs8 = EcdsaKeyPair::generate_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, &rng).unwrap(); let der = pkcs8.as_ref().to_vec(); let key_pair = KeyPair::from_der(&der).unwrap(); assert_eq!(key_pair.algorithm(), &PKCS_ECDSA_P256_SHA256); } } rcgen-0.11.3/src/lib.rs000064400000000000000000001642571046102023000127260ustar 00000000000000/*! Rust X.509 certificate generation utility This crate provides a way to generate self signed X.509 certificates. The most simple way of using this crate is by calling the [`generate_simple_self_signed`] function. For more customization abilities, we provide the lower level [`Certificate::from_params`] function. */ #![cfg_attr( feature = "pem", doc = r##" ## Example ``` extern crate rcgen; use rcgen::generate_simple_self_signed; # fn main () { // Generate a certificate that's valid for "localhost" and "hello.world.example" let subject_alt_names = vec!["hello.world.example".to_string(), "localhost".to_string()]; let cert = generate_simple_self_signed(subject_alt_names).unwrap(); println!("{}", cert.serialize_pem().unwrap()); println!("{}", cert.serialize_private_key_pem()); # } ```"## )] #![forbid(unsafe_code)] #![forbid(non_ascii_idents)] #![deny(missing_docs)] #![allow(clippy::complexity, clippy::style, clippy::pedantic)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #[cfg(feature = "pem")] use pem::Pem; use ring::digest; use std::collections::HashMap; use std::convert::TryFrom; use std::fmt; use std::hash::Hash; use std::net::IpAddr; #[cfg(feature = "x509-parser")] use std::net::{Ipv4Addr, Ipv6Addr}; use std::str::FromStr; use time::{Date, Month, OffsetDateTime, PrimitiveDateTime, Time}; use yasna::models::ObjectIdentifier; use yasna::models::{GeneralizedTime, UTCTime}; use yasna::tags::{TAG_BMPSTRING, TAG_TELETEXSTRING, TAG_UNIVERSALSTRING}; use yasna::DERWriter; use yasna::Tag; pub use crate::crl::{ CertificateRevocationList, CertificateRevocationListParams, CrlDistributionPoint, CrlIssuingDistributionPoint, CrlScope, RevocationReason, RevokedCertParams, }; pub use crate::csr::{CertificateSigningRequest, PublicKey}; pub use crate::error::RcgenError; use crate::key_pair::PublicKeyData; pub use crate::key_pair::{KeyPair, RemoteKeyPair}; use crate::oid::*; pub use crate::sign_algo::algo::*; pub use crate::sign_algo::SignatureAlgorithm; /// A self signed certificate together with signing keys pub struct Certificate { params: CertificateParams, key_pair: KeyPair, } /** KISS function to generate a self signed certificate Given a set of domain names you want your certificate to be valid for, this function fills in the other generation parameters with reasonable defaults and generates a self signed certificate as output. */ #[cfg_attr( feature = "pem", doc = r##" ## Example ``` extern crate rcgen; use rcgen::generate_simple_self_signed; # fn main () { let subject_alt_names :&[_] = &["hello.world.example".to_string(), "localhost".to_string()]; let cert = generate_simple_self_signed(subject_alt_names).unwrap(); // The certificate is now valid for localhost and the domain "hello.world.example" println!("{}", cert.serialize_pem().unwrap()); println!("{}", cert.serialize_private_key_pem()); # } ``` "## )] pub fn generate_simple_self_signed( subject_alt_names: impl Into>, ) -> Result { Certificate::from_params(CertificateParams::new(subject_alt_names)) } // https://tools.ietf.org/html/rfc5280#section-4.1.1 mod crl; mod csr; mod error; mod key_pair; mod oid; mod sign_algo; // Example certs usable as reference: // Uses ECDSA: https://crt.sh/?asn1=607203242 #[cfg(feature = "pem")] const ENCODE_CONFIG: pem::EncodeConfig = { let line_ending = match cfg!(target_family = "windows") { true => pem::LineEnding::CRLF, false => pem::LineEnding::LF, }; pem::EncodeConfig::new().set_line_ending(line_ending) }; #[derive(Debug, PartialEq, Eq, Hash, Clone)] #[allow(missing_docs)] #[non_exhaustive] /// The type of subject alt name pub enum SanType { /// Also known as E-Mail address Rfc822Name(String), DnsName(String), URI(String), IpAddress(IpAddr), } #[cfg(feature = "x509-parser")] fn ip_addr_from_octets(octets: &[u8]) -> Result { if let Ok(ipv6_octets) = <&[u8; 16]>::try_from(octets) { Ok(Ipv6Addr::from(*ipv6_octets).into()) } else if let Ok(ipv4_octets) = <&[u8; 4]>::try_from(octets) { Ok(Ipv4Addr::from(*ipv4_octets).into()) } else { Err(RcgenError::InvalidIpAddressOctetLength(octets.len())) } } impl SanType { #[cfg(feature = "x509-parser")] fn try_from_general( name: &x509_parser::extensions::GeneralName<'_>, ) -> Result { Ok(match name { x509_parser::extensions::GeneralName::RFC822Name(name) => { SanType::Rfc822Name((*name).into()) }, x509_parser::extensions::GeneralName::DNSName(name) => SanType::DnsName((*name).into()), x509_parser::extensions::GeneralName::URI(name) => SanType::URI((*name).into()), x509_parser::extensions::GeneralName::IPAddress(octets) => { SanType::IpAddress(ip_addr_from_octets(octets)?) }, _ => return Err(RcgenError::InvalidNameType), }) } fn tag(&self) -> u64 { // Defined in the GeneralName list in // https://tools.ietf.org/html/rfc5280#page-38 const TAG_RFC822_NAME: u64 = 1; const TAG_DNS_NAME: u64 = 2; const TAG_URI: u64 = 6; const TAG_IP_ADDRESS: u64 = 7; match self { SanType::Rfc822Name(_name) => TAG_RFC822_NAME, SanType::DnsName(_name) => TAG_DNS_NAME, SanType::URI(_name) => TAG_URI, SanType::IpAddress(_addr) => TAG_IP_ADDRESS, } } } #[derive(Debug, PartialEq, Eq, Clone)] #[allow(missing_docs)] #[non_exhaustive] /// General Subtree type. /// /// This type has similarities to the [`SanType`] enum but is not equal. /// For example, `GeneralSubtree` has CIDR subnets for ip addresses /// while [`SanType`] has IP addresses. pub enum GeneralSubtree { /// Also known as E-Mail address Rfc822Name(String), DnsName(String), DirectoryName(DistinguishedName), IpAddress(CidrSubnet), } impl GeneralSubtree { fn tag(&self) -> u64 { // Defined in the GeneralName list in // https://tools.ietf.org/html/rfc5280#page-38 const TAG_RFC822_NAME: u64 = 1; const TAG_DNS_NAME: u64 = 2; const TAG_DIRECTORY_NAME: u64 = 4; const TAG_IP_ADDRESS: u64 = 7; match self { GeneralSubtree::Rfc822Name(_name) => TAG_RFC822_NAME, GeneralSubtree::DnsName(_name) => TAG_DNS_NAME, GeneralSubtree::DirectoryName(_name) => TAG_DIRECTORY_NAME, GeneralSubtree::IpAddress(_addr) => TAG_IP_ADDRESS, } } } #[derive(Debug, PartialEq, Eq, Hash, Clone)] #[allow(missing_docs)] /// CIDR subnet, as per [RFC 4632](https://tools.ietf.org/html/rfc4632) /// /// You might know CIDR subnets better by their textual representation /// where they consist of an ip address followed by a slash and a prefix /// number, for example `192.168.99.0/24`. /// /// The first field in the enum is the address, the second is the mask. /// Both are specified in network byte order. pub enum CidrSubnet { V4([u8; 4], [u8; 4]), V6([u8; 16], [u8; 16]), } macro_rules! mask { ($t:ty, $d:expr) => {{ let v = <$t>::max_value(); let v = v.checked_shr($d as u32).unwrap_or(0); (!v).to_be_bytes() }}; } impl CidrSubnet { /// Obtains the CidrSubnet from the well-known /// addr/prefix notation. /// ``` /// # use std::str::FromStr; /// # use rcgen::CidrSubnet; /// // The "192.0.2.0/24" example from /// // https://tools.ietf.org/html/rfc5280#page-42 /// let subnet = CidrSubnet::from_str("192.0.2.0/24").unwrap(); /// assert_eq!(subnet, CidrSubnet::V4([0xC0, 0x00, 0x02, 0x00], [0xFF, 0xFF, 0xFF, 0x00])); /// ``` pub fn from_str(s: &str) -> Result { let mut iter = s.split('/'); if let (Some(addr_s), Some(prefix_s)) = (iter.next(), iter.next()) { let addr = IpAddr::from_str(addr_s).map_err(|_| ())?; let prefix = u8::from_str(prefix_s).map_err(|_| ())?; Ok(Self::from_addr_prefix(addr, prefix)) } else { Err(()) } } /// Obtains the CidrSubnet from an ip address /// as well as the specified prefix number. /// /// ``` /// # use std::net::IpAddr; /// # use std::str::FromStr; /// # use rcgen::CidrSubnet; /// // The "192.0.2.0/24" example from /// // https://tools.ietf.org/html/rfc5280#page-42 /// let addr = IpAddr::from_str("192.0.2.0").unwrap(); /// let subnet = CidrSubnet::from_addr_prefix(addr, 24); /// assert_eq!(subnet, CidrSubnet::V4([0xC0, 0x00, 0x02, 0x00], [0xFF, 0xFF, 0xFF, 0x00])); /// ``` pub fn from_addr_prefix(addr: IpAddr, prefix: u8) -> Self { match addr { IpAddr::V4(addr) => Self::from_v4_prefix(addr.octets(), prefix), IpAddr::V6(addr) => Self::from_v6_prefix(addr.octets(), prefix), } } /// Obtains the CidrSubnet from an IPv4 address in network byte order /// as well as the specified prefix. pub fn from_v4_prefix(addr: [u8; 4], prefix: u8) -> Self { CidrSubnet::V4(addr, mask!(u32, prefix)) } /// Obtains the CidrSubnet from an IPv6 address in network byte order /// as well as the specified prefix. pub fn from_v6_prefix(addr: [u8; 16], prefix: u8) -> Self { CidrSubnet::V6(addr, mask!(u128, prefix)) } fn to_bytes(&self) -> Vec { let mut res = Vec::new(); match self { CidrSubnet::V4(addr, mask) => { res.extend_from_slice(addr); res.extend_from_slice(mask); }, CidrSubnet::V6(addr, mask) => { res.extend_from_slice(addr); res.extend_from_slice(mask); }, } res } } #[derive(Debug, PartialEq, Eq, Hash, Clone)] #[non_exhaustive] /// The attribute type of a distinguished name entry pub enum DnType { /// X520countryName CountryName, /// X520LocalityName LocalityName, /// X520StateOrProvinceName StateOrProvinceName, /// X520OrganizationName OrganizationName, /// X520OrganizationalUnitName OrganizationalUnitName, /// X520CommonName CommonName, /// Custom distinguished name type CustomDnType(Vec), } impl DnType { fn to_oid(&self) -> ObjectIdentifier { let sl = match self { DnType::CountryName => OID_COUNTRY_NAME, DnType::LocalityName => OID_LOCALITY_NAME, DnType::StateOrProvinceName => OID_STATE_OR_PROVINCE_NAME, DnType::OrganizationName => OID_ORG_NAME, DnType::OrganizationalUnitName => OID_ORG_UNIT_NAME, DnType::CommonName => OID_COMMON_NAME, DnType::CustomDnType(ref oid) => oid.as_slice(), }; ObjectIdentifier::from_slice(sl) } /// Generate a DnType for the provided OID pub fn from_oid(slice: &[u64]) -> Self { match slice { OID_COUNTRY_NAME => DnType::CountryName, OID_LOCALITY_NAME => DnType::LocalityName, OID_STATE_OR_PROVINCE_NAME => DnType::StateOrProvinceName, OID_ORG_NAME => DnType::OrganizationName, OID_ORG_UNIT_NAME => DnType::OrganizationalUnitName, OID_COMMON_NAME => DnType::CommonName, oid => DnType::CustomDnType(oid.into()), } } } /// A distinguished name entry #[derive(Debug, PartialEq, Eq, Hash, Clone)] #[non_exhaustive] pub enum DnValue { /// A string of characters from the T.61 character set TeletexString(Vec), /// An ASCII string containing only A-Z, a-z, 0-9, '()+,-./:=? and `` PrintableString(String), /// A string encoded using UTF-32 UniversalString(Vec), /// A string encoded using UTF-8 Utf8String(String), /// A string encoded using UCS-2 BmpString(Vec), } impl From for DnValue where T: Into, { fn from(t: T) -> Self { DnValue::Utf8String(t.into()) } } #[derive(Debug, PartialEq, Eq, Clone)] /** Distinguished name used e.g. for the issuer and subject fields of a certificate A distinguished name is a set of (attribute type, attribute value) tuples. This datastructure keeps them ordered by insertion order. See also the RFC 5280 sections on the [issuer](https://tools.ietf.org/html/rfc5280#section-4.1.2.4) and [subject](https://tools.ietf.org/html/rfc5280#section-4.1.2.6) fields. */ pub struct DistinguishedName { entries: HashMap, order: Vec, } impl DistinguishedName { /// Creates a new, empty distinguished name pub fn new() -> Self { Self { entries: HashMap::new(), order: Vec::new(), } } /// Obtains the attribute value for the given attribute type pub fn get(&self, ty: &DnType) -> Option<&DnValue> { self.entries.get(ty) } /// Removes the attribute with the specified DnType /// /// Returns true when an actual removal happened, false /// when no attribute with the specified DnType was /// found. pub fn remove(&mut self, ty: DnType) -> bool { let removed = self.entries.remove(&ty).is_some(); if removed { self.order.retain(|ty_o| &ty != ty_o); } removed } /// Inserts or updates an attribute that consists of type and name /// /// ``` /// # use rcgen::{DistinguishedName, DnType, DnValue}; /// let mut dn = DistinguishedName::new(); /// dn.push(DnType::OrganizationName, "Crab widgits SE"); /// dn.push(DnType::CommonName, DnValue::PrintableString("Master Cert".to_string())); /// assert_eq!(dn.get(&DnType::OrganizationName), Some(&DnValue::Utf8String("Crab widgits SE".to_string()))); /// assert_eq!(dn.get(&DnType::CommonName), Some(&DnValue::PrintableString("Master Cert".to_string()))); /// ``` pub fn push(&mut self, ty: DnType, s: impl Into) { if !self.entries.contains_key(&ty) { self.order.push(ty.clone()); } self.entries.insert(ty, s.into()); } /// Iterate over the entries pub fn iter(&self) -> DistinguishedNameIterator<'_> { DistinguishedNameIterator { distinguished_name: self, iter: self.order.iter(), } } #[cfg(feature = "x509-parser")] fn from_name(name: &x509_parser::x509::X509Name) -> Result { use x509_parser::der_parser::asn1_rs::Tag; let mut dn = DistinguishedName::new(); for rdn in name.iter() { let mut rdn_iter = rdn.iter(); let dn_opt = rdn_iter.next(); let attr = if let Some(dn) = dn_opt { if rdn_iter.next().is_some() { // no support for distinguished names with more than one attribute return Err(RcgenError::CouldNotParseCertificate); } else { dn } } else { panic!("x509-parser distinguished name set is empty"); }; let attr_type_oid = attr .attr_type() .iter() .ok_or(RcgenError::CouldNotParseCertificate)?; let dn_type = DnType::from_oid(&attr_type_oid.collect::>()); let data = attr.attr_value().data; let dn_value = match attr.attr_value().header.tag() { Tag::T61String => DnValue::TeletexString(data.into()), Tag::PrintableString => { let data = std::str::from_utf8(data) .map_err(|_| RcgenError::CouldNotParseCertificate)?; DnValue::PrintableString(data.to_owned()) }, Tag::UniversalString => DnValue::UniversalString(data.into()), Tag::Utf8String => { let data = std::str::from_utf8(data) .map_err(|_| RcgenError::CouldNotParseCertificate)?; DnValue::Utf8String(data.to_owned()) }, Tag::BmpString => DnValue::BmpString(data.into()), _ => return Err(RcgenError::CouldNotParseCertificate), }; dn.push(dn_type, dn_value); } Ok(dn) } } /** Iterator over [`DistinguishedName`] entries */ pub struct DistinguishedNameIterator<'a> { distinguished_name: &'a DistinguishedName, iter: std::slice::Iter<'a, DnType>, } impl<'a> Iterator for DistinguishedNameIterator<'a> { type Item = (&'a DnType, &'a DnValue); fn next(&mut self) -> Option { self.iter .next() .and_then(|ty| self.distinguished_name.entries.get(ty).map(|v| (ty, v))) } } /// Parameters used for certificate generation #[allow(missing_docs)] #[non_exhaustive] pub struct CertificateParams { pub alg: &'static SignatureAlgorithm, pub not_before: OffsetDateTime, pub not_after: OffsetDateTime, pub serial_number: Option, pub subject_alt_names: Vec, pub distinguished_name: DistinguishedName, pub is_ca: IsCa, pub key_usages: Vec, pub extended_key_usages: Vec, pub name_constraints: Option, /// An optional list of certificate revocation list (CRL) distribution points as described /// in RFC 5280 Section 4.2.1.13[^1]. Each distribution point contains one or more URIs where /// an up-to-date CRL with scope including this certificate can be retrieved. /// /// [^1]: pub crl_distribution_points: Vec, pub custom_extensions: Vec, /// The certificate's key pair, a new random key pair will be generated if this is `None` pub key_pair: Option, /// If `true`, the 'Authority Key Identifier' extension will be added to the generated cert pub use_authority_key_identifier_extension: bool, /// Method to generate key identifiers from public keys /// /// Defaults to SHA-256. pub key_identifier_method: KeyIdMethod, } impl Default for CertificateParams { fn default() -> Self { // not_before and not_after set to reasonably long dates let not_before = date_time_ymd(1975, 01, 01); let not_after = date_time_ymd(4096, 01, 01); let mut distinguished_name = DistinguishedName::new(); distinguished_name.push(DnType::CommonName, "rcgen self signed cert"); CertificateParams { alg: &PKCS_ECDSA_P256_SHA256, not_before, not_after, serial_number: None, subject_alt_names: Vec::new(), distinguished_name, is_ca: IsCa::NoCa, key_usages: Vec::new(), extended_key_usages: Vec::new(), name_constraints: None, crl_distribution_points: Vec::new(), custom_extensions: Vec::new(), key_pair: None, use_authority_key_identifier_extension: false, key_identifier_method: KeyIdMethod::Sha256, } } } impl CertificateParams { /// Parses a ca certificate from the ASCII PEM format for signing /// /// See [`from_ca_cert_der`](Self::from_ca_cert_der) for more details. #[cfg(all(feature = "pem", feature = "x509-parser"))] pub fn from_ca_cert_pem(pem_str: &str, key_pair: KeyPair) -> Result { let certificate = pem::parse(pem_str).or(Err(RcgenError::CouldNotParseCertificate))?; Self::from_ca_cert_der(certificate.contents(), key_pair) } /// Parses a ca certificate from the DER format for signing /// /// This function is only of use if you have an existing ca certificate with /// which you want to sign a certificate newly generated by `rcgen` using the /// [`serialize_der_with_signer`](Certificate::serialize_der_with_signer) or /// [`serialize_pem_with_signer`](Certificate::serialize_pem_with_signer) /// functions. /// /// This function only extracts from the given ca cert the information /// needed for signing. Any information beyond that is not extracted /// and left to defaults. /// /// Will not check if certificate is a ca certificate! #[cfg(feature = "x509-parser")] pub fn from_ca_cert_der(ca_cert: &[u8], key_pair: KeyPair) -> Result { let (_remainder, x509) = x509_parser::parse_x509_certificate(ca_cert) .or(Err(RcgenError::CouldNotParseCertificate))?; let alg_oid = x509 .signature_algorithm .algorithm .iter() .ok_or(RcgenError::CouldNotParseCertificate)?; let alg = SignatureAlgorithm::from_oid(&alg_oid.collect::>())?; let dn = DistinguishedName::from_name(&x509.tbs_certificate.subject)?; let is_ca = Self::convert_x509_is_ca(&x509)?; let validity = x509.validity(); let subject_alt_names = Self::convert_x509_subject_alternative_name(&x509)?; let key_usages = Self::convert_x509_key_usages(&x509)?; let extended_key_usages = Self::convert_x509_extended_key_usages(&x509)?; let name_constraints = Self::convert_x509_name_constraints(&x509)?; let serial_number = Some(x509.serial.to_bytes_be().into()); Ok(CertificateParams { alg, is_ca, subject_alt_names, key_usages, extended_key_usages, name_constraints, serial_number, distinguished_name: dn, key_pair: Some(key_pair), not_before: validity.not_before.to_datetime(), not_after: validity.not_after.to_datetime(), ..Default::default() }) } #[cfg(feature = "x509-parser")] fn convert_x509_is_ca( x509: &x509_parser::certificate::X509Certificate<'_>, ) -> Result { use x509_parser::extensions::BasicConstraints as B; let basic_constraints = x509 .basic_constraints() .or(Err(RcgenError::CouldNotParseCertificate))? .map(|ext| ext.value); let is_ca = match basic_constraints { Some(B { ca: true, path_len_constraint: Some(n), }) if *n <= u8::MAX as u32 => IsCa::Ca(BasicConstraints::Constrained(*n as u8)), Some(B { ca: true, path_len_constraint: Some(_), }) => return Err(RcgenError::CouldNotParseCertificate), Some(B { ca: true, path_len_constraint: None, }) => IsCa::Ca(BasicConstraints::Unconstrained), Some(B { ca: false, .. }) => IsCa::ExplicitNoCa, None => IsCa::NoCa, }; Ok(is_ca) } #[cfg(feature = "x509-parser")] fn convert_x509_subject_alternative_name( x509: &x509_parser::certificate::X509Certificate<'_>, ) -> Result, RcgenError> { let sans = x509 .subject_alternative_name() .or(Err(RcgenError::CouldNotParseCertificate))? .map(|ext| &ext.value.general_names); if let Some(sans) = sans { let mut subject_alt_names = Vec::with_capacity(sans.len()); for san in sans { subject_alt_names.push(SanType::try_from_general(san)?); } Ok(subject_alt_names) } else { Ok(Vec::new()) } } #[cfg(feature = "x509-parser")] fn convert_x509_key_usages( x509: &x509_parser::certificate::X509Certificate<'_>, ) -> Result, RcgenError> { let key_usage = x509 .key_usage() .or(Err(RcgenError::CouldNotParseCertificate))? .map(|ext| ext.value); let mut key_usages = Vec::new(); if let Some(key_usage) = key_usage { if key_usage.digital_signature() { key_usages.push(KeyUsagePurpose::DigitalSignature); } if key_usage.non_repudiation() { key_usages.push(KeyUsagePurpose::ContentCommitment); } if key_usage.key_encipherment() { key_usages.push(KeyUsagePurpose::KeyEncipherment); } if key_usage.data_encipherment() { key_usages.push(KeyUsagePurpose::DataEncipherment); } if key_usage.key_agreement() { key_usages.push(KeyUsagePurpose::KeyAgreement); } if key_usage.key_cert_sign() { key_usages.push(KeyUsagePurpose::KeyCertSign); } if key_usage.crl_sign() { key_usages.push(KeyUsagePurpose::CrlSign); } if key_usage.encipher_only() { key_usages.push(KeyUsagePurpose::EncipherOnly); } if key_usage.decipher_only() { key_usages.push(KeyUsagePurpose::DecipherOnly); } } Ok(key_usages) } #[cfg(feature = "x509-parser")] fn convert_x509_extended_key_usages( x509: &x509_parser::certificate::X509Certificate<'_>, ) -> Result, RcgenError> { let extended_key_usage = x509 .extended_key_usage() .or(Err(RcgenError::CouldNotParseCertificate))? .map(|ext| ext.value); let mut extended_key_usages = Vec::new(); if let Some(extended_key_usage) = extended_key_usage { if extended_key_usage.any { extended_key_usages.push(ExtendedKeyUsagePurpose::Any); } if extended_key_usage.server_auth { extended_key_usages.push(ExtendedKeyUsagePurpose::ServerAuth); } if extended_key_usage.client_auth { extended_key_usages.push(ExtendedKeyUsagePurpose::ClientAuth); } if extended_key_usage.code_signing { extended_key_usages.push(ExtendedKeyUsagePurpose::CodeSigning); } if extended_key_usage.email_protection { extended_key_usages.push(ExtendedKeyUsagePurpose::EmailProtection); } if extended_key_usage.time_stamping { extended_key_usages.push(ExtendedKeyUsagePurpose::TimeStamping); } if extended_key_usage.ocsp_signing { extended_key_usages.push(ExtendedKeyUsagePurpose::OcspSigning); } } Ok(extended_key_usages) } #[cfg(feature = "x509-parser")] fn convert_x509_name_constraints( x509: &x509_parser::certificate::X509Certificate<'_>, ) -> Result, RcgenError> { let constraints = x509 .name_constraints() .or(Err(RcgenError::CouldNotParseCertificate))? .map(|ext| ext.value); if let Some(constraints) = constraints { let permitted_subtrees = if let Some(permitted) = &constraints.permitted_subtrees { Self::convert_x509_general_subtrees(&permitted)? } else { Vec::new() }; let excluded_subtrees = if let Some(excluded) = &constraints.excluded_subtrees { Self::convert_x509_general_subtrees(&excluded)? } else { Vec::new() }; let name_constraints = NameConstraints { permitted_subtrees, excluded_subtrees, }; Ok(Some(name_constraints)) } else { Ok(None) } } #[cfg(feature = "x509-parser")] fn convert_x509_general_subtrees( subtrees: &[x509_parser::extensions::GeneralSubtree<'_>], ) -> Result, RcgenError> { use x509_parser::extensions::GeneralName; let mut result = Vec::new(); for subtree in subtrees { let subtree = match &subtree.base { GeneralName::RFC822Name(s) => GeneralSubtree::Rfc822Name(s.to_string()), GeneralName::DNSName(s) => GeneralSubtree::DnsName(s.to_string()), GeneralName::DirectoryName(n) => { GeneralSubtree::DirectoryName(DistinguishedName::from_name(&n)?) }, GeneralName::IPAddress(bytes) if bytes.len() == 8 => { let addr: [u8; 4] = bytes[..4].try_into().unwrap(); let mask: [u8; 4] = bytes[4..].try_into().unwrap(); GeneralSubtree::IpAddress(CidrSubnet::V4(addr, mask)) }, GeneralName::IPAddress(bytes) if bytes.len() == 32 => { let addr: [u8; 16] = bytes[..16].try_into().unwrap(); let mask: [u8; 16] = bytes[16..].try_into().unwrap(); GeneralSubtree::IpAddress(CidrSubnet::V6(addr, mask)) }, _ => continue, }; result.push(subtree); } Ok(result) } fn write_subject_alt_names(&self, writer: DERWriter) { write_x509_extension(writer, OID_SUBJECT_ALT_NAME, false, |writer| { writer.write_sequence(|writer| { for san in self.subject_alt_names.iter() { writer.next().write_tagged_implicit( Tag::context(san.tag()), |writer| match san { SanType::Rfc822Name(name) | SanType::DnsName(name) | SanType::URI(name) => writer.write_ia5_string(name), SanType::IpAddress(IpAddr::V4(addr)) => { writer.write_bytes(&addr.octets()) }, SanType::IpAddress(IpAddr::V6(addr)) => { writer.write_bytes(&addr.octets()) }, }, ); } }); }); } fn write_request( &self, pub_key: &K, writer: DERWriter, ) -> Result<(), RcgenError> { // No .. pattern, we use this to ensure every field is used #[deny(unused)] let Self { alg, not_before, not_after, serial_number, subject_alt_names, distinguished_name, is_ca, key_usages, extended_key_usages, name_constraints, crl_distribution_points, custom_extensions, key_pair, use_authority_key_identifier_extension, key_identifier_method, } = self; // - alg and key_pair will be used by the caller // - not_before and not_after cannot be put in a CSR // - There might be a use case for specifying the key identifier // in the CSR, but in the current API it can't be distinguished // from the defaults so this is left for a later version if // needed. let _ = (alg, key_pair, not_before, not_after, key_identifier_method); if serial_number.is_some() || *is_ca != IsCa::NoCa || !key_usages.is_empty() || !extended_key_usages.is_empty() || name_constraints.is_some() || !crl_distribution_points.is_empty() || *use_authority_key_identifier_extension { return Err(RcgenError::UnsupportedInCsr); } writer.write_sequence(|writer| { // Write version writer.next().write_u8(0); // Write issuer writer.next().write_sequence(|writer| { for (ty, content) in distinguished_name.iter() { writer.next().write_set(|writer| { writer.next().write_sequence(|writer| { writer.next().write_oid(&ty.to_oid()); match content { DnValue::TeletexString(s) => writer .next() .write_tagged_implicit(TAG_TELETEXSTRING, |writer| { writer.write_bytes(s) }), DnValue::PrintableString(s) => { writer.next().write_printable_string(s) }, DnValue::UniversalString(s) => writer .next() .write_tagged_implicit(TAG_UNIVERSALSTRING, |writer| { writer.write_bytes(s) }), DnValue::Utf8String(s) => writer.next().write_utf8_string(s), DnValue::BmpString(s) => writer .next() .write_tagged_implicit(TAG_BMPSTRING, |writer| { writer.write_bytes(s) }), } }); }); } }); // Write subjectPublicKeyInfo pub_key.serialize_public_key_der(writer.next()); // Write extensions // According to the spec in RFC 2986, even if attributes are empty we need the empty attribute tag writer.next().write_tagged(Tag::context(0), |writer| { if !subject_alt_names.is_empty() || !custom_extensions.is_empty() { writer.write_sequence(|writer| { let oid = ObjectIdentifier::from_slice(OID_PKCS_9_AT_EXTENSION_REQUEST); writer.next().write_oid(&oid); writer.next().write_set(|writer| { writer.next().write_sequence(|writer| { // Write subject_alt_names self.write_subject_alt_names(writer.next()); // Write custom extensions for ext in custom_extensions { write_x509_extension( writer.next(), &ext.oid, ext.critical, |writer| writer.write_der(ext.content()), ); } }); }); }); } }); }); Ok(()) } fn write_cert( &self, writer: DERWriter, pub_key: &K, ca: &Certificate, ) -> Result<(), RcgenError> { writer.write_sequence(|writer| { // Write version writer.next().write_tagged(Tag::context(0), |writer| { writer.write_u8(2); }); // Write serialNumber if let Some(ref serial) = self.serial_number { writer.next().write_bigint_bytes(serial.as_ref(), true); } else { let hash = digest::digest(&digest::SHA256, pub_key.raw_bytes()); // RFC 5280 specifies at most 20 bytes for a serial number let sl = &hash.as_ref()[0..20]; writer.next().write_bigint_bytes(sl, true); }; // Write signature ca.params.alg.write_alg_ident(writer.next()); // Write issuer write_distinguished_name(writer.next(), &ca.params.distinguished_name); // Write validity writer.next().write_sequence(|writer| { // Not before write_dt_utc_or_generalized(writer.next(), self.not_before); // Not after write_dt_utc_or_generalized(writer.next(), self.not_after); Ok::<(), RcgenError>(()) })?; // Write subject write_distinguished_name(writer.next(), &self.distinguished_name); // Write subjectPublicKeyInfo pub_key.serialize_public_key_der(writer.next()); // write extensions let should_write_exts = self.use_authority_key_identifier_extension || !self.subject_alt_names.is_empty() || !self.extended_key_usages.is_empty() || self.name_constraints.iter().any(|c| !c.is_empty()) || matches!(self.is_ca, IsCa::ExplicitNoCa) || matches!(self.is_ca, IsCa::Ca(_)) || !self.custom_extensions.is_empty(); if should_write_exts { writer.next().write_tagged(Tag::context(3), |writer| { writer.write_sequence(|writer| { if self.use_authority_key_identifier_extension { write_x509_authority_key_identifier(writer.next(), ca) } // Write subject_alt_names if !self.subject_alt_names.is_empty() { self.write_subject_alt_names(writer.next()); } // Write standard key usage if !self.key_usages.is_empty() { write_x509_extension(writer.next(), OID_KEY_USAGE, true, |writer| { let mut bits: u16 = 0; for entry in self.key_usages.iter() { // Map the index to a value let index = match entry { KeyUsagePurpose::DigitalSignature => 0, KeyUsagePurpose::ContentCommitment => 1, KeyUsagePurpose::KeyEncipherment => 2, KeyUsagePurpose::DataEncipherment => 3, KeyUsagePurpose::KeyAgreement => 4, KeyUsagePurpose::KeyCertSign => 5, KeyUsagePurpose::CrlSign => 6, KeyUsagePurpose::EncipherOnly => 7, KeyUsagePurpose::DecipherOnly => 8, }; bits |= 1 << index; } // Compute the 1-based most significant bit let msb = 16 - bits.leading_zeros(); let nb = if msb <= 8 { 1 } else { 2 }; let bits = bits.reverse_bits().to_be_bytes(); // Finally take only the bytes != 0 let bits = &bits[..nb]; writer.write_bitvec_bytes(&bits, msb as usize) }); } // Write extended key usage if !self.extended_key_usages.is_empty() { write_x509_extension( writer.next(), OID_EXT_KEY_USAGE, false, |writer| { writer.write_sequence(|writer| { for usage in self.extended_key_usages.iter() { let oid = ObjectIdentifier::from_slice(usage.oid()); writer.next().write_oid(&oid); } }); }, ); } if let Some(name_constraints) = &self.name_constraints { // If both trees are empty, the extension must be omitted. if !name_constraints.is_empty() { write_x509_extension( writer.next(), OID_NAME_CONSTRAINTS, true, |writer| { writer.write_sequence(|writer| { if !name_constraints.permitted_subtrees.is_empty() { write_general_subtrees( writer.next(), 0, &name_constraints.permitted_subtrees, ); } if !name_constraints.excluded_subtrees.is_empty() { write_general_subtrees( writer.next(), 1, &name_constraints.excluded_subtrees, ); } }); }, ); } } if !self.crl_distribution_points.is_empty() { write_x509_extension( writer.next(), OID_CRL_DISTRIBUTION_POINTS, false, |writer| { writer.write_sequence(|writer| { for distribution_point in &self.crl_distribution_points { distribution_point.write_der(writer.next()); } }) }, ); } match self.is_ca { IsCa::Ca(ref constraint) => { // Write subject_key_identifier write_x509_extension( writer.next(), OID_SUBJECT_KEY_IDENTIFIER, false, |writer| { let key_identifier = self.key_identifier(pub_key); writer.write_bytes(key_identifier.as_ref()); }, ); // Write basic_constraints write_x509_extension( writer.next(), OID_BASIC_CONSTRAINTS, true, |writer| { writer.write_sequence(|writer| { writer.next().write_bool(true); // cA flag if let BasicConstraints::Constrained( path_len_constraint, ) = constraint { writer.next().write_u8(*path_len_constraint); } }); }, ); }, IsCa::ExplicitNoCa => { // Write subject_key_identifier write_x509_extension( writer.next(), OID_SUBJECT_KEY_IDENTIFIER, false, |writer| { let key_identifier = self.key_identifier(pub_key); writer.write_bytes(key_identifier.as_ref()); }, ); // Write basic_constraints write_x509_extension( writer.next(), OID_BASIC_CONSTRAINTS, true, |writer| { writer.write_sequence(|writer| { writer.next().write_bool(false); // cA flag }); }, ); }, IsCa::NoCa => {}, } // Write the custom extensions for ext in &self.custom_extensions { write_x509_extension(writer.next(), &ext.oid, ext.critical, |writer| { writer.write_der(ext.content()) }); } }); }); } Ok(()) }) } /// Calculates a subject key identifier for the certificate subject's public key. /// This key identifier is used in the SubjectKeyIdentifier X.509v3 extension. fn key_identifier(&self, pub_key: &K) -> Vec { // Decide which method from RFC 7093 to use let digest_method = match self.key_identifier_method { KeyIdMethod::Sha256 => &digest::SHA256, KeyIdMethod::Sha384 => &digest::SHA384, KeyIdMethod::Sha512 => &digest::SHA512, }; let digest = digest::digest(digest_method, pub_key.raw_bytes()); let truncated_digest = &digest.as_ref()[0..20]; truncated_digest.to_vec() } fn serialize_der_with_signer( &self, pub_key: &K, ca: &Certificate, ) -> Result, RcgenError> { yasna::try_construct_der(|writer| { writer.write_sequence(|writer| { let tbs_cert_list_serialized = yasna::try_construct_der(|writer| { self.write_cert(writer, pub_key, ca)?; Ok::<(), RcgenError>(()) })?; // Write tbsCertList writer.next().write_der(&tbs_cert_list_serialized); // Write signatureAlgorithm ca.params.alg.write_alg_ident(writer.next()); // Write signature ca.key_pair.sign(&tbs_cert_list_serialized, writer.next())?; Ok(()) }) }) } } /// Whether the certificate is allowed to sign other certificates #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub enum IsCa { /// The certificate can only sign itself NoCa, /// The certificate can only sign itself, adding the extension and `CA:FALSE` ExplicitNoCa, /// The certificate may be used to sign other certificates Ca(BasicConstraints), } /// The path length constraint (only relevant for CA certificates) /// /// Sets an optional upper limit on the length of the intermediate certificate chain /// length allowed for this CA certificate (not including the end entity certificate). #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub enum BasicConstraints { /// No constraint Unconstrained, /// Constrain to the contained number of intermediate certificates Constrained(u8), } impl CertificateParams { /// Generate certificate parameters with reasonable defaults pub fn new(subject_alt_names: impl Into>) -> Self { let subject_alt_names = subject_alt_names .into() .into_iter() .map(|s| match s.parse() { Ok(ip) => SanType::IpAddress(ip), Err(_) => SanType::DnsName(s), }) .collect::>(); CertificateParams { subject_alt_names, ..Default::default() } } } /// The [NameConstraints extension](https://tools.ietf.org/html/rfc5280#section-4.2.1.10) /// (only relevant for CA certificates) #[derive(Debug, PartialEq, Eq, Clone)] pub struct NameConstraints { /// A list of subtrees that the domain has to match. pub permitted_subtrees: Vec, /// A list of subtrees that the domain must not match. /// /// Any name matching an excluded subtree is invalid even if it also matches a permitted subtree. pub excluded_subtrees: Vec, } impl NameConstraints { fn is_empty(&self) -> bool { self.permitted_subtrees.is_empty() && self.excluded_subtrees.is_empty() } } /// One of the purposes contained in the [key usage](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3) extension #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub enum KeyUsagePurpose { /// digitalSignature DigitalSignature, /// contentCommitment / nonRepudiation ContentCommitment, /// keyEncipherment KeyEncipherment, /// dataEncipherment DataEncipherment, /// keyAgreement KeyAgreement, /// keyCertSign KeyCertSign, /// cRLSign CrlSign, /// encipherOnly EncipherOnly, /// decipherOnly DecipherOnly, } #[derive(Debug, PartialEq, Eq, Hash, Clone)] /// One of the purposes contained in the [extended key usage extension](https://tools.ietf.org/html/rfc5280#section-4.2.1.12) pub enum ExtendedKeyUsagePurpose { /// anyExtendedKeyUsage Any, /// id-kp-serverAuth ServerAuth, /// id-kp-clientAuth ClientAuth, /// id-kp-codeSigning CodeSigning, /// id-kp-emailProtection EmailProtection, /// id-kp-timeStamping TimeStamping, /// id-kp-OCSPSigning OcspSigning, } impl ExtendedKeyUsagePurpose { fn oid(&self) -> &'static [u64] { use ExtendedKeyUsagePurpose::*; match self { // anyExtendedKeyUsage Any => &[2, 5, 29, 37, 0], // id-kp-* ServerAuth => &[1, 3, 6, 1, 5, 5, 7, 3, 1], ClientAuth => &[1, 3, 6, 1, 5, 5, 7, 3, 2], CodeSigning => &[1, 3, 6, 1, 5, 5, 7, 3, 3], EmailProtection => &[1, 3, 6, 1, 5, 5, 7, 3, 4], TimeStamping => &[1, 3, 6, 1, 5, 5, 7, 3, 8], OcspSigning => &[1, 3, 6, 1, 5, 5, 7, 3, 9], } } } /// A custom extension of a certificate, as specified in /// [RFC 5280](https://tools.ietf.org/html/rfc5280#section-4.2) #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct CustomExtension { oid: Vec, critical: bool, /// The content must be DER-encoded content: Vec, } impl CustomExtension { /// Creates a new acmeIdentifier extension for ACME TLS-ALPN-01 /// as specified in [RFC 8737](https://tools.ietf.org/html/rfc8737#section-3) /// /// Panics if the passed `sha_digest` parameter doesn't hold 32 bytes (256 bits). pub fn new_acme_identifier(sha_digest: &[u8]) -> Self { assert_eq!(sha_digest.len(), 32, "wrong size of sha_digest"); let content = yasna::construct_der(|writer| { writer.write_bytes(sha_digest); }); Self { oid: OID_PE_ACME.to_owned(), critical: true, content, } } /// Create a new custom extension with the specified content pub fn from_oid_content(oid: &[u64], content: Vec) -> Self { Self { oid: oid.to_owned(), critical: false, content, } } /// Sets the criticality flag of the extension. pub fn set_criticality(&mut self, criticality: bool) { self.critical = criticality; } /// Obtains the criticality flag of the extension. pub fn criticality(&self) -> bool { self.critical } /// Obtains the content of the extension. pub fn content(&self) -> &[u8] { &self.content } /// Obtains the OID components of the extensions, as u64 pieces pub fn oid_components(&self) -> impl Iterator + '_ { self.oid.iter().copied() } } /// Method to generate key identifiers from public keys. /// /// This allows choice over methods to generate key identifiers /// as specified in RFC 7093 section 2. #[derive(Debug, PartialEq, Eq, Hash, Clone)] #[non_exhaustive] pub enum KeyIdMethod { /// RFC 7093 method 1 Sha256, /// RFC 7093 method 2 Sha384, /// RFC 7093 method 3 Sha512, } /// Helper to obtain an `OffsetDateTime` from year, month, day values /// /// The year, month, day values are assumed to be in UTC. /// /// This helper function serves two purposes: first, so that you don't /// have to import the time crate yourself in order to specify date /// information, second so that users don't have to type unproportionately /// long code just to generate an instance of [`OffsetDateTime`]. pub fn date_time_ymd(year: i32, month: u8, day: u8) -> OffsetDateTime { let month = Month::try_from(month).expect("out-of-range month"); let primitive_dt = PrimitiveDateTime::new( Date::from_calendar_date(year, month, day).expect("invalid or out-of-range date"), Time::MIDNIGHT, ); primitive_dt.assume_utc() } fn dt_strip_nanos(dt: OffsetDateTime) -> OffsetDateTime { // Set nanoseconds to zero // This is needed because the GeneralizedTime serializer would otherwise // output fractional values which RFC 5280 explicitly forbode [1]. // UTCTime cannot express fractional seconds or leap seconds // therefore, it needs to be stripped of nanoseconds fully. // [1]: https://tools.ietf.org/html/rfc5280#section-4.1.2.5.2 // TODO: handle leap seconds if dt becomes leap second aware let time = Time::from_hms(dt.hour(), dt.minute(), dt.second()).expect("invalid or out-of-range time"); dt.replace_time(time) } fn dt_to_generalized(dt: OffsetDateTime) -> GeneralizedTime { let date_time = dt_strip_nanos(dt); GeneralizedTime::from_datetime(date_time) } fn write_dt_utc_or_generalized(writer: DERWriter, dt: OffsetDateTime) { // RFC 5280 requires CAs to write certificate validity dates // below 2050 as UTCTime, and anything starting from 2050 // as GeneralizedTime [1]. The RFC doesn't say anything // about dates before 1950, but as UTCTime can't represent // them, we have to use GeneralizedTime if we want to or not. // [1]: https://tools.ietf.org/html/rfc5280#section-4.1.2.5 if (1950..2050).contains(&dt.year()) { let date_time = dt_strip_nanos(dt); let ut = UTCTime::from_datetime(date_time); writer.write_utctime(&ut); } else { let gt = dt_to_generalized(dt); writer.write_generalized_time(>); } } fn write_distinguished_name(writer: DERWriter, dn: &DistinguishedName) { writer.write_sequence(|writer| { for (ty, content) in dn.iter() { writer.next().write_set(|writer| { writer.next().write_sequence(|writer| { writer.next().write_oid(&ty.to_oid()); match content { DnValue::TeletexString(s) => writer .next() .write_tagged_implicit(TAG_TELETEXSTRING, |writer| { writer.write_bytes(s) }), DnValue::PrintableString(s) => writer.next().write_printable_string(s), DnValue::UniversalString(s) => writer .next() .write_tagged_implicit(TAG_UNIVERSALSTRING, |writer| { writer.write_bytes(s) }), DnValue::Utf8String(s) => writer.next().write_utf8_string(s), DnValue::BmpString(s) => writer .next() .write_tagged_implicit(TAG_BMPSTRING, |writer| writer.write_bytes(s)), } }); }); } }); } fn write_general_subtrees(writer: DERWriter, tag: u64, general_subtrees: &[GeneralSubtree]) { writer.write_tagged_implicit(Tag::context(tag), |writer| { writer.write_sequence(|writer| { for subtree in general_subtrees.iter() { writer.next().write_sequence(|writer| { writer .next() .write_tagged_implicit( Tag::context(subtree.tag()), |writer| match subtree { GeneralSubtree::Rfc822Name(name) | GeneralSubtree::DnsName(name) => writer.write_ia5_string(name), GeneralSubtree::DirectoryName(name) => { write_distinguished_name(writer, name) }, GeneralSubtree::IpAddress(subnet) => { writer.write_bytes(&subnet.to_bytes()) }, }, ); // minimum must be 0 (the default) and maximum must be absent }); } }); }); } impl Certificate { /// Generates a new certificate from the given parameters. /// /// If there is no key pair included, then a new key pair will be generated and used. pub fn from_params(mut params: CertificateParams) -> Result { let key_pair = if let Some(key_pair) = params.key_pair.take() { if !key_pair.is_compatible(¶ms.alg) { return Err(RcgenError::CertificateKeyPairMismatch); } key_pair } else { KeyPair::generate(¶ms.alg)? }; Ok(Certificate { params, key_pair }) } /// Returns the certificate parameters pub fn get_params(&self) -> &CertificateParams { &self.params } /// Calculates a subject key identifier for the certificate subject's public key. /// This key identifier is used in the SubjectKeyIdentifier X.509v3 extension. pub fn get_key_identifier(&self) -> Vec { self.params.key_identifier(&self.key_pair) } /// Serializes the certificate to the binary DER format pub fn serialize_der(&self) -> Result, RcgenError> { self.serialize_der_with_signer(&self) } /// Serializes the certificate, signed with another certificate's key, in binary DER format pub fn serialize_der_with_signer(&self, ca: &Certificate) -> Result, RcgenError> { self.params.serialize_der_with_signer(&self.key_pair, ca) } /// Serializes a certificate signing request in binary DER format pub fn serialize_request_der(&self) -> Result, RcgenError> { yasna::try_construct_der(|writer| { writer.write_sequence(|writer| { let cert_data = yasna::try_construct_der(|writer| { self.params.write_request(&self.key_pair, writer) })?; writer.next().write_der(&cert_data); // Write signatureAlgorithm self.params.alg.write_alg_ident(writer.next()); // Write signature self.key_pair.sign(&cert_data, writer.next())?; Ok(()) }) }) } /// Return the certificate's key pair pub fn get_key_pair(&self) -> &KeyPair { &self.key_pair } /// Serializes the certificate to the ASCII PEM format #[cfg(feature = "pem")] pub fn serialize_pem(&self) -> Result { let contents = self.serialize_der()?; let p = Pem::new("CERTIFICATE", contents); Ok(pem::encode_config(&p, ENCODE_CONFIG)) } /// Serializes the certificate, signed with another certificate's key, to the ASCII PEM format #[cfg(feature = "pem")] pub fn serialize_pem_with_signer(&self, ca: &Certificate) -> Result { let contents = self.serialize_der_with_signer(ca)?; let p = Pem::new("CERTIFICATE", contents); Ok(pem::encode_config(&p, ENCODE_CONFIG)) } /// Serializes the certificate signing request to the ASCII PEM format #[cfg(feature = "pem")] pub fn serialize_request_pem(&self) -> Result { let contents = self.serialize_request_der()?; let p = Pem::new("CERTIFICATE REQUEST", contents); Ok(pem::encode_config(&p, ENCODE_CONFIG)) } /// Serializes the private key in PKCS#8 format /// /// Panics if called on a remote key pair. pub fn serialize_private_key_der(&self) -> Vec { self.key_pair.serialize_der() } /// Serializes the private key in PEM format /// /// Panics if called on a remote key pair. #[cfg(feature = "pem")] pub fn serialize_private_key_pem(&self) -> String { self.key_pair.serialize_pem() } } /// Serializes an X.509v3 extension according to RFC 5280 fn write_x509_extension( writer: DERWriter, extension_oid: &[u64], is_critical: bool, value_serializer: impl FnOnce(DERWriter), ) { // Extension specification: // Extension ::= SEQUENCE { // extnID OBJECT IDENTIFIER, // critical BOOLEAN DEFAULT FALSE, // extnValue OCTET STRING // -- contains the DER encoding of an ASN.1 value // -- corresponding to the extension type identified // -- by extnID // } writer.write_sequence(|writer| { let oid = ObjectIdentifier::from_slice(extension_oid); writer.next().write_oid(&oid); if is_critical { writer.next().write_bool(true); } let bytes = yasna::construct_der(value_serializer); writer.next().write_bytes(&bytes); }) } /// Serializes an X.509v3 authority key identifier extension according to RFC 5280. fn write_x509_authority_key_identifier(writer: DERWriter, ca: &Certificate) { // Write Authority Key Identifier // RFC 5280 states: // 'The keyIdentifier field of the authorityKeyIdentifier extension MUST // be included in all certificates generated by conforming CAs to // facilitate certification path construction. There is one exception; // where a CA distributes its public key in the form of a "self-signed" // certificate, the authority key identifier MAY be omitted.' // In addition, for CRLs: // 'Conforming CRL issuers MUST use the key identifier method, and MUST // include this extension in all CRLs issued.' write_x509_extension(writer, OID_AUTHORITY_KEY_IDENTIFIER, false, |writer| { writer.write_sequence(|writer| { writer .next() .write_tagged_implicit(Tag::context(0), |writer| { writer.write_bytes(ca.get_key_identifier().as_ref()) }) }); }); } #[cfg(feature = "zeroize")] impl zeroize::Zeroize for KeyPair { fn zeroize(&mut self) { self.serialized_der.zeroize(); } } #[cfg(feature = "zeroize")] impl zeroize::Zeroize for Certificate { fn zeroize(&mut self) { self.params.zeroize(); self.key_pair.zeroize(); } } #[cfg(feature = "zeroize")] impl zeroize::Zeroize for CertificateSigningRequest { fn zeroize(&mut self) { self.params.zeroize(); } } #[cfg(feature = "zeroize")] impl zeroize::Zeroize for CertificateParams { fn zeroize(&mut self) { self.key_pair.zeroize(); } } /// A certificate serial number. #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct SerialNumber { inner: Vec, } impl SerialNumber { /// Create a serial number from the given byte slice. pub fn from_slice(bytes: &[u8]) -> SerialNumber { let inner = bytes.to_vec(); SerialNumber { inner } } /// Return the byte representation of the serial number. pub fn to_bytes(&self) -> Vec { self.inner.clone() } /// Return the length of the serial number in bytes. pub fn len(&self) -> usize { self.inner.len() } } impl fmt::Display for SerialNumber { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { let hex: Vec<_> = self.inner.iter().map(|b| format!("{:02x}", b)).collect(); write!(f, "{}", hex.join(":")) } } impl From> for SerialNumber { fn from(inner: Vec) -> SerialNumber { SerialNumber { inner } } } impl From for SerialNumber { fn from(u: u64) -> SerialNumber { let inner = u.to_be_bytes().into(); SerialNumber { inner } } } impl AsRef<[u8]> for SerialNumber { fn as_ref(&self) -> &[u8] { &self.inner } } #[cfg(test)] mod tests { use super::*; use std::panic::catch_unwind; fn get_times() -> [OffsetDateTime; 2] { let dt_nanos = { let date = Date::from_calendar_date(2020, Month::December, 3).unwrap(); let time = Time::from_hms_nano(0, 0, 1, 444).unwrap(); PrimitiveDateTime::new(date, time).assume_utc() }; let dt_zero = { let date = Date::from_calendar_date(2020, Month::December, 3).unwrap(); let time = Time::from_hms_nano(0, 0, 1, 0).unwrap(); PrimitiveDateTime::new(date, time).assume_utc() }; // TODO: include leap seconds if time becomes leap second aware [dt_nanos, dt_zero] } #[test] fn test_dt_utc_strip_nanos() { let times = get_times(); // No stripping - OffsetDateTime with nanos let res = catch_unwind(|| UTCTime::from_datetime(times[0])); assert!(res.is_err()); // Stripping for dt in times { let date_time = dt_strip_nanos(dt); assert_eq!(date_time.time().nanosecond(), 0); let _ut = UTCTime::from_datetime(date_time); } } #[test] fn test_dt_to_generalized() { let times = get_times(); for dt in times { let _gt = dt_to_generalized(dt); } } #[test] fn test_with_key_usages() { let mut params: CertificateParams = Default::default(); // Set key_usages params.key_usages = vec![ KeyUsagePurpose::DigitalSignature, KeyUsagePurpose::KeyEncipherment, KeyUsagePurpose::ContentCommitment, ]; // This can sign things! params.is_ca = IsCa::Ca(BasicConstraints::Constrained(0)); // Make the cert let cert = Certificate::from_params(params).unwrap(); // Serialize it let der = cert.serialize_der().unwrap(); // Parse it let (_rem, cert) = x509_parser::parse_x509_certificate(&der).unwrap(); // Check oid let key_usage_oid_str = "2.5.29.15"; // Found flag let mut found = false; for ext in cert.extensions() { if key_usage_oid_str == ext.oid.to_id_string() { match ext.parsed_extension() { x509_parser::extensions::ParsedExtension::KeyUsage(usage) => { assert!(usage.flags == 7); found = true; }, _ => {}, } } } assert!(found); } #[test] fn test_with_key_usages_decipheronly_only() { let mut params: CertificateParams = Default::default(); // Set key_usages params.key_usages = vec![KeyUsagePurpose::DecipherOnly]; // This can sign things! params.is_ca = IsCa::Ca(BasicConstraints::Constrained(0)); // Make the cert let cert = Certificate::from_params(params).unwrap(); // Serialize it let der = cert.serialize_der().unwrap(); // Parse it let (_rem, cert) = x509_parser::parse_x509_certificate(&der).unwrap(); // Check oid let key_usage_oid_str = "2.5.29.15"; // Found flag let mut found = false; for ext in cert.extensions() { if key_usage_oid_str == ext.oid.to_id_string() { match ext.parsed_extension() { x509_parser::extensions::ParsedExtension::KeyUsage(usage) => { assert!(usage.flags == 256); found = true; }, _ => {}, } } } assert!(found); } #[test] fn test_with_extended_key_usages_any() { let mut params: CertificateParams = Default::default(); // Set extended_key_usages params.extended_key_usages = vec![ExtendedKeyUsagePurpose::Any]; // Make the cert let cert = Certificate::from_params(params).unwrap(); // Serialize it let der = cert.serialize_der().unwrap(); // Parse it let (_rem, cert) = x509_parser::parse_x509_certificate(&der).unwrap(); // Ensure we found it. let maybe_extension = cert.extended_key_usage().unwrap(); let extension = maybe_extension.unwrap(); assert!(extension.value.any); } #[test] fn signature_algos_different() { // TODO unify this with test_key_params_mismatch. // Note that that test doesn't have a full list of signature // algorithms, as it has no access to the iter function. for (i, alg_i) in SignatureAlgorithm::iter().enumerate() { for (j, alg_j) in SignatureAlgorithm::iter().enumerate() { assert_eq!( alg_i == alg_j, i == j, "Algorighm relationship mismatch for algorithm index pair {} and {}", i, j ); } } } #[cfg(feature = "pem")] mod test_pem_serialization { use crate::Certificate; use crate::CertificateParams; #[test] #[cfg(windows)] fn test_windows_line_endings() { let cert = Certificate::from_params(CertificateParams::default()).unwrap(); let pem = cert.serialize_pem().expect("Failed to serialize pem"); assert!(pem.contains("\r\n")); } #[test] #[cfg(not(windows))] fn test_not_windows_line_endings() { let cert = Certificate::from_params(CertificateParams::default()).unwrap(); let pem = cert.serialize_pem().expect("Failed to serialize pem"); assert!(!pem.contains("\r")); } } #[cfg(feature = "x509-parser")] mod test_ip_address_from_octets { use super::super::ip_addr_from_octets; use super::super::RcgenError; use std::net::IpAddr; #[test] fn ipv4() { let octets = [10, 20, 30, 40]; let actual = ip_addr_from_octets(&octets).unwrap(); assert_eq!(IpAddr::from(octets), actual) } #[test] fn ipv6() { let octets = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; let actual = ip_addr_from_octets(&octets).unwrap(); assert_eq!(IpAddr::from(octets), actual) } #[test] fn mismatch() { let incorrect: Vec = (0..10).into_iter().collect(); let actual = ip_addr_from_octets(&incorrect).unwrap_err(); assert_eq!(RcgenError::InvalidIpAddressOctetLength(10), actual); } #[test] fn none() { let actual = ip_addr_from_octets(&[]).unwrap_err(); assert_eq!(RcgenError::InvalidIpAddressOctetLength(0), actual); } #[test] fn too_many() { let incorrect: Vec = (0..20).into_iter().collect(); let actual = ip_addr_from_octets(&incorrect).unwrap_err(); assert_eq!(RcgenError::InvalidIpAddressOctetLength(20), actual); } } #[cfg(feature = "x509-parser")] mod test_san_type_from_general_name { use crate::SanType; use std::net::IpAddr; use x509_parser::extensions::GeneralName; #[test] fn with_ipv4() { let octets = [1, 2, 3, 4]; let value = GeneralName::IPAddress(&octets); let actual = SanType::try_from_general(&value).unwrap(); assert_eq!(SanType::IpAddress(IpAddr::from(octets)), actual); } } } rcgen-0.11.3/src/main.rs000064400000000000000000000024051046102023000130660ustar 00000000000000#![allow(clippy::complexity, clippy::style, clippy::pedantic)] use rcgen::{date_time_ymd, Certificate, CertificateParams, DistinguishedName, DnType, SanType}; use std::fs; fn main() -> Result<(), Box> { let mut params: CertificateParams = Default::default(); params.not_before = date_time_ymd(1975, 01, 01); params.not_after = date_time_ymd(4096, 01, 01); params.distinguished_name = DistinguishedName::new(); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Master Cert"); params.subject_alt_names = vec![ SanType::DnsName("crabs.crabs".to_string()), SanType::DnsName("localhost".to_string()), ]; let cert = Certificate::from_params(params)?; let pem_serialized = cert.serialize_pem()?; let pem = pem::parse(&pem_serialized)?; let der_serialized = pem.contents(); println!("{pem_serialized}"); println!("{}", cert.serialize_private_key_pem()); std::fs::create_dir_all("certs/")?; fs::write("certs/cert.pem", &pem_serialized.as_bytes())?; fs::write("certs/cert.der", &der_serialized)?; fs::write( "certs/key.pem", &cert.serialize_private_key_pem().as_bytes(), )?; fs::write("certs/key.der", &cert.serialize_private_key_der())?; Ok(()) } rcgen-0.11.3/src/oid.rs000064400000000000000000000074041046102023000127210ustar 00000000000000/// pkcs-9-at-extensionRequest in [RFC 2985](https://www.rfc-editor.org/rfc/rfc2985#appendix-A) pub const OID_PKCS_9_AT_EXTENSION_REQUEST: &[u64] = &[1, 2, 840, 113549, 1, 9, 14]; /// id-at-countryName in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub const OID_COUNTRY_NAME: &[u64] = &[2, 5, 4, 6]; /// id-at-localityName in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub const OID_LOCALITY_NAME: &[u64] = &[2, 5, 4, 7]; /// id-at-stateOrProvinceName in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub const OID_STATE_OR_PROVINCE_NAME: &[u64] = &[2, 5, 4, 8]; /// id-at-organizationName in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub const OID_ORG_NAME: &[u64] = &[2, 5, 4, 10]; /// id-at-organizationalUnitName in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub const OID_ORG_UNIT_NAME: &[u64] = &[2, 5, 4, 11]; /// id-at-commonName in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub const OID_COMMON_NAME: &[u64] = &[2, 5, 4, 3]; /// id-ecPublicKey in [RFC 5480](https://datatracker.ietf.org/doc/html/rfc5480#appendix-A) pub const OID_EC_PUBLIC_KEY: &[u64] = &[1, 2, 840, 10045, 2, 1]; /// secp256r1 in [RFC 5480](https://datatracker.ietf.org/doc/html/rfc5480#appendix-A) pub const OID_EC_SECP_256_R1: &[u64] = &[1, 2, 840, 10045, 3, 1, 7]; /// secp384r1 in [RFC 5480](https://datatracker.ietf.org/doc/html/rfc5480#appendix-A) pub const OID_EC_SECP_384_R1: &[u64] = &[1, 3, 132, 0, 34]; /// rsaEncryption in [RFC 4055](https://www.rfc-editor.org/rfc/rfc4055#section-6) pub const OID_RSA_ENCRYPTION: &[u64] = &[1, 2, 840, 113549, 1, 1, 1]; /// id-RSASSA-PSS in [RFC 4055](https://www.rfc-editor.org/rfc/rfc4055#section-6) pub const OID_RSASSA_PSS: &[u64] = &[1, 2, 840, 113549, 1, 1, 10]; /// id-ce-keyUsage in [RFC 5280](https://tools.ietf.org/html/rfc5280#appendix-A.2) pub const OID_KEY_USAGE: &[u64] = &[2, 5, 29, 15]; /// id-ce-subjectAltName in [RFC 5280](https://tools.ietf.org/html/rfc5280#appendix-A.2) pub const OID_SUBJECT_ALT_NAME: &[u64] = &[2, 5, 29, 17]; /// id-ce-basicConstraints in [RFC 5280](https://tools.ietf.org/html/rfc5280#appendix-A.2) pub const OID_BASIC_CONSTRAINTS: &[u64] = &[2, 5, 29, 19]; /// id-ce-subjectKeyIdentifier in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub const OID_SUBJECT_KEY_IDENTIFIER: &[u64] = &[2, 5, 29, 14]; /// id-ce-authorityKeyIdentifier in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub const OID_AUTHORITY_KEY_IDENTIFIER: &[u64] = &[2, 5, 29, 35]; /// id-ce-extKeyUsage in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub const OID_EXT_KEY_USAGE: &[u64] = &[2, 5, 29, 37]; /// id-ce-nameConstraints in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub const OID_NAME_CONSTRAINTS: &[u64] = &[2, 5, 29, 30]; /// id-ce-cRLDistributionPoints in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub const OID_CRL_DISTRIBUTION_POINTS: &[u64] = &[2, 5, 29, 31]; /// id-pe-acmeIdentifier in /// [IANA SMI Numbers registry](https://www.iana.org/assignments/smi-numbers/smi-numbers.xhtml#smi-numbers-1.3.6.1.5.5.7.1) pub const OID_PE_ACME: &[u64] = &[1, 3, 6, 1, 5, 5, 7, 1, 31]; /// id-ce-cRLNumber in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub const OID_CRL_NUMBER: &[u64] = &[2, 5, 29, 20]; /// id-ce-cRLReasons in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub const OID_CRL_REASONS: &[u64] = &[2, 5, 29, 21]; /// id-ce-invalidityDate in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub const OID_CRL_INVALIDITY_DATE: &[u64] = &[2, 5, 29, 24]; /// id-ce-issuingDistributionPoint in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub const OID_CRL_ISSUING_DISTRIBUTION_POINT: &[u64] = &[2, 5, 29, 28]; rcgen-0.11.3/src/sign_algo.rs000064400000000000000000000204071046102023000141060ustar 00000000000000use ring::signature::{self, EcdsaSigningAlgorithm, EdDSAParameters}; use std::fmt; use std::hash::{Hash, Hasher}; use yasna::models::ObjectIdentifier; use yasna::DERWriter; use yasna::Tag; use crate::oid::*; use crate::RcgenError; pub(crate) enum SignAlgo { EcDsa(&'static EcdsaSigningAlgorithm), EdDsa(&'static EdDSAParameters), Rsa(), } #[derive(PartialEq, Eq)] pub(crate) enum SignatureAlgorithmParams { /// Omit the parameters None, /// Write null parameters Null, /// RSASSA-PSS-params as per RFC 4055 RsaPss { hash_algorithm: &'static [u64], salt_length: u64, }, } /// Signature algorithm type pub struct SignatureAlgorithm { oids_sign_alg: &'static [&'static [u64]], pub(crate) sign_alg: SignAlgo, oid_components: &'static [u64], params: SignatureAlgorithmParams, } impl fmt::Debug for SignatureAlgorithm { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use algo::*; if self == &PKCS_RSA_SHA256 { write!(f, "PKCS_RSA_SHA256") } else if self == &PKCS_RSA_SHA384 { write!(f, "PKCS_RSA_SHA384") } else if self == &PKCS_RSA_SHA512 { write!(f, "PKCS_RSA_SHA512") } else if self == &PKCS_RSA_PSS_SHA256 { write!(f, "PKCS_RSA_PSS_SHA256") } else if self == &PKCS_ECDSA_P256_SHA256 { write!(f, "PKCS_ECDSA_P256_SHA256") } else if self == &PKCS_ECDSA_P384_SHA384 { write!(f, "PKCS_ECDSA_P384_SHA384") } else if self == &PKCS_ED25519 { write!(f, "PKCS_ED25519") } else { write!(f, "Unknown") } } } impl PartialEq for SignatureAlgorithm { fn eq(&self, other: &Self) -> bool { (self.oids_sign_alg, self.oid_components) == (other.oids_sign_alg, other.oid_components) } } impl Eq for SignatureAlgorithm {} /// The `Hash` trait is not derived, but implemented according to impl of the `PartialEq` trait impl Hash for SignatureAlgorithm { fn hash(&self, state: &mut H) { // see SignatureAlgorithm::eq(), just this field is compared self.oids_sign_alg.hash(state); } } impl SignatureAlgorithm { pub(crate) fn iter() -> std::slice::Iter<'static, &'static SignatureAlgorithm> { use algo::*; static ALGORITHMS: &[&SignatureAlgorithm] = &[ &PKCS_RSA_SHA256, &PKCS_RSA_SHA384, &PKCS_RSA_SHA512, //&PKCS_RSA_PSS_SHA256, &PKCS_ECDSA_P256_SHA256, &PKCS_ECDSA_P384_SHA384, &PKCS_ED25519, ]; ALGORITHMS.iter() } /// Retrieve the SignatureAlgorithm for the provided OID pub fn from_oid(oid: &[u64]) -> Result<&'static SignatureAlgorithm, RcgenError> { for algo in Self::iter() { if algo.oid_components == oid { return Ok(algo); } } Err(RcgenError::UnsupportedSignatureAlgorithm) } } /// The list of supported signature algorithms pub mod algo { use super::*; /// RSA signing with PKCS#1 1.5 padding and SHA-256 hashing as per [RFC 4055](https://tools.ietf.org/html/rfc4055) pub static PKCS_RSA_SHA256: SignatureAlgorithm = SignatureAlgorithm { oids_sign_alg: &[&OID_RSA_ENCRYPTION], sign_alg: SignAlgo::Rsa(), // sha256WithRSAEncryption in RFC 4055 oid_components: &[1, 2, 840, 113549, 1, 1, 11], params: SignatureAlgorithmParams::Null, }; /// RSA signing with PKCS#1 1.5 padding and SHA-256 hashing as per [RFC 4055](https://tools.ietf.org/html/rfc4055) pub static PKCS_RSA_SHA384: SignatureAlgorithm = SignatureAlgorithm { oids_sign_alg: &[&OID_RSA_ENCRYPTION], sign_alg: SignAlgo::Rsa(), // sha384WithRSAEncryption in RFC 4055 oid_components: &[1, 2, 840, 113549, 1, 1, 12], params: SignatureAlgorithmParams::Null, }; /// RSA signing with PKCS#1 1.5 padding and SHA-512 hashing as per [RFC 4055](https://tools.ietf.org/html/rfc4055) pub static PKCS_RSA_SHA512: SignatureAlgorithm = SignatureAlgorithm { oids_sign_alg: &[&OID_RSA_ENCRYPTION], sign_alg: SignAlgo::Rsa(), // sha512WithRSAEncryption in RFC 4055 oid_components: &[1, 2, 840, 113549, 1, 1, 13], params: SignatureAlgorithmParams::Null, }; // TODO: not really sure whether the certs we generate actually work. // Both openssl and webpki reject them. It *might* be possible that openssl // accepts the certificate if the key is a proper RSA-PSS key, but ring doesn't // support those: https://github.com/briansmith/ring/issues/1353 // /// RSA signing with PKCS#1 2.1 RSASSA-PSS padding and SHA-256 hashing as per [RFC 4055](https://tools.ietf.org/html/rfc4055) pub(crate) static PKCS_RSA_PSS_SHA256: SignatureAlgorithm = SignatureAlgorithm { // We could also use OID_RSA_ENCRYPTION here, but it's recommended // to use ID-RSASSA-PSS if possible. oids_sign_alg: &[&OID_RSASSA_PSS], sign_alg: SignAlgo::Rsa(), oid_components: &OID_RSASSA_PSS, //&[1, 2, 840, 113549, 1, 1, 13], // rSASSA-PSS-SHA256-Params in RFC 4055 params: SignatureAlgorithmParams::RsaPss { // id-sha256 in https://datatracker.ietf.org/doc/html/rfc4055#section-2.1 hash_algorithm: &[2, 16, 840, 1, 101, 3, 4, 2, 1], salt_length: 20, }, }; /// ECDSA signing using the P-256 curves and SHA-256 hashing as per [RFC 5758](https://tools.ietf.org/html/rfc5758#section-3.2) pub static PKCS_ECDSA_P256_SHA256: SignatureAlgorithm = SignatureAlgorithm { oids_sign_alg: &[&OID_EC_PUBLIC_KEY, &OID_EC_SECP_256_R1], sign_alg: SignAlgo::EcDsa(&signature::ECDSA_P256_SHA256_ASN1_SIGNING), // ecdsa-with-SHA256 in RFC 5758 oid_components: &[1, 2, 840, 10045, 4, 3, 2], params: SignatureAlgorithmParams::None, }; /// ECDSA signing using the P-384 curves and SHA-384 hashing as per [RFC 5758](https://tools.ietf.org/html/rfc5758#section-3.2) pub static PKCS_ECDSA_P384_SHA384: SignatureAlgorithm = SignatureAlgorithm { oids_sign_alg: &[&OID_EC_PUBLIC_KEY, &OID_EC_SECP_384_R1], sign_alg: SignAlgo::EcDsa(&signature::ECDSA_P384_SHA384_ASN1_SIGNING), // ecdsa-with-SHA384 in RFC 5758 oid_components: &[1, 2, 840, 10045, 4, 3, 3], params: SignatureAlgorithmParams::None, }; // TODO PKCS_ECDSA_P521_SHA512 https://github.com/briansmith/ring/issues/824 /// ED25519 curve signing as per [RFC 8410](https://tools.ietf.org/html/rfc8410) pub static PKCS_ED25519: SignatureAlgorithm = SignatureAlgorithm { // id-Ed25519 in RFC 8410 oids_sign_alg: &[&[1, 3, 101, 112]], sign_alg: SignAlgo::EdDsa(&signature::ED25519), // id-Ed25519 in RFC 8410 oid_components: &[1, 3, 101, 112], params: SignatureAlgorithmParams::None, }; } // Signature algorithm IDs as per https://tools.ietf.org/html/rfc4055 impl SignatureAlgorithm { fn alg_ident_oid(&self) -> ObjectIdentifier { ObjectIdentifier::from_slice(self.oid_components) } fn write_params(&self, writer: &mut yasna::DERWriterSeq) { match self.params { SignatureAlgorithmParams::None => (), SignatureAlgorithmParams::Null => { writer.next().write_null(); }, SignatureAlgorithmParams::RsaPss { hash_algorithm, salt_length, } => { writer.next().write_sequence(|writer| { // https://datatracker.ietf.org/doc/html/rfc4055#section-3.1 let oid = ObjectIdentifier::from_slice(hash_algorithm); // hashAlgorithm writer.next().write_tagged(Tag::context(0), |writer| { writer.write_sequence(|writer| { writer.next().write_oid(&oid); }); }); // maskGenAlgorithm writer.next().write_tagged(Tag::context(1), |writer| { writer.write_sequence(|writer| { // id-mgf1 in RFC 4055 const ID_MGF1: &[u64] = &[1, 2, 840, 113549, 1, 1, 8]; let oid = ObjectIdentifier::from_slice(ID_MGF1); writer.next().write_oid(&oid); writer.next().write_sequence(|writer| { let oid = ObjectIdentifier::from_slice(hash_algorithm); writer.next().write_oid(&oid); writer.next().write_null(); }); }); }); // saltLength writer.next().write_tagged(Tag::context(2), |writer| { writer.write_u64(salt_length); }); // We *must* omit the trailerField element as per RFC 4055 section 3.1 }) }, } } /// Writes the algorithm identifier as it appears inside a signature pub(crate) fn write_alg_ident(&self, writer: DERWriter) { writer.write_sequence(|writer| { writer.next().write_oid(&self.alg_ident_oid()); self.write_params(writer); }); } /// Writes the algorithm identifier as it appears inside subjectPublicKeyInfo pub(crate) fn write_oids_sign_alg(&self, writer: DERWriter) { writer.write_sequence(|writer| { for oid in self.oids_sign_alg { let oid = ObjectIdentifier::from_slice(oid); writer.next().write_oid(&oid); } self.write_params(writer); }); } } rcgen-0.11.3/tests/botan.rs000064400000000000000000000210241046102023000136160ustar 00000000000000#![cfg(feature = "x509-parser")] use rcgen::DnValue; use rcgen::{BasicConstraints, Certificate, CertificateParams, DnType, IsCa}; use rcgen::{ CertificateRevocationList, CertificateRevocationListParams, RevocationReason, RevokedCertParams, }; use rcgen::{KeyUsagePurpose, SerialNumber}; use time::{Duration, OffsetDateTime}; mod util; fn default_params() -> CertificateParams { let mut params = util::default_params(); // Botan has a sanity check that enforces a maximum expiration date params.not_after = rcgen::date_time_ymd(3016, 01, 01); params } fn check_cert<'a, 'b>(cert_der: &[u8], cert: &'a Certificate) { println!("{}", cert.serialize_pem().unwrap()); check_cert_ca(cert_der, cert, cert_der); } fn check_cert_ca<'a, 'b>(cert_der: &[u8], _cert: &'a Certificate, ca_der: &[u8]) { println!( "botan version: {}", botan::Version::current().unwrap().string ); let trust_anchor = botan::Certificate::load(&ca_der).unwrap(); let end_entity_cert = botan::Certificate::load(&cert_der).unwrap(); // Set time to Jan 10, 2004 const REFERENCE_TIME: Option = Some(0x40_00_00_00); // Verify the certificate end_entity_cert .verify( &[], &[&trust_anchor], None, Some("crabs.crabs"), REFERENCE_TIME, ) .unwrap(); // TODO perform a full handshake } #[test] fn test_botan() { let params = default_params(); let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. let cert_der = cert.serialize_der().unwrap(); check_cert(&cert_der, &cert); } #[test] fn test_botan_256() { let mut params = default_params(); params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. let cert_der = cert.serialize_der().unwrap(); check_cert(&cert_der, &cert); } #[test] fn test_botan_384() { let mut params = default_params(); params.alg = &rcgen::PKCS_ECDSA_P384_SHA384; let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. let cert_der = cert.serialize_der().unwrap(); check_cert(&cert_der, &cert); } #[test] fn test_botan_25519() { let mut params = default_params(); params.alg = &rcgen::PKCS_ED25519; let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. let cert_der = cert.serialize_der().unwrap(); check_cert(&cert_der, &cert); } #[test] fn test_botan_25519_v1_given() { let mut params = default_params(); params.alg = &rcgen::PKCS_ED25519; let kp = rcgen::KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V1).unwrap(); params.key_pair = Some(kp); let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. let cert_der = cert.serialize_der().unwrap(); check_cert(&cert_der, &cert); } #[test] fn test_botan_25519_v2_given() { let mut params = default_params(); params.alg = &rcgen::PKCS_ED25519; let kp = rcgen::KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V2).unwrap(); params.key_pair = Some(kp); let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. let cert_der = cert.serialize_der().unwrap(); check_cert(&cert_der, &cert); } #[test] fn test_botan_rsa_given() { let mut params = default_params(); params.alg = &rcgen::PKCS_RSA_SHA256; let kp = rcgen::KeyPair::from_pem(util::RSA_TEST_KEY_PAIR_PEM).unwrap(); params.key_pair = Some(kp); let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. let cert_der = cert.serialize_der().unwrap(); check_cert(&cert_der, &cert); } #[test] fn test_botan_separate_ca() { let mut params = default_params(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); let ca_cert = Certificate::from_params(params).unwrap(); let ca_der = ca_cert.serialize_der().unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Dev domain"); // Botan has a sanity check that enforces a maximum expiration date params.not_after = rcgen::date_time_ymd(3016, 01, 01); let cert = Certificate::from_params(params).unwrap(); let cert_der = cert.serialize_der_with_signer(&ca_cert).unwrap(); check_cert_ca(&cert_der, &cert, &ca_der); } #[cfg(feature = "x509-parser")] #[test] fn test_botan_imported_ca() { use std::convert::TryInto; let mut params = default_params(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); let ca_cert = Certificate::from_params(params).unwrap(); let (ca_cert_der, ca_key_der) = ( ca_cert.serialize_der().unwrap(), ca_cert.serialize_private_key_der(), ); let ca_key_pair = ca_key_der.as_slice().try_into().unwrap(); let imported_ca_cert_params = CertificateParams::from_ca_cert_der(ca_cert_der.as_slice(), ca_key_pair).unwrap(); let imported_ca_cert = Certificate::from_params(imported_ca_cert_params).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Dev domain"); // Botan has a sanity check that enforces a maximum expiration date params.not_after = rcgen::date_time_ymd(3016, 01, 01); let cert = Certificate::from_params(params).unwrap(); let cert_der = cert.serialize_der_with_signer(&imported_ca_cert).unwrap(); check_cert_ca(&cert_der, &cert, &ca_cert_der); } #[cfg(feature = "x509-parser")] #[test] fn test_botan_imported_ca_with_printable_string() { use std::convert::TryInto; let mut params = default_params(); params.distinguished_name.push( DnType::CountryName, DnValue::PrintableString("US".to_string()), ); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); let ca_cert = Certificate::from_params(params).unwrap(); let (ca_cert_der, ca_key_der) = ( ca_cert.serialize_der().unwrap(), ca_cert.serialize_private_key_der(), ); let ca_key_pair = ca_key_der.as_slice().try_into().unwrap(); let imported_ca_cert_params = CertificateParams::from_ca_cert_der(ca_cert_der.as_slice(), ca_key_pair).unwrap(); let imported_ca_cert = Certificate::from_params(imported_ca_cert_params).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Dev domain"); // Botan has a sanity check that enforces a maximum expiration date params.not_after = rcgen::date_time_ymd(3016, 01, 01); let cert = Certificate::from_params(params).unwrap(); let cert_der = cert.serialize_der_with_signer(&imported_ca_cert).unwrap(); check_cert_ca(&cert_der, &cert, &ca_cert_der); } #[test] fn test_botan_crl_parse() { // Create an issuer CA. let alg = &rcgen::PKCS_ECDSA_P256_SHA256; let mut issuer = util::default_params(); issuer.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); issuer.key_usages = vec![ KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::DigitalSignature, KeyUsagePurpose::CrlSign, ]; issuer.alg = alg; let issuer = Certificate::from_params(issuer).unwrap(); // Create an end entity cert issued by the issuer. let mut ee = util::default_params(); ee.alg = alg; ee.is_ca = IsCa::NoCa; ee.serial_number = Some(SerialNumber::from(99999)); // Botan has a sanity check that enforces a maximum expiration date ee.not_after = rcgen::date_time_ymd(3016, 01, 01); let ee = Certificate::from_params(ee).unwrap(); let ee_der = ee.serialize_der_with_signer(&issuer).unwrap(); let botan_ee = botan::Certificate::load(ee_der.as_ref()).unwrap(); // Generate a CRL with the issuer that revokes the EE cert. let now = OffsetDateTime::now_utc(); let crl = CertificateRevocationListParams { this_update: now, next_update: now + Duration::weeks(1), crl_number: rcgen::SerialNumber::from(1234), issuing_distribution_point: None, revoked_certs: vec![RevokedCertParams { serial_number: ee.get_params().serial_number.clone().unwrap(), revocation_time: now, reason_code: Some(RevocationReason::KeyCompromise), invalidity_date: None, }], key_identifier_method: rcgen::KeyIdMethod::Sha256, alg, }; let crl = CertificateRevocationList::from_params(crl).unwrap(); // Serialize to both DER and PEM. let crl_der = crl.serialize_der_with_signer(&issuer).unwrap(); let crl_pem = crl.serialize_pem_with_signer(&issuer).unwrap(); // We should be able to load the CRL in both serializations. botan::CRL::load(crl_pem.as_ref()).unwrap(); let crl = botan::CRL::load(crl_der.as_ref()).unwrap(); // We should find the EE cert revoked. assert!(crl.is_revoked(&botan_ee).unwrap()); } rcgen-0.11.3/tests/generic.rs000064400000000000000000000235511046102023000141360ustar 00000000000000mod util; #[cfg(feature = "pem")] mod test_key_params_mismatch { use crate::util; use rcgen::{Certificate, KeyPair, RcgenError}; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; fn generate_hash(subject: &T) -> u64 { let mut hasher = DefaultHasher::new(); subject.hash(&mut hasher); hasher.finish() } #[test] fn test_key_params_mismatch() { let available_key_params = [ &rcgen::PKCS_RSA_SHA256, &rcgen::PKCS_ECDSA_P256_SHA256, &rcgen::PKCS_ECDSA_P384_SHA384, &rcgen::PKCS_ED25519, ]; for (i, kalg_1) in available_key_params.iter().enumerate() { for (j, kalg_2) in available_key_params.iter().enumerate() { if i == j { assert_eq!(*kalg_1, *kalg_2); assert_eq!(generate_hash(*kalg_1), generate_hash(*kalg_2)); continue; } assert_ne!(*kalg_1, *kalg_2); assert_ne!(generate_hash(*kalg_1), generate_hash(*kalg_2)); let mut wrong_params = util::default_params(); if i != 0 { wrong_params.key_pair = Some(KeyPair::generate(kalg_1).unwrap()); } else { let kp = KeyPair::from_pem(util::RSA_TEST_KEY_PAIR_PEM).unwrap(); wrong_params.key_pair = Some(kp); } wrong_params.alg = *kalg_2; assert_eq!( Certificate::from_params(wrong_params).err(), Some(RcgenError::CertificateKeyPairMismatch), "i: {} j: {}", i, j ); } } } } #[cfg(feature = "x509-parser")] mod test_convert_x509_subject_alternative_name { use rcgen::{ BasicConstraints, Certificate, CertificateParams, IsCa, KeyPair, SanType, PKCS_ECDSA_P256_SHA256, }; use std::net::{IpAddr, Ipv4Addr}; #[test] fn converts_from_ip() { let ip = Ipv4Addr::new(2, 4, 6, 8); let ip_san = SanType::IpAddress(IpAddr::V4(ip)); let mut params = super::util::default_params(); // Add the SAN we want to test the parsing for params.subject_alt_names.push(ip_san.clone()); // Because we're using a function for CA certificates params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); let cert = Certificate::from_params(params).unwrap(); // Serialize our cert that has our chosen san, so we can testing parsing/deserializing it. let ca_der = cert.serialize_der().unwrap(); // Arbitrary key pair not used with the test, but required by the parsing function let key_pair = KeyPair::generate(&PKCS_ECDSA_P256_SHA256).unwrap(); let actual = CertificateParams::from_ca_cert_der(&ca_der, key_pair).unwrap(); assert!(actual.subject_alt_names.contains(&ip_san)); } } #[cfg(feature = "x509-parser")] mod test_x509_custom_ext { use crate::util; use rcgen::{Certificate, CustomExtension}; use x509_parser::oid_registry::asn1_rs; use x509_parser::prelude::{ FromDer, ParsedCriAttribute, X509Certificate, X509CertificationRequest, }; #[test] fn custom_ext() { // Create an imaginary critical custom extension for testing. let test_oid = asn1_rs::Oid::from(&[2, 5, 29, 999999]).unwrap(); let test_ext = yasna::construct_der(|writer| { writer.write_utf8_string("🦀 greetz to ferris 🦀"); }); let mut custom_ext = CustomExtension::from_oid_content( test_oid.iter().unwrap().collect::>().as_slice(), test_ext.clone(), ); custom_ext.set_criticality(true); // Generate a certificate with the custom extension, parse it with x509-parser. let mut params = util::default_params(); params.custom_extensions = vec![custom_ext]; // Ensure the custom exts. being omitted into a CSR doesn't require SAN ext being present. // See https://github.com/rustls/rcgen/issues/122 params.subject_alt_names = Vec::default(); let test_cert = Certificate::from_params(params).unwrap(); let test_cert_der = test_cert.serialize_der().unwrap(); let (_, x509_test_cert) = X509Certificate::from_der(&test_cert_der).unwrap(); // We should be able to find the extension by OID, with expected criticality and value. let favorite_drink_ext = x509_test_cert .get_extension_unique(&test_oid) .expect("invalid extensions") .expect("missing custom extension"); assert_eq!(favorite_drink_ext.critical, true); assert_eq!(favorite_drink_ext.value, test_ext); // Generate a CSR with the custom extension, parse it with x509-parser. let test_cert_csr_der = test_cert.serialize_request_der().unwrap(); let (_, x509_csr) = X509CertificationRequest::from_der(&test_cert_csr_der).unwrap(); // We should find that the CSR contains requested extensions. // Note: we can't use `x509_csr.requested_extensions()` here because it maps the raw extension // request extensions to their parsed form, and of course x509-parser doesn't parse our custom extension. let exts = x509_csr .certification_request_info .iter_attributes() .find_map(|attr| { if let ParsedCriAttribute::ExtensionRequest(requested) = &attr.parsed_attribute() { Some(requested.extensions.iter().collect::>()) } else { None } }) .expect("missing requested extensions"); // We should find the custom extension with expected criticality and value. let custom_ext = exts .iter() .find(|ext| ext.oid == test_oid) .expect("missing requested custom extension"); assert_eq!(custom_ext.critical, true); assert_eq!(custom_ext.value, test_ext); } } #[cfg(feature = "x509-parser")] mod test_x509_parser_crl { use crate::util; use x509_parser::num_bigint::BigUint; use x509_parser::prelude::{FromDer, X509Certificate}; use x509_parser::revocation_list::CertificateRevocationList; use x509_parser::x509::X509Version; #[test] fn parse_crl() { // Create a CRL with one revoked cert, and an issuer to sign the CRL. let (crl, issuer) = util::test_crl(); let revoked_cert = crl.get_params().revoked_certs.first().unwrap(); let revoked_cert_serial = BigUint::from_bytes_be(revoked_cert.serial_number.as_ref()); let issuer_der = issuer.serialize_der().unwrap(); let (_, x509_issuer) = X509Certificate::from_der(&issuer_der).unwrap(); // Serialize the CRL signed by the issuer in DER form. let crl_der = crl.serialize_der_with_signer(&issuer).unwrap(); // We should be able to parse the CRL with x509-parser without error. let (_, x509_crl) = CertificateRevocationList::from_der(&crl_der).expect("failed to parse CRL DER"); // The properties of the CRL should match expected. assert_eq!(x509_crl.version().unwrap(), X509Version(1)); assert_eq!(x509_crl.issuer(), x509_issuer.subject()); assert_eq!( x509_crl.last_update().to_datetime().unix_timestamp(), crl.get_params().this_update.unix_timestamp() ); assert_eq!( x509_crl .next_update() .unwrap() .to_datetime() .unix_timestamp(), crl.get_params().next_update.unix_timestamp() ); // TODO: Waiting on x509-parser 0.15.1 to be released. // let crl_number = BigUint::from_bytes_be(crl.get_params().crl_number.as_ref()); // assert_eq!(x509_crl.crl_number().unwrap(), &crl_number); // We should find the expected revoked certificate serial with the correct reason code. let x509_revoked_cert = x509_crl .iter_revoked_certificates() .next() .expect("failed to find revoked cert in CRL"); assert_eq!(x509_revoked_cert.user_certificate, revoked_cert_serial); let (_, reason_code) = x509_revoked_cert.reason_code().unwrap(); assert_eq!(reason_code.0, revoked_cert.reason_code.unwrap() as u8); // The issuing distribution point extension should be present and marked critical. let issuing_dp_ext = x509_crl .extensions() .iter() .find(|ext| { ext.oid == x509_parser::oid_registry::OID_X509_EXT_ISSUER_DISTRIBUTION_POINT }) .expect("failed to find issuing distribution point extension"); assert!(issuing_dp_ext.critical); // TODO: x509-parser does not yet parse the CRL issuing DP extension for further examination. // We should be able to verify the CRL signature with the issuer. assert!(x509_crl.verify_signature(&x509_issuer.public_key()).is_ok()); } } #[cfg(feature = "x509-parser")] mod test_parse_crl_dps { use crate::util; use x509_parser::extensions::{DistributionPointName, ParsedExtension}; #[test] fn parse_crl_dps() { // Generate and parse a certificate that includes two CRL distribution points. let der = util::cert_with_crl_dps(); let (_, parsed_cert) = x509_parser::parse_x509_certificate(&der).unwrap(); // We should find a CRL DP extension was parsed. let crl_dps = parsed_cert .get_extension_unique(&x509_parser::oid_registry::OID_X509_EXT_CRL_DISTRIBUTION_POINTS) .expect("malformed CRL distribution points extension") .expect("missing CRL distribution points extension"); // The extension should not be critical. assert!(!crl_dps.critical); // We should be able to parse the definition. let crl_dps = match crl_dps.parsed_extension() { ParsedExtension::CRLDistributionPoints(crl_dps) => crl_dps, _ => panic!("unexpected parsed extension type"), }; // There should be two DPs. assert_eq!(crl_dps.points.len(), 2); // Each distribution point should only include a distribution point name holding a sequence // of general names. let general_names = crl_dps .points .iter() .flat_map(|dp| { // We shouldn't find a cRLIssuer or onlySomeReasons field. assert!(dp.crl_issuer.is_none()); assert!(dp.reasons.is_none()); match dp .distribution_point .as_ref() .expect("missing distribution point name") { DistributionPointName::FullName(general_names) => general_names.iter(), DistributionPointName::NameRelativeToCRLIssuer(_) => { panic!("unexpected name relative to cRL issuer") }, } }) .collect::>(); // All of the general names should be URIs. let uris = general_names .iter() .map(|general_name| match general_name { x509_parser::extensions::GeneralName::URI(uri) => *uri, _ => panic!("unexpected general name type"), }) .collect::>(); // We should find the expected URIs. assert_eq!( uris, &[ "http://example.com/crl.der", "http://crls.example.com/1234", "ldap://example.com/crl.der" ] ); } } rcgen-0.11.3/tests/openssl.rs000064400000000000000000000345171046102023000142110ustar 00000000000000#![cfg(feature = "pem")] use openssl::asn1::{Asn1Integer, Asn1Time}; use openssl::bn::BigNum; use openssl::pkey::PKey; use openssl::ssl::{HandshakeError, SslAcceptor, SslConnector, SslMethod}; use openssl::stack::Stack; use openssl::x509::store::{X509Store, X509StoreBuilder}; use openssl::x509::{CrlStatus, X509Crl, X509Req, X509StoreContext, X509}; use rcgen::{ BasicConstraints, Certificate, CertificateParams, DnType, DnValue, GeneralSubtree, IsCa, NameConstraints, }; use std::cell::RefCell; use std::io::{Error, ErrorKind, Read, Result as ioResult, Write}; use std::rc::Rc; mod util; fn verify_cert_basic(cert: &Certificate) { let cert_pem = cert.serialize_pem().unwrap(); println!("{cert_pem}"); let x509 = X509::from_pem(&cert_pem.as_bytes()).unwrap(); let mut builder = X509StoreBuilder::new().unwrap(); builder.add_cert(x509.clone()).unwrap(); let store: X509Store = builder.build(); let mut ctx = X509StoreContext::new().unwrap(); let mut stack = Stack::new().unwrap(); stack.push(x509.clone()).unwrap(); ctx.init(&store, &x509, &stack.as_ref(), |ctx| { ctx.verify_cert().unwrap(); Ok(()) }) .unwrap(); } // TODO implement Debug manually instead of // deriving it #[derive(Debug)] struct PipeInner([Vec; 2]); #[derive(Debug)] struct PipeEnd { read_pos: usize, /// Which end of the pipe end_idx: usize, inner: Rc>, } fn create_pipe() -> (PipeEnd, PipeEnd) { let pipe_inner = PipeInner([Vec::new(), Vec::new()]); let inner = Rc::new(RefCell::new(pipe_inner)); ( PipeEnd { read_pos: 0, end_idx: 0, inner: inner.clone(), }, PipeEnd { read_pos: 0, end_idx: 1, inner, }, ) } impl Write for PipeEnd { fn write(&mut self, buf: &[u8]) -> ioResult { self.inner.borrow_mut().0[self.end_idx].extend_from_slice(buf); Ok(buf.len()) } fn flush(&mut self) -> ioResult<()> { Ok(()) } } impl Read for PipeEnd { fn read(&mut self, mut buf: &mut [u8]) -> ioResult { let inner = self.inner.borrow_mut(); let r_sl = &inner.0[1 - self.end_idx][self.read_pos..]; if r_sl.len() == 0 { return Err(Error::new(ErrorKind::WouldBlock, "oh no!")); } let r = buf.len().min(r_sl.len()); std::io::copy(&mut &r_sl[..r], &mut buf)?; self.read_pos += r; Ok(r) } } fn verify_cert(cert: &Certificate) { verify_cert_basic(cert); let cert_pem = cert.serialize_pem().unwrap(); let key = cert.serialize_private_key_der(); verify_cert_ca(&cert_pem, &key, &cert_pem); } fn verify_cert_ca(cert_pem: &str, key: &[u8], ca_cert_pem: &str) { println!("{cert_pem}"); println!("{ca_cert_pem}"); let x509 = X509::from_pem(&cert_pem.as_bytes()).unwrap(); let ca_x509 = X509::from_pem(&ca_cert_pem.as_bytes()).unwrap(); let mut builder = X509StoreBuilder::new().unwrap(); builder.add_cert(ca_x509).unwrap(); let store: X509Store = builder.build(); let srv = SslMethod::tls_server(); let mut ssl_srv_ctx = SslAcceptor::mozilla_modern(srv).unwrap(); //let key = cert.serialize_private_key_der(); let pkey = PKey::private_key_from_der(&key).unwrap(); ssl_srv_ctx.set_private_key(&pkey).unwrap(); ssl_srv_ctx.set_certificate(&x509).unwrap(); let cln = SslMethod::tls_client(); let mut ssl_cln_ctx = SslConnector::builder(cln).unwrap(); ssl_cln_ctx.set_cert_store(store); let ssl_srv_ctx = ssl_srv_ctx.build(); let ssl_cln_ctx = ssl_cln_ctx.build(); let (pipe_end_1, pipe_end_2) = create_pipe(); let (mut ssl_srv_stream, mut ssl_cln_stream) = { let mut srv_res = ssl_srv_ctx.accept(pipe_end_1); let mut cln_res = ssl_cln_ctx.connect("crabs.crabs", pipe_end_2); let mut ready = 0u8; let mut iter_budget = 100; loop { match cln_res { Ok(_) => ready |= 2, Err(HandshakeError::WouldBlock(mh)) => cln_res = mh.handshake(), Err(e) => panic!("Error: {:?}", e), } match srv_res { Ok(_) => ready |= 1, Err(HandshakeError::WouldBlock(mh)) => srv_res = mh.handshake(), Err(e) => panic!("Error: {:?}", e), } if ready == 3 { break (cln_res.unwrap(), srv_res.unwrap()); } if iter_budget == 0 { panic!("iter budget exhausted"); } iter_budget -= 1; } }; const HELLO_FROM_SRV: &[u8] = b"hello from server"; const HELLO_FROM_CLN: &[u8] = b"hello from client"; ssl_srv_stream.ssl_write(HELLO_FROM_SRV).unwrap(); ssl_cln_stream.ssl_write(HELLO_FROM_CLN).unwrap(); // TODO read the data we just wrote from the streams } fn verify_csr(cert: &Certificate) { let csr = cert.serialize_request_pem().unwrap(); println!("{csr}"); let key = cert.serialize_private_key_der(); let pkey = PKey::private_key_from_der(&key).unwrap(); let req = X509Req::from_pem(&csr.as_bytes()).unwrap(); req.verify(&pkey).unwrap(); } #[test] fn test_openssl() { let params = util::default_params(); let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. verify_cert(&cert); } #[test] fn test_request() { let params = util::default_params(); let cert = Certificate::from_params(params).unwrap(); verify_csr(&cert); } #[test] fn test_openssl_256() { let mut params = util::default_params(); params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. verify_cert(&cert); verify_csr(&cert); } #[test] fn test_openssl_384() { let mut params = util::default_params(); params.alg = &rcgen::PKCS_ECDSA_P384_SHA384; let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. verify_cert(&cert); verify_csr(&cert); } #[test] fn test_openssl_25519() { let mut params = util::default_params(); params.alg = &rcgen::PKCS_ED25519; let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. // TODO openssl doesn't support v2 keys (yet) // https://github.com/est31/rcgen/issues/11 // https://github.com/openssl/openssl/issues/10468 verify_cert_basic(&cert); //verify_csr(&cert); } #[test] fn test_openssl_25519_v1_given() { let mut params = util::default_params(); params.alg = &rcgen::PKCS_ED25519; let kp = rcgen::KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V1).unwrap(); params.key_pair = Some(kp); let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate as well as CSR, // but only on OpenSSL >= 1.1.1 // On prior versions, only do basic verification if openssl::version::number() >= 0x1_01_01_00_f { verify_cert(&cert); verify_csr(&cert); } else { verify_cert_basic(&cert); } } #[test] fn test_openssl_25519_v2_given() { let mut params = util::default_params(); params.alg = &rcgen::PKCS_ED25519; let kp = rcgen::KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V2).unwrap(); params.key_pair = Some(kp); let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. // TODO openssl doesn't support v2 keys (yet) // https://github.com/est31/rcgen/issues/11 // https://github.com/openssl/openssl/issues/10468 verify_cert_basic(&cert); //verify_csr(&cert); } #[test] fn test_openssl_rsa_given() { let mut params = util::default_params(); params.alg = &rcgen::PKCS_RSA_SHA256; let kp = rcgen::KeyPair::from_pem(util::RSA_TEST_KEY_PAIR_PEM).unwrap(); params.key_pair = Some(kp); let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. verify_cert(&cert); verify_csr(&cert); } #[test] fn test_openssl_rsa_combinations_given() { let alg_list = [ &rcgen::PKCS_RSA_SHA256, &rcgen::PKCS_RSA_SHA384, &rcgen::PKCS_RSA_SHA512, //&rcgen::PKCS_RSA_PSS_SHA256, ]; for (i, alg) in alg_list.iter().enumerate() { let mut params = util::default_params(); params.alg = alg; let kp = rcgen::KeyPair::from_pem_and_sign_algo(util::RSA_TEST_KEY_PAIR_PEM, alg).unwrap(); params.key_pair = Some(kp); let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. if i >= 4 { verify_cert(&cert); verify_csr(&cert); } else { // The PSS key types are not fully supported. // An attempt to use them gives a handshake error. verify_cert_basic(&cert); } } } #[test] fn test_openssl_separate_ca() { let mut params = util::default_params(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); let ca_cert = Certificate::from_params(params).unwrap(); let ca_cert_pem = ca_cert.serialize_pem().unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Dev domain"); let cert = Certificate::from_params(params).unwrap(); let cert_pem = cert.serialize_pem_with_signer(&ca_cert).unwrap(); let key = cert.serialize_private_key_der(); verify_cert_ca(&cert_pem, &key, &ca_cert_pem); } #[test] fn test_openssl_separate_ca_with_printable_string() { let mut params = util::default_params(); params.distinguished_name.push( DnType::CountryName, DnValue::PrintableString("US".to_string()), ); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); let ca_cert = Certificate::from_params(params).unwrap(); let ca_cert_pem = ca_cert.serialize_pem().unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Dev domain"); let cert = Certificate::from_params(params).unwrap(); let cert_pem = cert.serialize_pem_with_signer(&ca_cert).unwrap(); let key = cert.serialize_private_key_der(); verify_cert_ca(&cert_pem, &key, &ca_cert_pem); } #[test] fn test_openssl_separate_ca_with_other_signing_alg() { let mut params = util::default_params(); params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); let ca_cert = Certificate::from_params(params).unwrap(); let ca_cert_pem = ca_cert.serialize_pem().unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params.alg = &rcgen::PKCS_ECDSA_P384_SHA384; params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Dev domain"); let cert = Certificate::from_params(params).unwrap(); let cert_pem = cert.serialize_pem_with_signer(&ca_cert).unwrap(); let key = cert.serialize_private_key_der(); verify_cert_ca(&cert_pem, &key, &ca_cert_pem); } #[test] fn test_openssl_separate_ca_name_constraints() { let mut params = util::default_params(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); println!("openssl version: {:x}", openssl::version::number()); params.name_constraints = Some(NameConstraints { permitted_subtrees: vec![GeneralSubtree::DnsName("crabs.crabs".to_string())], //permitted_subtrees : vec![GeneralSubtree::DnsName("".to_string())], //permitted_subtrees : Vec::new(), //excluded_subtrees : vec![GeneralSubtree::DnsName(".v".to_string())], excluded_subtrees: Vec::new(), }); let ca_cert = Certificate::from_params(params).unwrap(); let ca_cert_pem = ca_cert.serialize_pem().unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Dev domain"); let cert = Certificate::from_params(params).unwrap(); let cert_pem = cert.serialize_pem_with_signer(&ca_cert).unwrap(); let key = cert.serialize_private_key_der(); verify_cert_ca(&cert_pem, &key, &ca_cert_pem); } #[test] fn test_openssl_crl_parse() { // Create a CRL with one revoked cert, and an issuer to sign the CRL. let (crl, issuer) = util::test_crl(); let revoked_cert = crl.get_params().revoked_certs.first().unwrap(); let revoked_cert_serial = &revoked_cert.serial_number; // Serialize the CRL signed by the issuer in both PEM and DER. let crl_pem = crl.serialize_pem_with_signer(&issuer).unwrap(); let crl_der = crl.serialize_der_with_signer(&issuer).unwrap(); // We should be able to parse the PEM form without error. assert!(X509Crl::from_pem(crl_pem.as_bytes()).is_ok()); // We should also be able to parse the DER form without error. let openssl_crl = X509Crl::from_der(&crl_der).expect("failed to parse CRL DER"); // The properties of the CRL should match expected. let openssl_issuer = X509::from_der(&issuer.serialize_der().unwrap()).unwrap(); let expected_last_update = Asn1Time::from_unix(crl.get_params().this_update.unix_timestamp()).unwrap(); assert!(openssl_crl.last_update().eq(&expected_last_update)); let expected_next_update = Asn1Time::from_unix(crl.get_params().next_update.unix_timestamp()).unwrap(); assert!(openssl_crl.next_update().unwrap().eq(&expected_next_update)); assert!(matches!( openssl_crl .issuer_name() .try_cmp(openssl_issuer.issuer_name()) .unwrap(), core::cmp::Ordering::Equal )); // We should find the revoked certificate is revoked. let openssl_serial = BigNum::from_slice(revoked_cert_serial.as_ref()).unwrap(); let openssl_serial = Asn1Integer::from_bn(&openssl_serial).unwrap(); let openssl_crl_status = openssl_crl.get_by_serial(&openssl_serial); assert!(matches!(openssl_crl_status, CrlStatus::Revoked(_))); // We should be able to verify the CRL signature with the issuer's public key. let issuer_pkey = openssl_issuer.public_key().unwrap(); assert!(openssl_crl .verify(&issuer_pkey) .expect("failed to verify CRL signature")); } #[test] fn test_openssl_crl_dps_parse() { // Generate and parse a certificate that includes two CRL distribution points. let der = util::cert_with_crl_dps(); let cert = X509::from_der(&der).expect("failed to parse cert DER"); // We should find the CRL DPs extension. let dps = cert .crl_distribution_points() .expect("missing crl distribution points extension"); assert!(!dps.is_empty()); // We should find two distribution points, each with a distribution point name containing // a full name sequence of general names. let general_names = dps .iter() .flat_map(|dp| { dp.distpoint() .expect("distribution point missing distribution point name") .fullname() .expect("distribution point name missing general names") .iter() }) .collect::>(); // Each general name should be a URI name. let uris = general_names .iter() .map(|general_name| { general_name .uri() .expect("general name is not a directory name") }) .collect::>(); // We should find the expected URIs. assert_eq!( uris, &[ "http://example.com/crl.der", "http://crls.example.com/1234", "ldap://example.com/crl.der" ] ); } rcgen-0.11.3/tests/util.rs000064400000000000000000000121301046102023000134660ustar 00000000000000use rcgen::{BasicConstraints, Certificate, CertificateParams}; use rcgen::{ CertificateRevocationList, CrlDistributionPoint, CrlIssuingDistributionPoint, CrlScope, }; use rcgen::{CertificateRevocationListParams, DnType, IsCa, KeyIdMethod}; use rcgen::{ KeyUsagePurpose, RevocationReason, RevokedCertParams, SerialNumber, PKCS_ECDSA_P256_SHA256, }; use time::{Duration, OffsetDateTime}; // Generated by adding `println!("{}", cert.serialize_private_key_pem());` // to the test_webpki_25519 test and panicing explicitly. // This is a "v2" key containing the public key as well as the // private one. #[allow(unused)] pub const ED25519_TEST_KEY_PAIR_PEM_V2: &str = r#" -----BEGIN PRIVATE KEY----- MFMCAQEwBQYDK2VwBCIEIC2pHJYjFHhK8V7mj6BnHWUVMS4CRolUlDdRXKCtguDu oSMDIQDrvH/x8Nx9untsuc6ET+ce3w7PSuLY8BLWcHdXDGvkQA== -----END PRIVATE KEY----- "#; // Generated with `openssl genpkey -algorithm ED25519` // A "v1" key as it doesn't contain the public key (which can be // derived from the private one) #[allow(unused)] pub const ED25519_TEST_KEY_PAIR_PEM_V1: &str = r#" -----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEIDSat0MacDt2fokpnzuBaXvAQR6RJGS9rgIYOU2mZKld -----END PRIVATE KEY----- "#; /* Generated by: openssl genpkey -algorithm RSA \ -pkeyopt rsa_keygen_bits:2048 \ -pkeyopt rsa_keygen_pubexp:65537 | \ openssl pkcs8 -topk8 -nocrypt -outform pem */ #[cfg(feature = "pem")] pub const RSA_TEST_KEY_PAIR_PEM: &str = r#" -----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDYjmgyV3/LSizJ XrYrATZrrPr2Edo8yiOgBLFmi4sgeGdQ5n6nhjTGfBEIP2Ia6z+hbiGOMncabEBc zkdME+JFYVCSkS7r4ivMOzp2egxLgcPKcerBoXI8DUbHhIR9z89lHiPHDJv3+d0A c1b9bz9b8OAeZWiQmFvmjpbc2DfhQ2OFx2MwFZCYF196rrXOc6/SR2esZVRrkW22 RBKFTgz6GIA5A/5VWKIISSqEB1gOcMz2iq5987I28+Ez4rcLZ2lB7cZ7TbNxkAwt 0fPL+EuyP7XOzbIj4/kSAlU5xfwNERa3BEuOFro4i5EmSDj+lR5xdRpFnx0j5zOo zUL2lHG9AgMBAAECggEARpV8DtSIOcmOeYAeXjwB8eyqy+Obv26fV/vPmr3m9glo m2zVYWMT9pHft1F5d46v6b0MwN1gBsO74sP1Zy2f9b83VN5vbcEFR4cSkiVLtpyw JV8mBkDKDBrDtCpUSPGgBrRhMvLAL35Ic2oks2w8OYp0clPZVi/i3G4jbA4pgIkt yB6k79Uhzz2nfZ0VpPORGNsBOl5UK1LkmIhTJ6S0LsLj7XSet9YHR0k0F0/NOSzz +jMUzfjOPm8M+b3wk9yAQP7qT9Iy3MHbGAad4gNXGu1LqeDRkfmM5pnoG0ASP3+B IvX2l0ZLeCtg+GRLlGvUVI1HSQHCsuiC6/g2bq7JAQKBgQD3/Eb58VjpdwJYPrg/ srfnC9sKSf5C0Q8YSmkfvOmeD6Vqe0EXRuMyhwTkkVdz04yPiB2j0fXdeB9h16Ic 9HWb/UNGWNpV7Ul1MSHbeu32Xor+5IkqCGgSoMznlt9QPR4PxfIOgO8cVL1HgNAZ JnBDzhTG0FfY75hqpCDmFGAZwQKBgQDfjhk5aM0yGLYgZfw/K9BrwjctQBWdrps2 4TtkG7Kuj0hsimCdrqJQ5JN8aUM41zDUr3Px1uN5gUAZ3dE9DoGsgj15ZwgVkAMM E54bfzOqkbh+mRpptIxL4HmHB45vgvz0YljeRoOEQvPF/OSGLti7VIkD4898PFKl cU+P9m5+/QKBgDi8XTi+AQuZEM5Duz/Hkc+opLqb5zI+RmfWTmrWe9SP29aa0G+U 5lIfFf19SzbSxavpBm7+kHPVEcj+3rYlL+s6bHPhzEIwgcfwL8DZRSxCwSZD/yXA up7Yb0jk+b6P3RravOCYmxwuPwfm7rVyV+kLczFxZUfauVJcrrI1Iy+BAoGBAJjG MEDGeSxaLOS5LYgyNg3ePPzkhaEruRDpHUBNmW+npZPfgSVhObXUb2IfQXwvu0Qt 3yuPcgcQKDFFIH/8UOwGWWKE4cZyk1KGeY9K/5D6Yr3JfX5tj08vSX3Y0SMtvhZ4 u0izoZ8abiOIrtdwXlau76/D2ICLbON5Kykz/NE1AoGAId2+pO9p8jBt9l+5jZo7 Rw/mb5icMaG2hqAzs37gUPbpSwQFOmGhQmNM+WvYEvUUuiTxI3AOeEK8Mj+BVB4+ uE3X/fWK/JR9iOzH9OM31Nua8/EJzr7BmUpXeRr4dAtVimeQ+5HY6IgRsFGPKKwv YPTHy8SWRA2sMII3ArhHJ8A= -----END PRIVATE KEY----- "#; pub fn default_params() -> CertificateParams { let mut params = CertificateParams::new(vec!["crabs.crabs".to_string(), "localhost".to_string()]); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Master CA"); params } #[allow(unused)] // Used by openssl + x509-parser features. pub fn test_crl() -> (CertificateRevocationList, Certificate) { let mut issuer = default_params(); issuer.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); issuer.key_usages = vec![ KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::DigitalSignature, KeyUsagePurpose::CrlSign, ]; let issuer = Certificate::from_params(issuer).unwrap(); let now = OffsetDateTime::now_utc(); let next_week = now + Duration::weeks(1); let revoked_cert = RevokedCertParams { serial_number: SerialNumber::from_slice(&[0x00, 0xC0, 0xFF, 0xEE]), revocation_time: now, reason_code: Some(RevocationReason::KeyCompromise), invalidity_date: None, }; let crl = CertificateRevocationListParams { this_update: now, next_update: next_week, crl_number: SerialNumber::from(1234), issuing_distribution_point: Some(CrlIssuingDistributionPoint { distribution_point: CrlDistributionPoint { uris: vec!["http://example.com/crl".to_string()], }, scope: Some(CrlScope::UserCertsOnly), }), revoked_certs: vec![revoked_cert], alg: &PKCS_ECDSA_P256_SHA256, key_identifier_method: KeyIdMethod::Sha256, }; let crl = CertificateRevocationList::from_params(crl).unwrap(); (crl, issuer) } #[allow(unused)] // Used by openssl + x509-parser features. pub fn cert_with_crl_dps() -> Vec { let mut params = default_params(); params.crl_distribution_points = vec![ CrlDistributionPoint { uris: vec![ "http://example.com/crl.der".to_string(), "http://crls.example.com/1234".to_string(), ], }, CrlDistributionPoint { uris: vec!["ldap://example.com/crl.der".to_string()], }, ]; let cert = Certificate::from_params(params).unwrap(); cert.serialize_der().unwrap() } rcgen-0.11.3/tests/webpki.rs000064400000000000000000000504071046102023000140030ustar 00000000000000use rcgen::{ BasicConstraints, Certificate, CertificateParams, DnType, IsCa, KeyPair, RemoteKeyPair, }; use rcgen::{ CertificateRevocationList, CertificateRevocationListParams, RevocationReason, RevokedCertParams, }; #[cfg(feature = "x509-parser")] use rcgen::{CertificateSigningRequest, DnValue}; use rcgen::{ExtendedKeyUsagePurpose, KeyUsagePurpose, SerialNumber}; use webpki::SignatureAlgorithm; use webpki::{ BorrowedCertRevocationList, CertRevocationList, EndEntityCert, KeyUsage, TrustAnchor, }; use webpki::{DnsNameRef, Time}; use ring::rand::SystemRandom; use ring::signature::{self, EcdsaKeyPair, EcdsaSigningAlgorithm, Ed25519KeyPair, KeyPair as _}; #[cfg(feature = "pem")] use ring::signature::{RsaEncoding, RsaKeyPair}; use std::convert::TryFrom; use time::{Duration, OffsetDateTime}; mod util; fn sign_msg_ecdsa(cert: &Certificate, msg: &[u8], alg: &'static EcdsaSigningAlgorithm) -> Vec { let pk_der = cert.serialize_private_key_der(); let key_pair = EcdsaKeyPair::from_pkcs8(&alg, &pk_der).unwrap(); let system_random = SystemRandom::new(); let signature = key_pair.sign(&system_random, &msg).unwrap(); signature.as_ref().to_vec() } fn sign_msg_ed25519(cert: &Certificate, msg: &[u8]) -> Vec { let pk_der = cert.serialize_private_key_der(); let key_pair = Ed25519KeyPair::from_pkcs8_maybe_unchecked(&pk_der).unwrap(); let signature = key_pair.sign(&msg); signature.as_ref().to_vec() } #[cfg(feature = "pem")] fn sign_msg_rsa(cert: &Certificate, msg: &[u8], encoding: &'static dyn RsaEncoding) -> Vec { let pk_der = cert.serialize_private_key_der(); let key_pair = RsaKeyPair::from_pkcs8(&pk_der).unwrap(); let system_random = SystemRandom::new(); let mut signature = vec![0; key_pair.public_modulus_len()]; key_pair .sign(encoding, &system_random, &msg, &mut signature) .unwrap(); signature } fn check_cert<'a, 'b>( cert_der: &[u8], cert: &'a Certificate, alg: &SignatureAlgorithm, sign_fn: impl FnOnce(&'a Certificate, &'b [u8]) -> Vec, ) { #[cfg(feature = "pem")] { println!("{}", cert.serialize_pem().unwrap()); } check_cert_ca(cert_der, cert, cert_der, alg, alg, sign_fn); } fn check_cert_ca<'a, 'b>( cert_der: &[u8], cert: &'a Certificate, ca_der: &[u8], cert_alg: &SignatureAlgorithm, ca_alg: &SignatureAlgorithm, sign_fn: impl FnOnce(&'a Certificate, &'b [u8]) -> Vec, ) { let trust_anchor = TrustAnchor::try_from_cert_der(&ca_der).unwrap(); let trust_anchor_list = &[trust_anchor]; let end_entity_cert = EndEntityCert::try_from(cert_der).unwrap(); // Set time to Jan 10, 2004 let time = Time::from_seconds_since_unix_epoch(0x40_00_00_00); // (1/3) Check whether the cert is valid end_entity_cert .verify_for_usage( &[&cert_alg, &ca_alg], &trust_anchor_list[..], &[], time, KeyUsage::server_auth(), &[], ) .expect("valid TLS server cert"); // (2/3) Check that the cert is valid for the given DNS name let dns_name = DnsNameRef::try_from_ascii_str("crabs.crabs").unwrap(); end_entity_cert .verify_is_valid_for_subject_name(webpki::SubjectNameRef::from(dns_name)) .expect("valid for DNS name"); // (3/3) Check that a message signed by the cert is valid. let msg = b"Hello, World! This message is signed."; let signature = sign_fn(&cert, msg); end_entity_cert .verify_signature(&cert_alg, msg, &signature) .expect("signature is valid"); } #[test] fn test_webpki() { let params = util::default_params(); let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. let cert_der = cert.serialize_der().unwrap(); let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); check_cert(&cert_der, &cert, &webpki::ECDSA_P256_SHA256, sign_fn); } #[test] fn test_webpki_256() { let mut params = util::default_params(); params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. let cert_der = cert.serialize_der().unwrap(); let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); check_cert(&cert_der, &cert, &webpki::ECDSA_P256_SHA256, sign_fn); } #[test] fn test_webpki_384() { let mut params = util::default_params(); params.alg = &rcgen::PKCS_ECDSA_P384_SHA384; let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. let cert_der = cert.serialize_der().unwrap(); let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P384_SHA384_ASN1_SIGNING); check_cert(&cert_der, &cert, &webpki::ECDSA_P384_SHA384, sign_fn); } #[test] fn test_webpki_25519() { let mut params = util::default_params(); params.alg = &rcgen::PKCS_ED25519; let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. let cert_der = cert.serialize_der().unwrap(); check_cert(&cert_der, &cert, &webpki::ED25519, &sign_msg_ed25519); } #[cfg(feature = "pem")] #[test] fn test_webpki_25519_v1_given() { let mut params = util::default_params(); params.alg = &rcgen::PKCS_ED25519; let kp = rcgen::KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V1).unwrap(); params.key_pair = Some(kp); let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. let cert_der = cert.serialize_der().unwrap(); check_cert(&cert_der, &cert, &webpki::ED25519, &sign_msg_ed25519); } #[cfg(feature = "pem")] #[test] fn test_webpki_25519_v2_given() { let mut params = util::default_params(); params.alg = &rcgen::PKCS_ED25519; let kp = rcgen::KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V2).unwrap(); params.key_pair = Some(kp); let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. let cert_der = cert.serialize_der().unwrap(); check_cert(&cert_der, &cert, &webpki::ED25519, &sign_msg_ed25519); } #[cfg(feature = "pem")] #[test] fn test_webpki_rsa_given() { let mut params = util::default_params(); params.alg = &rcgen::PKCS_RSA_SHA256; let kp = rcgen::KeyPair::from_pem(util::RSA_TEST_KEY_PAIR_PEM).unwrap(); params.key_pair = Some(kp); let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. let cert_der = cert.serialize_der().unwrap(); check_cert( &cert_der, &cert, &webpki::RSA_PKCS1_2048_8192_SHA256, |msg, cert| sign_msg_rsa(msg, cert, &signature::RSA_PKCS1_SHA256), ); } #[cfg(feature = "pem")] #[test] fn test_webpki_rsa_combinations_given() { let configs: &[(_, _, &'static dyn signature::RsaEncoding)] = &[ ( &rcgen::PKCS_RSA_SHA256, &webpki::RSA_PKCS1_2048_8192_SHA256, &signature::RSA_PKCS1_SHA256, ), ( &rcgen::PKCS_RSA_SHA384, &webpki::RSA_PKCS1_2048_8192_SHA384, &signature::RSA_PKCS1_SHA384, ), ( &rcgen::PKCS_RSA_SHA512, &webpki::RSA_PKCS1_2048_8192_SHA512, &signature::RSA_PKCS1_SHA512, ), //(&rcgen::PKCS_RSA_PSS_SHA256, &webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY, &signature::RSA_PSS_SHA256), ]; for c in configs { let mut params = util::default_params(); params.alg = c.0; let kp = rcgen::KeyPair::from_pem_and_sign_algo(util::RSA_TEST_KEY_PAIR_PEM, c.0).unwrap(); params.key_pair = Some(kp); let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. let cert_der = cert.serialize_der().unwrap(); check_cert(&cert_der, &cert, c.1, |msg, cert| { sign_msg_rsa(msg, cert, c.2) }); } } #[test] fn test_webpki_separate_ca() { let mut params = util::default_params(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); let ca_cert = Certificate::from_params(params).unwrap(); let ca_der = ca_cert.serialize_der().unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Dev domain"); let cert = Certificate::from_params(params).unwrap(); let cert_der = cert.serialize_der_with_signer(&ca_cert).unwrap(); let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); check_cert_ca( &cert_der, &cert, &ca_der, &webpki::ECDSA_P256_SHA256, &webpki::ECDSA_P256_SHA256, sign_fn, ); } #[test] fn test_webpki_separate_ca_with_other_signing_alg() { let mut params = util::default_params(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; let ca_cert = Certificate::from_params(params).unwrap(); let ca_der = ca_cert.serialize_der().unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params.alg = &rcgen::PKCS_ED25519; params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Dev domain"); let cert = Certificate::from_params(params).unwrap(); let cert_der = cert.serialize_der_with_signer(&ca_cert).unwrap(); check_cert_ca( &cert_der, &cert, &ca_der, &webpki::ED25519, &webpki::ECDSA_P256_SHA256, sign_msg_ed25519, ); } #[test] fn from_remote() { struct Remote(EcdsaKeyPair); impl RemoteKeyPair for Remote { fn public_key(&self) -> &[u8] { self.0.public_key().as_ref() } fn sign(&self, msg: &[u8]) -> Result, rcgen::RcgenError> { let system_random = SystemRandom::new(); self.0 .sign(&system_random, msg) .map(|s| s.as_ref().to_owned()) .map_err(rcgen::RcgenError::from) } fn algorithm(&self) -> &'static rcgen::SignatureAlgorithm { &rcgen::PKCS_ECDSA_P256_SHA256 } } let key_pair = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap(); let remote = EcdsaKeyPair::from_pkcs8( &signature::ECDSA_P256_SHA256_ASN1_SIGNING, &key_pair.serialize_der(), ) .unwrap(); let key_pair = EcdsaKeyPair::from_pkcs8( &signature::ECDSA_P256_SHA256_ASN1_SIGNING, &key_pair.serialize_der(), ) .unwrap(); let remote = KeyPair::from_remote(Box::new(Remote(remote))).unwrap(); let mut params = util::default_params(); params.alg = &rcgen::PKCS_ECDSA_P256_SHA256; params.key_pair = Some(remote); let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. let cert_der = cert.serialize_der().unwrap(); let sign_fn = move |_, msg| { let system_random = SystemRandom::new(); let signature = key_pair.sign(&system_random, msg).unwrap(); signature.as_ref().to_vec() }; check_cert(&cert_der, &cert, &webpki::ECDSA_P256_SHA256, sign_fn); } /* // TODO https://github.com/briansmith/webpki/issues/134 // TODO https://github.com/briansmith/webpki/issues/135 #[test] fn test_webpki_separate_ca_name_constraints() { let mut params = util::default_params(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); params.name_constraints = Some(NameConstraints { // TODO also add a test with non-empty permitted_subtrees that // doesn't contain a DirectoryName entry. This isn't possible // currently due to a limitation of webpki. permitted_subtrees : vec![GeneralSubtree::DnsName("dev".to_string()), GeneralSubtree::DirectoryName(rcgen::DistinguishedName::new())], //permitted_subtrees : vec![GeneralSubtree::DnsName("dev".to_string())], //permitted_subtrees : Vec::new(), //excluded_subtrees : vec![GeneralSubtree::DnsName("v".to_string())], excluded_subtrees : Vec::new(), }); let ca_cert = Certificate::from_params(params).unwrap(); println!("{}", ca_cert.serialize_pem().unwrap()); let ca_der = ca_cert.serialize_der().unwrap(); let mut params = CertificateParams::new(vec!["crabs.dev".to_string()]); params.distinguished_name = rcgen::DistinguishedName::new(); //params.distinguished_name.push(DnType::OrganizationName, "Crab widgits SE"); //params.distinguished_name.push(DnType::CommonName, "Dev domain"); let cert = Certificate::from_params(params).unwrap(); let cert_der = cert.serialize_der_with_signer(&ca_cert).unwrap(); println!("{}", cert.serialize_pem_with_signer(&ca_cert).unwrap()); let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); check_cert_ca(&cert_der, &cert, &ca_der, &webpki::ECDSA_P256_SHA256, &webpki::ECDSA_P256_SHA256, sign_fn); } */ #[cfg(feature = "x509-parser")] #[test] fn test_webpki_imported_ca() { use std::convert::TryInto; let mut params = util::default_params(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); let ca_cert = Certificate::from_params(params).unwrap(); let (ca_cert_der, ca_key_der) = ( ca_cert.serialize_der().unwrap(), ca_cert.serialize_private_key_der(), ); let ca_key_pair = ca_key_der.as_slice().try_into().unwrap(); let imported_ca_cert_params = CertificateParams::from_ca_cert_der(ca_cert_der.as_slice(), ca_key_pair).unwrap(); let imported_ca_cert = Certificate::from_params(imported_ca_cert_params).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Dev domain"); let cert = Certificate::from_params(params).unwrap(); let cert_der = cert.serialize_der_with_signer(&imported_ca_cert).unwrap(); let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); check_cert_ca( &cert_der, &cert, &ca_cert_der, &webpki::ECDSA_P256_SHA256, &webpki::ECDSA_P256_SHA256, sign_fn, ); } #[cfg(feature = "x509-parser")] #[test] fn test_webpki_imported_ca_with_printable_string() { use std::convert::TryInto; let mut params = util::default_params(); params.distinguished_name.push( DnType::CountryName, DnValue::PrintableString("US".to_string()), ); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); let ca_cert = Certificate::from_params(params).unwrap(); let (ca_cert_der, ca_key_der) = ( ca_cert.serialize_der().unwrap(), ca_cert.serialize_private_key_der(), ); let ca_key_pair = ca_key_der.as_slice().try_into().unwrap(); let imported_ca_cert_params = CertificateParams::from_ca_cert_der(ca_cert_der.as_slice(), ca_key_pair).unwrap(); let imported_ca_cert = Certificate::from_params(imported_ca_cert_params).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Dev domain"); let cert = Certificate::from_params(params).unwrap(); let cert_der = cert.serialize_der_with_signer(&imported_ca_cert).unwrap(); let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); check_cert_ca( &cert_der, &cert, &ca_cert_der, &webpki::ECDSA_P256_SHA256, &webpki::ECDSA_P256_SHA256, sign_fn, ); } #[cfg(feature = "x509-parser")] #[test] fn test_certificate_from_csr() { let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Dev domain"); let cert = Certificate::from_params(params).unwrap(); let csr_der = cert.serialize_request_der().unwrap(); let csr = CertificateSigningRequest::from_der(&csr_der).unwrap(); let mut params = util::default_params(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); let ca_cert = Certificate::from_params(params).unwrap(); let ca_cert_der = ca_cert.serialize_der().unwrap(); let cert_der = csr.serialize_der_with_signer(&ca_cert).unwrap(); let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); check_cert_ca( &cert_der, &cert, &ca_cert_der, &webpki::ECDSA_P256_SHA256, &webpki::ECDSA_P256_SHA256, sign_fn, ); } #[test] fn test_webpki_serial_number() { let mut params = util::default_params(); params.serial_number = Some(vec![0, 1, 2].into()); let cert = Certificate::from_params(params).unwrap(); // Now verify the certificate. let cert_der = cert.serialize_der().unwrap(); let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); check_cert(&cert_der, &cert, &webpki::ECDSA_P256_SHA256, sign_fn); } #[test] fn test_webpki_crl_parse() { // Create a CRL with one revoked cert, and an issuer to sign the CRL. let (crl, issuer) = util::test_crl(); let revoked_cert = crl.get_params().revoked_certs.first().unwrap(); // Serialize the CRL signed by the issuer to DER. let der = crl.serialize_der_with_signer(&issuer).unwrap(); // We should be able to parse the CRL DER without error. let webpki_crl = BorrowedCertRevocationList::from_der(&der).expect("failed to parse CRL DER"); // Webpki represents certificate SPKIs internally without the outer SEQUENCE. // We remove that here before calling verify_signature. let issuer_spki = issuer.get_key_pair().public_key_der(); let raw_spki = yasna::parse_der(&issuer_spki, |reader| reader.read_tagged_der()).unwrap(); // We should be able to verify the CRL signature with the issuer's raw SPKI. webpki_crl .verify_signature(&[&webpki::ECDSA_P256_SHA256], &raw_spki.value()) .expect("failed to validate CRL signature"); // We should be able to find the revoked cert with the expected properties. let webpki_revoked_cert = webpki_crl .find_serial(&revoked_cert.serial_number.as_ref()) .expect("failed to parse revoked certs in CRL") .expect("failed to find expected revoked cert in CRL"); assert_eq!( webpki_revoked_cert.serial_number.as_ref(), revoked_cert.serial_number.as_ref() ); assert_eq!( webpki_revoked_cert.reason_code.unwrap() as u64, revoked_cert.reason_code.unwrap() as u64 ); assert_eq!( webpki_revoked_cert.revocation_date, Time::from_seconds_since_unix_epoch(revoked_cert.revocation_time.unix_timestamp() as u64) ); } #[test] fn test_webpki_crl_revoke() { // Create an issuer CA. let alg = &rcgen::PKCS_ECDSA_P256_SHA256; let mut issuer = util::default_params(); issuer.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); issuer.key_usages = vec![ KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::DigitalSignature, KeyUsagePurpose::CrlSign, ]; issuer.alg = alg; let issuer = Certificate::from_params(issuer).unwrap(); let issuer_der = issuer.serialize_der().unwrap(); // Create an end entity cert issued by the issuer. let mut ee = util::default_params(); ee.is_ca = IsCa::NoCa; ee.extended_key_usages = vec![ExtendedKeyUsagePurpose::ClientAuth]; ee.alg = alg; ee.serial_number = Some(SerialNumber::from(99999)); let ee = Certificate::from_params(ee).unwrap(); let ee_der = ee.serialize_der_with_signer(&issuer).unwrap(); // Set up webpki's verification requirements. let trust_anchor = TrustAnchor::try_from_cert_der(issuer_der.as_ref()).unwrap(); let trust_anchor_list = &[trust_anchor]; let end_entity_cert = EndEntityCert::try_from(ee_der.as_ref()).unwrap(); let unix_time = 0x40_00_00_00; let time = Time::from_seconds_since_unix_epoch(unix_time); // The end entity cert should validate with the issuer without error. end_entity_cert .verify_for_usage( &[&webpki::ECDSA_P256_SHA256], &trust_anchor_list[..], &[], time, KeyUsage::client_auth(), &[], ) .expect("failed to validate ee cert with issuer"); // Generate a CRL with the issuer that revokes the EE cert. let now = OffsetDateTime::from_unix_timestamp(unix_time as i64).unwrap(); let crl = CertificateRevocationListParams { this_update: now, next_update: now + Duration::weeks(1), crl_number: rcgen::SerialNumber::from(1234), issuing_distribution_point: None, revoked_certs: vec![RevokedCertParams { serial_number: ee.get_params().serial_number.clone().unwrap(), revocation_time: now, reason_code: Some(RevocationReason::KeyCompromise), invalidity_date: None, }], key_identifier_method: rcgen::KeyIdMethod::Sha256, alg, }; let crl = CertificateRevocationList::from_params(crl).unwrap(); let crl_der = crl.serialize_der_with_signer(&issuer).unwrap(); let crl = BorrowedCertRevocationList::from_der(&crl_der).unwrap(); // The end entity cert should **not** validate when we provide a CRL that revokes the EE cert. let result = end_entity_cert.verify_for_usage( &[&webpki::ECDSA_P256_SHA256], &trust_anchor_list[..], &[], time, KeyUsage::client_auth(), &[&crl], ); assert!(matches!(result, Err(webpki::Error::CertRevoked))); } #[test] fn test_webpki_cert_crl_dps() { let der = util::cert_with_crl_dps(); webpki::EndEntityCert::try_from(der.as_ref()).expect("failed to parse cert with CRL DPs ext"); // Webpki doesn't expose the parsed CRL distribution extension, so we can't interrogate that // it matches the expected form. See `openssl.rs` for more extensive coverage. }