rcgen-0.13.2/.cargo_vcs_info.json0000644000000001430000000000100122130ustar { "git": { "sha1": "447322c693d6ef6420ce61fdcdb6de516c04660a" }, "path_in_vcs": "rcgen" }rcgen-0.13.2/CHANGELOG.md000064400000000000000000000306331046102023000126230ustar 00000000000000 # Changes ## Release 0.13.1 - April 4th, 2024 Fixed: - Fixed incorrect usage of the subject certificate's parameter's key identifier method when computing the key identifier of the issuer for the subject's authority key identifier (AKI) extension. ## Release 0.13.0 - March 28th, 2024 Breaking changes: - The API used to create/issue key pairs, certificates, certificate signing requests (CSRs), and certificate revocation lists (CRLs) has been restructured to emphasize consistency and avoid common errors with serialization. For each concrete type (cert, CSR, CRL) the process is now the same: 0. generate or load a key pair and any information about issuers required. 1. create parameters, customizing as appropriate. 2. call a generation `fn` on the parameters, providing subject key pair and issuer information and as appropriate. 3. call serialization `fn`s on the finalized type, obtaining DER or PEM. For more information, see [rcgen/docs/0.12-to-0.13.md]. - Throughout the API DER inputs are now represented using types from the Rustls `rustls-pki-types` crate, e.g. `PrivateKeyDer`, `CertificateDer`, `CertificateSigningRequestDer`. Contributed by [Tudyx](https://github.com/tudyx). - String types used in `SanType` and `DnValue` enums for non-UTF8 string types have been replaced with more specific types that prevent representation of illegal values. E.g. `Ia5String`, `BmpString`, `PrintableString`, `TeletexString`, and `UniversalString`. Contributed by [Tudyx](https://github.com/tudyx). - Method names starting with `get_` have been renamed to match Rust convention: `CertificateRevocationList::get_params()` -> `params()` `Certificate::get_params()` -> `params()` `Certificate::get_key_identifier()` -> `Certificate::key_identifier()` `Certificate::get_times()` -> `Certificate::times()` Added: - RSA key generation support has been added. This support requires using the `aws-lc-rs` feature. By default using `KeyPair::generate_for()` with an RSA `SignatureAlgorithm` will generate an RSA 2048 keypair. See `KeyPair::generate_rsa_for()` for support for RSA 2048, 3072 and 4096 key sizes. - Support for ECDSA P521 signatures and key generation has been added when using the `aws-lc-rs` feature. Contributed by [Alvenix](https://github.com/alvenix). - Support for loading private keys that may be PKCS8, PKCS1, or SEC1 has been added when using the `aws-lc-rs` feature. Without this feature private keys must be PKCS8. See `KeyPair::from_pem_and_sign_algo()` and `KeyPair::from_der_and_sign_algo()` for more information. Contributed by [Alvenix](https://github.com/alvenix). - Support has been added for Subject Alternative Name (SAN) names of type `OtherName`. Contributed by [Tudyx](https://github.com/tudyx). - Support has been added for specifying custom "other" OIDs in extended key usage. Contributed by [Tudyx](https://github.com/tudyx). - Support has been added for building rcgen _without_ cryptography by omitting the new (default-enabled) `crypto` feature flag. Contributed by [corrideat](https://github.com/corrideat). - Support for using `aws-lc-rs` in `fips` mode can now be activated by using the `fips` feature in combination with the `aws-lc-rs` feature. Contributed by [BiagioFesta](https://github.com/biagiofesta). - A small command-line tool for certificate generation (`rustls-cert-gen`) was added. Contributed by [tbro](https://github.com/tbro). ## Release 0.12.1 - January 25th, 2024 - RFC 5280 specifies that a serial number must not be larger than 20 octets in length. Prior to this release an unintended interaction between rcgen and its underlying DER encoding library could result in 21 octet serials. This has now been fixed. - A regression that caused build errors when the optional `pem` feature was omitted has been fixed. ## Release 0.12.0 - December 16, 2023 - Rename `RcgenError` to `Error`. Contributed by [thomaseizinger](https://github.com/thomaseizinger). - The public interface of `Error` has been made not expose external library types: `Error::PemError` now holds a `String` value, and the `Error` type doesn't support `From<_>` based conversion any more. This allows rcgen to update dependencies without impacting downstream users. - Upgrade to `ring` `v0.17`. Contributed by [thomaseizinger](https://github.com/thomaseizinger). - Make dependency on `ring` optional and allow usage of `aws-lc-rs` via a cargo feature. Ring remains the default. Contributed by [BiagioFesta](https://github.com/BiagioFesta). - Add `Ia5String` support for `DistinguishedName`s. - Add a `KeyIdMethod::PreSpecified` variant to set, and not generate the SKI. `CertificateParams::from_ca_cert_pem` now uses it when building params from an existing CA certificate. Contributed by [Brocar](https://github.com/Brocar). ## 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.13.2/Cargo.lock0000644000000702750000000000100102030ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "asn1-rs" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ad1373757efa0f70ec53939aabc7152e1591cb485208052993070ac8d2429d" dependencies = [ "asn1-rs-derive", "asn1-rs-impl", "displaydoc", "nom", "num-traits", "rusticata-macros", "thiserror", "time", ] [[package]] name = "asn1-rs-derive" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "asn1-rs-impl" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "aws-lc-fips-sys" version = "0.12.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cfb6142ca55e3c1be078c970f46b74f93b14e732b664059ef0d0ed718c10829" dependencies = [ "bindgen", "cmake", "dunce", "fs_extra", "libc", "paste", ] [[package]] name = "aws-lc-rs" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8a47f2fb521b70c11ce7369a6c5fa4bd6af7e5d62ec06303875bafe7c6ba245" dependencies = [ "aws-lc-fips-sys", "aws-lc-sys", "mirai-annotations", "paste", "zeroize", ] [[package]] name = "aws-lc-sys" version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2927c7af777b460b7ccd95f8b67acd7b4c04ec8896bf0c8e80ba30523cffc057" dependencies = [ "bindgen", "cc", "cmake", "dunce", "fs_extra", "libc", "paste", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bindgen" version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ "bitflags", "cexpr", "clang-sys", "itertools", "lazy_static", "lazycell", "log", "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", "syn", "which", ] [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "botan" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "350081af1a3c6883f8a1f863ac553bfe6922589aad60008a70947765ed57c53e" 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 = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" dependencies = [ "jobserver", "libc", "once_cell", ] [[package]] name = "cexpr" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ "nom", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clang-sys" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", "libloading", ] [[package]] name = "cmake" version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" dependencies = [ "cc", ] [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[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.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "der" version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "pem-rfc7468", "zeroize", ] [[package]] name = "der-parser" version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" dependencies = [ "asn1-rs", "displaydoc", "nom", "num-bigint", "num-traits", "rusticata-macros", ] [[package]] name = "deranged" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", ] [[package]] name = "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.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "dunce" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys", ] [[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 = "fs_extra" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[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.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "home" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ "windows-sys", ] [[package]] name = "itertools" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ "spin", ] [[package]] name = "lazycell" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libloading" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" dependencies = [ "cfg-if", "windows-targets", ] [[package]] name = "libm" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mirai-annotations" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "num-bigint" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", ] [[package]] name = "num-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-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-iter" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", ] [[package]] name = "oid-registry" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c958dd45046245b9c3c2547369bb634eb461670b2e7e0de552905801a648d1d" dependencies = [ "asn1-rs", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" 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", ] [[package]] name = "openssl-sys" version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pem" version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" 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.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "prettyplease" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", "syn", ] [[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 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.13.2" dependencies = [ "aws-lc-rs", "botan", "openssl", "pem", "rand", "ring", "rsa", "rustls-pki-types", "rustls-webpki", "time", "x509-parser", "yasna", "zeroize", ] [[package]] name = "regex" version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "ring" version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", "getrandom", "libc", "spin", "untrusted", "windows-sys", ] [[package]] name = "rsa" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" dependencies = [ "const-oid", "digest", "num-bigint-dig", "num-integer", "num-traits", "pkcs1", "pkcs8", "rand_core", "signature", "spki", "subtle", "zeroize", ] [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rusticata-macros" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" dependencies = [ "nom", ] [[package]] name = "rustix" version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "rustls-pki-types" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-webpki" version = "0.102.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] [[package]] name = "serde" version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signature" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", "rand_core", ] [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "spki" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", ] [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "2.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "synstructure" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "thiserror" version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "time" version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "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 = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[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 = "which" version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", "home", "once_cell", "rustix", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" [[package]] name = "windows_i686_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "x509-parser" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" 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.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", "syn", ] rcgen-0.13.2/Cargo.toml0000644000000060230000000000100102140ustar # 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.13.2" build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false 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/rustls/rcgen" [package.metadata.cargo_check_external_types] allowed_external_types = [ "time::offset_date_time::OffsetDateTime", "zeroize::Zeroize", "rustls_pki_types::*", ] [package.metadata.docs.rs] features = ["x509-parser"] [lib] name = "rcgen" path = "src/lib.rs" [[example]] name = "rsa-irc" path = "examples/rsa-irc.rs" required-features = ["pem"] [[example]] name = "rsa-irc-openssl" path = "examples/rsa-irc-openssl.rs" required-features = ["pem"] [[example]] name = "sign-leaf-with-ca" path = "examples/sign-leaf-with-ca.rs" required-features = [ "pem", "x509-parser", ] [[example]] name = "simple" path = "examples/simple.rs" required-features = [ "crypto", "pem", ] [[test]] name = "botan" path = "tests/botan.rs" [[test]] name = "generic" path = "tests/generic.rs" [[test]] name = "openssl" path = "tests/openssl.rs" [[test]] name = "util" path = "tests/util.rs" [[test]] name = "webpki" path = "tests/webpki.rs" [dependencies.aws-lc-rs] version = "1.6.0" optional = true default-features = false [dependencies.pem] version = "3.0.2" optional = true [dependencies.pki-types] version = "1.4.1" package = "rustls-pki-types" [dependencies.ring] version = "0.17" optional = true [dependencies.time] version = "0.3.6" default-features = false [dependencies.x509-parser] version = "0.16" 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.pki-types] version = "1" package = "rustls-pki-types" [dev-dependencies.rand] version = "0.8" [dev-dependencies.ring] version = "0.17" [dev-dependencies.rsa] version = "0.9" [dev-dependencies.rustls-webpki] version = "0.102" features = ["std"] [dev-dependencies.x509-parser] version = "0.16" features = ["verify"] [features] aws_lc_rs = [ "crypto", "dep:aws-lc-rs", "aws-lc-rs/aws-lc-sys", ] crypto = [] default = [ "crypto", "pem", "ring", ] fips = [ "crypto", "dep:aws-lc-rs", "aws-lc-rs/fips", ] ring = [ "crypto", "dep:ring", ] rcgen-0.13.2/Cargo.toml.orig000064400000000000000000000032541046102023000137000ustar 00000000000000[package] name = "rcgen" version = "0.13.2" documentation = "https://docs.rs/rcgen" description.workspace = true repository.workspace = true readme.workspace = true license.workspace = true edition.workspace = true keywords.workspace = true [[example]] name = "rsa-irc" required-features = ["pem"] [[example]] name = "rsa-irc-openssl" required-features = ["pem"] [[example]] name = "sign-leaf-with-ca" required-features = ["pem", "x509-parser"] [[example]] name = "simple" required-features = ["crypto", "pem"] [dependencies] aws-lc-rs = { workspace = true, optional = true } yasna = { version = "0.5.2", features = ["time", "std"] } ring = { workspace = true, optional = true } pem = { workspace = true, optional = true } pki-types = { workspace = true } time = { version = "0.3.6", default-features = false } x509-parser = { workspace = true, features = ["verify"], optional = true } zeroize = { version = "1.2", optional = true } [features] default = ["crypto", "pem", "ring"] crypto = [] aws_lc_rs = ["crypto", "dep:aws-lc-rs", "aws-lc-rs/aws-lc-sys"] ring = ["crypto", "dep:ring"] fips = ["crypto", "dep:aws-lc-rs", "aws-lc-rs/fips"] [package.metadata.docs.rs] features = ["x509-parser"] [package.metadata.cargo_check_external_types] allowed_external_types = [ "time::offset_date_time::OffsetDateTime", "zeroize::Zeroize", "rustls_pki_types::*", ] [dev-dependencies] openssl = "0.10" pki-types = { package = "rustls-pki-types", version = "1" } x509-parser = { workspace = true, features = ["verify"] } rustls-webpki = { version = "0.102", features = ["std"] } botan = { version = "0.10", features = ["vendored"] } rand = { workspace = true } rsa = "0.9" ring = { workspace = true } rcgen-0.13.2/LICENSE000064400000000000000000000266261046102023000120260ustar 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.13.2/README.md000064400000000000000000000035041046102023000122660ustar 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 use rcgen::{generate_simple_self_signed, CertifiedKey}; // 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 CertifiedKey { cert, key_pair } = generate_simple_self_signed(subject_alt_names).unwrap(); println!("{}", cert.pem()); println!("{}", key_pair.serialize_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/quinn-rs/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.13.2/docs/0.12-to-0.13.md000064400000000000000000000110061046102023000136340ustar 00000000000000# Rcgen 0.12 to 0.13 Migration Guide This document is a meant to be a helpful guide for some of the API changes made between rcgen 0.12 and 0.13. For information on other changes in 0.13 see [rcgen/CHANGELOG.md]. ## Key Pairs * Previously it was possible to have certificate generation automatically create a subject `KeyPair` for you by leaving the `key_pair` field of `CertificateParams` empty, and retrieving the generated `KeyPair` from a `Certificate` created with the `CertificateParams` by calling `Certificate::get_key_pair()`. To offer more consistency and to keep the `CertificateParams` and `Certificate` types from holding private key data, the new API requires you handle `KeyPair` creation yourself. See `CertifiedKey`, `KeyPair::generate()`, `KeyPair::generate_for()` and `KeyPair::generate_rsa_for()` for more information. * Serializing a `Certificate`'s `KeyPair` to DER or PEM was previously done by calling `Certificate::serialize_private_key_der()` or `Certificate::serialize_private_key_pem()`. This is now handled by calling `KeyPair::serialize_der()` or `KeyPair::serialize_pem()`. ## Certificates * For quick-and-easy self-signed certificate issuance, `generate_simple_self_signed` now returns a `CertifiedKey` in the success case instead of a `Certificate`. The self-signed `Certificate` can be accessed in the `cert` field of `CertifiedKey`, and the generated subject key pair in `key_pair`. * Custom self-signed certificate issuance was previously done by constructing `CertificateParams` and calling `Certificate::from_params()` to create a `Certificate`. This is now done by calling `CertificateParams::self_signed()`, providing a subject `KeyPair` of your choosing. * Custom certificate issuance signed by an issuer was previously done by constructing `CertificateParams`, calling `Certificate::from_params()` and then choosing the issuer at serialization time. This is now done ahead of serialization by calling `CertificateParams::signed_by()` and providing a subject `KeyPair` as well as an issuer `Certificate` and `KeyPair`. * Previously certificate serialization was done by calling `Certificate::serialize_der()`, `Certificate::serialize_pem()`, `Certificate::serialize_der_with_signer()` or `Certificate::serialize_pem_with_signer()`. Each time a serialization fn was called a new certificate was issued, leading to confusion when it was desired to serialize the same certificate in two formats. In the new API issuance is handled by `CertificateParams` fns and the generated `Certificate` will not change when serialized. You can serialize it to PEM by calling `Certificate::pem()`, or access the DER encoding by calling `Certificate::der()`. ## Certificate Signing Requests (CSRs) * Previously it was only possible to create a new CSR by first issuing a `Certificate` from `CertificateParams`, and calling `Certificate::serialize_request_pem()` or `Certificate::serialize_request_der()`. In the updated API you can create a `CertificateSigningRequest` directly from `CertificateParams` by calling `CertificateParams::serialize_request` and providing a subject `KeyPair`. You may serialize the CSR to DER or PEM by calling `CertificateSigningRequest::der()` or `CertificateSingingRequest::pem()`. * To load a CSR from an existing PEM/DER copy with the old API required calling `CertificateSingingRequest::from_pem()` or `CertificateSigningRequest::from_der()`. The new API introduces a `CertificateSingingRequestParams` type that can be created using `CertificateSigningRequestParams::from_pem()` or `CertificateSingingRequest::from_der()`. * To issue a certificate from an existing CSR with the old API required calling `CertificateSigningRequest::serialize_der_with_signer()` or `CertificateSigningRequest::serialize_pem_with_signer(). In the new API, call `CertificateSigningRequestParams::signed_by()` and provide an issuer `Certificate` and `KeyPair`. ## Certificate Revocation Lists (CRLs) * Previously a `CertificateRevocationList` was created by calling `CertificateRevocationList::from_params()`. This is now done by calling `CertificateRevocationListParams::signed_by()` and providing an issuer `Certificate` and `KeyPair`. * Previously a created `CertificateRevocationList` could be serialized to DER or PEM by calling `CertificateRevocationList::serialize_der_with_signer()` or `CertificateRevocationList::serialize_pem_with_signer()`. This is now done by calling `CertificateRevocationList::der()` or `CertificateRevocationList::pem()`. rcgen-0.13.2/examples/rsa-irc-openssl.rs000064400000000000000000000024671046102023000162230ustar 00000000000000fn main() -> Result<(), Box> { use rcgen::{date_time_ymd, CertificateParams, DistinguishedName}; use std::fmt::Write; use std::fs; let mut params: CertificateParams = Default::default(); params.not_before = date_time_ymd(2021, 5, 19); params.not_after = date_time_ymd(4096, 1, 1); params.distinguished_name = DistinguishedName::new(); 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)?; let cert = params.self_signed(&key_pair)?; let pem_serialized = cert.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 = hash.as_ref().iter().fold(String::new(), |mut output, b| { let _ = write!(output, "{b:02x}"); output }); println!("sha-512 fingerprint: {hash_hex}"); println!("{pem_serialized}"); println!("{}", key_pair.serialize_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", key_pair.serialize_pem().as_bytes())?; fs::write("certs/key.der", key_pair.serialize_der())?; Ok(()) } rcgen-0.13.2/examples/rsa-irc.rs000064400000000000000000000026271046102023000145400ustar 00000000000000fn main() -> Result<(), Box> { use rand::rngs::OsRng; use rsa::pkcs8::EncodePrivateKey; use rsa::RsaPrivateKey; use rcgen::{date_time_ymd, CertificateParams, DistinguishedName}; use std::fmt::Write; use std::fs; let mut params: CertificateParams = Default::default(); params.not_before = date_time_ymd(2021, 5, 19); params.not_after = date_time_ymd(4096, 1, 1); params.distinguished_name = DistinguishedName::new(); 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(); let cert = params.self_signed(&key_pair)?; let pem_serialized = cert.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 = hash.as_ref().iter().fold(String::new(), |mut output, b| { let _ = write!(output, "{b:02x}"); output }); println!("sha-512 fingerprint: {hash_hex}"); println!("{pem_serialized}"); println!("{}", key_pair.serialize_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", key_pair.serialize_pem().as_bytes())?; fs::write("certs/key.der", key_pair.serialize_der())?; Ok(()) } rcgen-0.13.2/examples/sign-leaf-with-ca.rs000064400000000000000000000043031046102023000163700ustar 00000000000000use rcgen::{ BasicConstraints, Certificate, CertificateParams, DnType, DnValue::PrintableString, ExtendedKeyUsagePurpose, IsCa, KeyPair, KeyUsagePurpose, }; use time::{Duration, OffsetDateTime}; /// Example demonstrating signing end-entity certificate with ca fn main() { let (ca, ca_key) = new_ca(); let end_entity = new_end_entity(&ca, &ca_key); let end_entity_pem = end_entity.pem(); println!("directly signed end-entity certificate: {end_entity_pem}"); let ca_cert_pem = ca.pem(); println!("ca certificate: {ca_cert_pem}"); } fn new_ca() -> (Certificate, KeyPair) { let mut params = CertificateParams::new(Vec::default()).expect("empty subject alt name can't produce error"); let (yesterday, tomorrow) = validity_period(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); params.distinguished_name.push( DnType::CountryName, PrintableString("BR".try_into().unwrap()), ); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params.key_usages.push(KeyUsagePurpose::DigitalSignature); params.key_usages.push(KeyUsagePurpose::KeyCertSign); params.key_usages.push(KeyUsagePurpose::CrlSign); params.not_before = yesterday; params.not_after = tomorrow; let key_pair = KeyPair::generate().unwrap(); (params.self_signed(&key_pair).unwrap(), key_pair) } fn new_end_entity(ca: &Certificate, ca_key: &KeyPair) -> Certificate { let name = "entity.other.host"; let mut params = CertificateParams::new(vec![name.into()]).expect("we know the name is valid"); let (yesterday, tomorrow) = validity_period(); params.distinguished_name.push(DnType::CommonName, name); params.use_authority_key_identifier_extension = true; params.key_usages.push(KeyUsagePurpose::DigitalSignature); params .extended_key_usages .push(ExtendedKeyUsagePurpose::ServerAuth); params.not_before = yesterday; params.not_after = tomorrow; let key_pair = KeyPair::generate().unwrap(); params.signed_by(&key_pair, ca, ca_key).unwrap() } fn validity_period() -> (OffsetDateTime, OffsetDateTime) { let day = Duration::new(86400, 0); let yesterday = OffsetDateTime::now_utc().checked_sub(day).unwrap(); let tomorrow = OffsetDateTime::now_utc().checked_add(day).unwrap(); (yesterday, tomorrow) } rcgen-0.13.2/examples/simple.rs000064400000000000000000000022541046102023000144650ustar 00000000000000use rcgen::{date_time_ymd, CertificateParams, DistinguishedName, DnType, KeyPair, SanType}; use std::fs; fn main() -> Result<(), Box> { let mut params: CertificateParams = Default::default(); params.not_before = date_time_ymd(1975, 1, 1); params.not_after = date_time_ymd(4096, 1, 1); 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".try_into()?), SanType::DnsName("localhost".try_into()?), ]; let key_pair = KeyPair::generate()?; let cert = params.self_signed(&key_pair)?; let pem_serialized = cert.pem(); let pem = pem::parse(&pem_serialized)?; let der_serialized = pem.contents(); println!("{pem_serialized}"); println!("{}", key_pair.serialize_pem()); 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", key_pair.serialize_pem().as_bytes())?; fs::write("certs/key.der", key_pair.serialize_der())?; Ok(()) } rcgen-0.13.2/src/certificate.rs000064400000000000000000001420521046102023000144300ustar 00000000000000use std::net::IpAddr; use std::str::FromStr; #[cfg(feature = "pem")] use pem::Pem; use pki_types::{CertificateDer, CertificateSigningRequestDer}; use time::{Date, Month, OffsetDateTime, PrimitiveDateTime, Time}; use yasna::models::ObjectIdentifier; use yasna::{DERWriter, Tag}; use crate::crl::CrlDistributionPoint; use crate::csr::CertificateSigningRequest; use crate::key_pair::{serialize_public_key_der, PublicKeyData}; #[cfg(feature = "crypto")] use crate::ring_like::digest; #[cfg(feature = "pem")] use crate::ENCODE_CONFIG; use crate::{ oid, write_distinguished_name, write_dt_utc_or_generalized, write_x509_authority_key_identifier, write_x509_extension, DistinguishedName, Error, Issuer, KeyIdMethod, KeyPair, KeyUsagePurpose, SanType, SerialNumber, }; /// An issued certificate together with the parameters used to generate it. pub struct Certificate { pub(crate) params: CertificateParams, pub(crate) subject_public_key_info: Vec, pub(crate) der: CertificateDer<'static>, } impl Certificate { /// Returns the certificate parameters pub fn 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 key_identifier(&self) -> Vec { self.params .key_identifier_method .derive(&self.subject_public_key_info) } /// Get the certificate in DER encoded format. /// /// [`CertificateDer`] implements `Deref` and `AsRef<[u8]>`, so you can easily /// extract the DER bytes from the return value. pub fn der(&self) -> &CertificateDer<'static> { &self.der } /// Get the certificate in PEM encoded format. #[cfg(feature = "pem")] pub fn pem(&self) -> String { pem::encode_config(&Pem::new("CERTIFICATE", self.der().to_vec()), ENCODE_CONFIG) } } impl From for CertificateDer<'static> { fn from(cert: Certificate) -> Self { cert.der } } /// Parameters used for certificate generation #[allow(missing_docs)] #[non_exhaustive] #[derive(Debug, PartialEq, Eq, Clone)] pub struct CertificateParams { 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, /// 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 a truncated SHA-256 digest. See [`KeyIdMethod`] for more information. 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 { 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(), use_authority_key_identifier_extension: false, #[cfg(feature = "crypto")] key_identifier_method: KeyIdMethod::Sha256, #[cfg(not(feature = "crypto"))] key_identifier_method: KeyIdMethod::PreSpecified(Vec::new()), } } } impl CertificateParams { /// Generate certificate parameters with reasonable defaults pub fn new(subject_alt_names: impl Into>) -> Result { let subject_alt_names = subject_alt_names .into() .into_iter() .map(|s| { Ok(match IpAddr::from_str(&s) { Ok(ip) => SanType::IpAddress(ip), Err(_) => SanType::DnsName(s.try_into()?), }) }) .collect::, _>>()?; Ok(CertificateParams { subject_alt_names, ..Default::default() }) } /// Generate a new certificate from the given parameters, signed by the provided issuer. /// /// The returned certificate will have its issuer field set to the subject of the /// provided `issuer`, and the authority key identifier extension will be populated using /// the subject public key of `issuer`. It will be signed by `issuer_key`. /// /// Note that no validation of the `issuer` certificate is performed. Rcgen will not require /// the certificate to be a CA certificate, or have key usage extensions that allow signing. /// /// The returned [`Certificate`] may be serialized using [`Certificate::der`] and /// [`Certificate::pem`]. pub fn signed_by( self, public_key: &impl PublicKeyData, issuer: &Certificate, issuer_key: &KeyPair, ) -> Result { let issuer = Issuer { distinguished_name: &issuer.params.distinguished_name, key_identifier_method: &issuer.params.key_identifier_method, key_usages: &issuer.params.key_usages, key_pair: issuer_key, }; let subject_public_key_info = yasna::construct_der(|writer| serialize_public_key_der(public_key, writer)); let der = self.serialize_der_with_signer(public_key, issuer)?; Ok(Certificate { params: self, subject_public_key_info, der, }) } /// Generates a new self-signed certificate from the given parameters. /// /// The returned [`Certificate`] may be serialized using [`Certificate::der`] and /// [`Certificate::pem`]. pub fn self_signed(self, key_pair: &KeyPair) -> Result { let issuer = Issuer { distinguished_name: &self.distinguished_name, key_identifier_method: &self.key_identifier_method, key_usages: &self.key_usages, key_pair, }; let subject_public_key_info = key_pair.public_key_der(); let der = self.serialize_der_with_signer(key_pair, issuer)?; Ok(Certificate { params: self, subject_public_key_info, der, }) } /// Parses an existing ca certificate from the ASCII PEM format. /// /// 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) -> Result { let certificate = pem::parse(pem_str).or(Err(Error::CouldNotParseCertificate))?; Self::from_ca_cert_der(&certificate.contents().into()) } /// Parses an existing ca certificate from the DER format. /// /// This function is only of use if you have an existing CA certificate /// you would like to use to sign a certificate generated by `rcgen`. /// By providing the constructed [`CertificateParams`] and the [`KeyPair`] /// associated with your existing `ca_cert` you can use [`CertificateParams::signed_by()`] /// or [`crate::CertificateSigningRequestParams::signed_by()`] to issue new certificates /// using the CA cert. /// /// In general this function only extracts the information needed for signing. /// Other attributes of the [`Certificate`] may be left as defaults. /// /// This function assumes the provided certificate is a CA. It will not check /// for the presence of the `BasicConstraints` extension, or perform any other /// validation. /// /// [`rustls_pemfile::certs()`] is often used to obtain a [`CertificateDer`] from PEM input. /// If you already have a byte slice containing DER, it can trivially be converted into /// [`CertificateDer`] using the [`Into`] trait. /// /// [`rustls_pemfile::certs()`]: https://docs.rs/rustls-pemfile/latest/rustls_pemfile/fn.certs.html #[cfg(feature = "x509-parser")] pub fn from_ca_cert_der(ca_cert: &CertificateDer<'_>) -> Result { let (_remainder, x509) = x509_parser::parse_x509_certificate(ca_cert) .or(Err(Error::CouldNotParseCertificate))?; 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()); let key_identifier_method = x509.iter_extensions() .find_map(|ext| match ext.parsed_extension() { x509_parser::extensions::ParsedExtension::SubjectKeyIdentifier(key_id) => { Some(KeyIdMethod::PreSpecified(key_id.0.into())) }, _ => None, }); let key_identifier_method = match key_identifier_method { Some(method) => method, None => { #[cfg(not(feature = "crypto"))] return Err(Error::UnsupportedSignatureAlgorithm); #[cfg(feature = "crypto")] KeyIdMethod::Sha256 }, }; Ok(CertificateParams { is_ca, subject_alt_names, key_usages, extended_key_usages, name_constraints, serial_number, key_identifier_method, distinguished_name: dn, 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(Error::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(Error::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, Error> { let sans = x509 .subject_alternative_name() .or(Err(Error::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, Error> { let key_usage = x509 .key_usage() .or(Err(Error::CouldNotParseCertificate))? .map(|ext| ext.value); // This x509 parser stores flags in reversed bit BIT STRING order let flags = key_usage.map_or(0u16, |k| k.flags).reverse_bits(); Ok(KeyUsagePurpose::from_u16(flags)) } #[cfg(feature = "x509-parser")] fn convert_x509_extended_key_usages( x509: &x509_parser::certificate::X509Certificate<'_>, ) -> Result, Error> { let extended_key_usage = x509 .extended_key_usage() .or(Err(Error::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, Error> { let constraints = x509 .name_constraints() .or(Err(Error::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, Error> { 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) } /// Write a CSR extension request attribute as defined in [RFC 2985]. /// /// [RFC 2985]: fn write_extension_request_attribute(&self, writer: DERWriter) { writer.write_sequence(|writer| { writer.next().write_oid(&ObjectIdentifier::from_slice( oid::PKCS_9_AT_EXTENSION_REQUEST, )); writer.next().write_set(|writer| { writer.next().write_sequence(|writer| { // Write key_usage self.write_key_usage(writer.next()); // Write subject_alt_names self.write_subject_alt_names(writer.next()); self.write_extended_key_usage(writer.next()); // Write custom extensions for ext in &self.custom_extensions { write_x509_extension(writer.next(), &ext.oid, ext.critical, |writer| { writer.write_der(ext.content()) }); } }); }); }); } /// Write a certificate's KeyUsage as defined in RFC 5280. fn write_key_usage(&self, writer: DERWriter) { // RFC 5280 defines 9 key usages, which we detail in our key usage enum // We could use std::mem::variant_count here, but it's experimental const KEY_USAGE_BITS: usize = 9; if self.key_usages.is_empty() { return; } // "When present, conforming CAs SHOULD mark this extension as critical." write_x509_extension(writer, oid::KEY_USAGE, true, |writer| { // u16 is large enough to encode the largest possible key usage (two-bytes) let bit_string = self.key_usages.iter().fold(0u16, |bit_string, key_usage| { bit_string | key_usage.to_u16() }); writer.write_bitvec_bytes(&bit_string.to_be_bytes(), KEY_USAGE_BITS); }); } fn write_extended_key_usage(&self, writer: DERWriter) { if !self.extended_key_usages.is_empty() { write_x509_extension(writer, oid::EXT_KEY_USAGE, false, |writer| { writer.write_sequence(|writer| { for usage in &self.extended_key_usages { writer .next() .write_oid(&ObjectIdentifier::from_slice(usage.oid())); } }); }); } } fn write_subject_alt_names(&self, writer: DERWriter) { if self.subject_alt_names.is_empty() { return; } 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.as_str()), SanType::IpAddress(IpAddr::V4(addr)) => { writer.write_bytes(&addr.octets()) }, SanType::IpAddress(IpAddr::V6(addr)) => { writer.write_bytes(&addr.octets()) }, SanType::OtherName((oid, value)) => { // otherName SEQUENCE { OID, [0] explicit any defined by oid } // https://datatracker.ietf.org/doc/html/rfc5280#page-38 writer.write_sequence(|writer| { writer.next().write_oid(&ObjectIdentifier::from_slice(oid)); value.write_der(writer.next()); }); }, }, ); } }); }); } /// Generate and serialize a certificate signing request (CSR). /// /// The constructed CSR will contain attributes based on the certificate parameters, /// and include the subject public key information from `subject_key`. Additionally, /// the CSR will be signed using the subject key. /// /// Note that subsequent invocations of `serialize_request()` will not produce the exact /// same output. pub fn serialize_request( &self, subject_key: &KeyPair, ) -> Result { self.serialize_request_with_attributes(subject_key, Vec::new()) } /// Generate and serialize a certificate signing request (CSR) with custom PKCS #10 attributes. /// as defined in [RFC 2986]. /// /// The constructed CSR will contain attributes based on the certificate parameters, /// and include the subject public key information from `subject_key`. Additionally, /// the CSR will be self-signed using the subject key. /// /// Note that subsequent invocations of `serialize_request_with_attributes()` will not produce the exact /// same output. /// /// [RFC 2986]: pub fn serialize_request_with_attributes( &self, subject_key: &KeyPair, attrs: Vec, ) -> Result { // No .. pattern, we use this to ensure every field is used #[deny(unused)] let Self { 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, 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 // - key_identifier_method is here because self.write_extended_key_usage uses it // - 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 _ = ( not_before, not_after, key_identifier_method, extended_key_usages, ); if serial_number.is_some() || *is_ca != IsCa::NoCa || name_constraints.is_some() || !crl_distribution_points.is_empty() || *use_authority_key_identifier_extension { return Err(Error::UnsupportedInCsr); } // Whether or not to write an extension request attribute let write_extension_request = !key_usages.is_empty() || !subject_alt_names.is_empty() || !extended_key_usages.is_empty() || !custom_extensions.is_empty(); let der = subject_key.sign_der(|writer| { // Write version writer.next().write_u8(0); write_distinguished_name(writer.next(), distinguished_name); serialize_public_key_der(subject_key, writer.next()); // According to the spec in RFC 2986, even if attributes are empty we need the empty attribute tag writer .next() .write_tagged_implicit(Tag::context(0), |writer| { // RFC 2986 specifies that attributes are a SET OF Attribute writer.write_set_of(|writer| { if write_extension_request { self.write_extension_request_attribute(writer.next()); } for Attribute { oid, values } in attrs { writer.next().write_sequence(|writer| { writer.next().write_oid(&ObjectIdentifier::from_slice(&oid)); writer.next().write_der(&values); }); } }); }); Ok(()) })?; Ok(CertificateSigningRequest { der: CertificateSigningRequestDer::from(der), }) } pub(crate) fn serialize_der_with_signer( &self, pub_key: &K, issuer: Issuer<'_>, ) -> Result, Error> { let der = issuer.key_pair.sign_der(|writer| { let pub_key_spki = yasna::construct_der(|writer| serialize_public_key_der(pub_key, 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 { #[cfg(feature = "crypto")] { let hash = digest::digest(&digest::SHA256, pub_key.der_bytes()); // RFC 5280 specifies at most 20 bytes for a serial number let mut sl = hash.as_ref()[0..20].to_vec(); sl[0] &= 0x7f; // MSB must be 0 to ensure encoding bignum in 20 bytes writer.next().write_bigint_bytes(&sl, true); } #[cfg(not(feature = "crypto"))] if self.serial_number.is_none() { return Err(Error::MissingSerialNumber); } }; // Write signature algorithm issuer.key_pair.alg.write_alg_ident(writer.next()); // Write issuer name write_distinguished_name(writer.next(), &issuer.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::<(), Error>(()) })?; // Write subject write_distinguished_name(writer.next(), &self.distinguished_name); // Write subjectPublicKeyInfo serialize_public_key_der(pub_key, 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 { return Ok(()); } 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(), match issuer.key_identifier_method { KeyIdMethod::PreSpecified(aki) => aki.clone(), #[cfg(feature = "crypto")] _ => issuer .key_identifier_method .derive(issuer.key_pair.public_key_der()), }, ); } // Write subject_alt_names if !self.subject_alt_names.is_empty() { self.write_subject_alt_names(writer.next()); } // Write standard key usage self.write_key_usage(writer.next()); // 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| { writer.write_bytes( &self.key_identifier_method.derive(pub_key_spki), ); }, ); // 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| { writer.write_bytes( &self.key_identifier_method.derive(pub_key_spki), ); }, ); // 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(()) })?; Ok(der.into()) } /// Insert an extended key usage (EKU) into the parameters if it does not already exist pub fn insert_extended_key_usage(&mut self, eku: ExtendedKeyUsagePurpose) { if !self.extended_key_usages.contains(&eku) { self.extended_key_usages.push(eku); } } } 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 }); } }); }); } /// A PKCS #10 CSR attribute, as defined in [RFC 5280] and constrained /// by [RFC 2986]. /// /// [RFC 5280]: /// [RFC 2986]: #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct Attribute { /// `AttributeType` of the `Attribute`, defined as an `OBJECT IDENTIFIER`. pub oid: &'static [u64], /// DER-encoded values of the `Attribute`, defined by [RFC 2986] as: /// /// ```text /// SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{@type}) /// ``` /// /// [RFC 2986]: https://datatracker.ietf.org/doc/html/rfc2986#section-4 pub values: Vec, } /// 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() } } #[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 { pub(crate) 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()), } } } #[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, /// A custom purpose not from the pre-specified list of purposes Other(Vec), } impl ExtendedKeyUsagePurpose { fn oid(&self) -> &[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], Other(oid) => oid, } } } /// 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() } } #[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 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 } } /// 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])); /// ``` impl FromStr for CidrSubnet { type Err = (); 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(()) } } } /// 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() } /// 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), } #[cfg(test)] mod tests { #[cfg(feature = "pem")] use super::*; #[cfg(feature = "crypto")] #[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 key_pair = KeyPair::generate().unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Parse it let (_rem, cert) = x509_parser::parse_x509_certificate(cert.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); } #[cfg(feature = "crypto")] #[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 key_pair = KeyPair::generate().unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Parse it let (_rem, cert) = x509_parser::parse_x509_certificate(cert.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); } #[cfg(feature = "crypto")] #[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 key_pair = KeyPair::generate().unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Parse it let (_rem, cert) = x509_parser::parse_x509_certificate(cert.der()).unwrap(); // Ensure we found it. let maybe_extension = cert.extended_key_usage().unwrap(); let extension = maybe_extension.unwrap(); assert!(extension.value.any); } #[cfg(feature = "crypto")] #[test] fn test_with_extended_key_usages_other() { use x509_parser::der_parser::asn1_rs::Oid; let mut params: CertificateParams = Default::default(); const OID_1: &[u64] = &[1, 2, 3, 4]; const OID_2: &[u64] = &[1, 2, 3, 4, 5, 6]; // Set extended_key_usages params.extended_key_usages = vec![ ExtendedKeyUsagePurpose::Other(Vec::from(OID_1)), ExtendedKeyUsagePurpose::Other(Vec::from(OID_2)), ]; // Make the cert let key_pair = KeyPair::generate().unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Parse it let (_rem, cert) = x509_parser::parse_x509_certificate(cert.der()).unwrap(); // Ensure we found it. let maybe_extension = cert.extended_key_usage().unwrap(); let extension = maybe_extension.unwrap(); let expected_oids = vec![Oid::from(OID_1).unwrap(), Oid::from(OID_2).unwrap()]; assert_eq!(extension.value.other, expected_oids); } #[cfg(feature = "pem")] mod test_pem_serialization { use super::*; #[test] #[cfg(windows)] fn test_windows_line_endings() { let key_pair = KeyPair::generate().unwrap(); let cert = CertificateParams::default().self_signed(&key_pair).unwrap(); assert!(cert.pem().contains("\r\n")); } #[test] #[cfg(not(windows))] fn test_not_windows_line_endings() { let key_pair = KeyPair::generate().unwrap(); let cert = CertificateParams::default().self_signed(&key_pair).unwrap(); assert!(!cert.pem().contains('\r')); } } #[cfg(all(feature = "pem", feature = "x509-parser"))] mod test_key_identifier_from_ca { use super::*; #[test] fn load_ca_and_sign_cert() { let ca_cert = r#"-----BEGIN CERTIFICATE----- MIIFDTCCAvWgAwIBAgIUVuDfDt/BUVfObGOHsM+L5/qPZfIwDQYJKoZIhvcNAQEL BQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wHhcNMjMxMjA4MTAwOTI2WhcNMjQx MTI4MTAwOTI2WjAWMRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCAiIwDQYJKoZIhvcN AQEBBQADggIPADCCAgoCggIBAKXyZsv7Zwek9yc54IXWjCkMwU4eDMz9Uw06WETF hZtauwDo4usCeYJa/7x8RZbGcI99s/vOMHjIdVzY6g9p5c6qS+7EUBhXARYVB74z XUGwgVGss7lgw+0dNxhQ8F0M2smBXUP9FlJJjJpbWeU+93iynGy+PTXFtYMnOoVI 4G7YKsG5lX0zBJUNYZslEz6Kp8eRYu7FAdccU0u5bmg02a1WiXOYJeN1+AifUbRN zNInZCqMCFgoHczb0DvKU3QX/xrcBxfr/SNJPqxlecUvsozteUoAFAUF1uTxH31q cVmCHf9I0r6JJoGxs+XMVbH2SJLdsq/+zpjeHz6gy0z4aRMBpaUWUQ9pEENeSq15 PXCuX3yPT2BII30mL86OWO6qgms70iALak6xZ/xAT7RT22E1bOF+XJsiUM3OgGF0 TPmDcpafEMH4kwzdaC7U5hqhYk9I2lfTMEghV86kUXClExuHEQD4GZLcd1HMD/Wg qOZO4y/t/yzBPNq01FpeilFph/tW6pxr1X7Jloz1/yIuNFK0oXTB24J/TUi+/S1B kavOBg3eNHHDXDjESKtnV+iwo1cFt6LVCrnKhKJ6m95+c+YKQGIrcwkR91OxZ9ZT DEzySsPDpWrteZf3K1VA0Ut41aTKu8pYwxsnVdOiBGaJkOh/lrevI6U9Eg4vVq94 hyAZAgMBAAGjUzBRMB0GA1UdDgQWBBSX1HahmxpxNSrH9KGEElYGul1hhDAfBgNV HSMEGDAWgBSX1HahmxpxNSrH9KGEElYGul1hhDAPBgNVHRMBAf8EBTADAQH/MA0G CSqGSIb3DQEBCwUAA4ICAQAhtwt0OrHVITVOzoH3c+7SS/rGd9KGpHG4Z/N7ASs3 7A2PXFC5XbUuylky0+/nbkN6hhecj+Zwt5x5R8k4saXUZ8xkMfP8RaRxyZ3rUOIC BZhZm1XbQzaWIQjpjyPUWDDa9P0lGsUyrEIQaLjg1J5jYPOD132bmdIuhZtzldTV zeE/4sKdrkj6HZxe1jxAhx2IWm6W+pEAcq1Ld9SmJGOxBVRRKyGsMMw6hCdWfQHv Z8qRIhn3FU6ZKW2jvTGJBIXoK4u454qi6DVxkFZ0OK9VwWVuDLvs2Es95TiZPTq+ KJmRHWHF/Ic78XFgxVq0tVaJAs7qoOMjDkehPG1V8eewanlpcaE6rPx0eiPq+nHE gCf0KmKGVM8lQe63obzprkdLKL3T4UDN19K2wqscJcPKK++27OYx2hJaJKmYzF23 4WhIRzdALTs/2fbB68nVSz7kBtHvsHHS33Q57zEdQq5YeyUaTtCvJJobt70dy9vN YolzLWoY/itEPFtbBAdnJxXlctI3bw4Mzw1d66Wt+//R45+cIe6cJdUIqMHDhsGf U8EuffvDcTJuUzIkyzbyOI15r1TMbRt8vFR0jzagZBCG73lVacH/bYEb2j4Z1ORi L2Fl4tgIQ5tyaTpu9gpJZvPU0VZ/j+1Jdk1c9PJ6xhCjof4nzI9YsLbI8lPtu8K/ Ng== -----END CERTIFICATE-----"#; let ca_key = r#"-----BEGIN PRIVATE KEY----- MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQCl8mbL+2cHpPcn OeCF1owpDMFOHgzM/VMNOlhExYWbWrsA6OLrAnmCWv+8fEWWxnCPfbP7zjB4yHVc 2OoPaeXOqkvuxFAYVwEWFQe+M11BsIFRrLO5YMPtHTcYUPBdDNrJgV1D/RZSSYya W1nlPvd4spxsvj01xbWDJzqFSOBu2CrBuZV9MwSVDWGbJRM+iqfHkWLuxQHXHFNL uW5oNNmtVolzmCXjdfgIn1G0TczSJ2QqjAhYKB3M29A7ylN0F/8a3AcX6/0jST6s ZXnFL7KM7XlKABQFBdbk8R99anFZgh3/SNK+iSaBsbPlzFWx9kiS3bKv/s6Y3h8+ oMtM+GkTAaWlFlEPaRBDXkqteT1wrl98j09gSCN9Ji/OjljuqoJrO9IgC2pOsWf8 QE+0U9thNWzhflybIlDNzoBhdEz5g3KWnxDB+JMM3Wgu1OYaoWJPSNpX0zBIIVfO pFFwpRMbhxEA+BmS3HdRzA/1oKjmTuMv7f8swTzatNRaXopRaYf7Vuqca9V+yZaM 9f8iLjRStKF0wduCf01Ivv0tQZGrzgYN3jRxw1w4xEirZ1fosKNXBbei1Qq5yoSi epvefnPmCkBiK3MJEfdTsWfWUwxM8krDw6Vq7XmX9ytVQNFLeNWkyrvKWMMbJ1XT ogRmiZDof5a3ryOlPRIOL1aveIcgGQIDAQABAoICACVWAWzZdlfQ9M59hhd2qvg9 Z2yE9EpWoI30V5G5gxLt+e79drh7SQ1cHfexWhLPONn/5TO9M0ipiUZHg3nOUKcL x6PDxWWEhbkLKD/R3KR/6siOe600qUA6939gDoRQ9RSrJ2m5koEXDSxZa0NZxGIC hZEtyCXGAs2sUM1WFTC7L/uAHrMZfGlwpko6sDa9CXysKD8iUgSs2czKvp1xbpxC QRCh5bxkeVavSbmwW2nY9P9hnCsBc5r4xcP+BIK1N286m9n0/XIn85LkDd6gmaJ9 d3F/zQFITA4cdgJIpZIG5WrfXpMB1okNizUjoRA2IiPw/1f7k03vg8YadUMvDKye FOYsHePLYkq8COfGJaPq0b3ekkiS5CO/Aeo0rFVlDj9003N6IJ67oAHHPLpALNLR RCJpztcGbfZHc1tLKvUnK56IL1FCbCm0SpsuNtTXXPd14i15ei4BkVUkANsEKOAR BHlA/rn2As2lntZ/oJ07Torj2cKpn7uKw65ajtM7wAoVW1oL0qDyhGi/JGuL9zlg CB7jVaPqzlo+bxWyCmfHW3erR0Y3QIMTBNMUZU/NKba3HjSVDadZK563mbfgWw0W qP17gfM5tOFUVulAnMTjsmmjqoUZs9irku0bd1J+CfzF4Z56qFoiolBTUD8RdSSm sXJytHZj3ajH8D3e3SDFAoIBAQDc6td5UqAc+KGrpW3+y6R6+PM8T6NySCu3jvF+ WMt5O7lsKCXUbVRo6w07bUN+4nObJOi41uR6nC8bdKhsuex97h7tpmtN3yGM6I9m zFulfkRafaVTS8CH7l0nTBkd7wfdUX0bjznxB1xVDPFoPC3ybRXoub4he9MLlHQ9 JPiIXGxJQI3CTYQRXwKTtovBV70VSzuaZERAgta0uH1yS6Rqk3lAyWrAKifPnG2I kSOC/ZTxX0sEliJ5xROvRoBVsWG2W/fDRRwavzJVWnNAR1op+gbVNKFrKuGnYsEF 5AfeF2tEnCHa+E6Vzo4lNOKkNSSVPQGbp8MVE43PU3EPW2BDAoIBAQDATMtWrW0R 9qRiHDtYZAvFk1pJHhDzSjtPhZoNk+/8WJ7VXDnV9/raEkXktE1LQdSeER0uKFgz vwZTLh74FVQQWu0HEFgy/Fm6S8ogO4xsRvS+zAhKUfPsjT+aHo0JaJUmPYW+6+d2 +nXC6MNrA9tzZnSJzM+H8bE1QF2cPriEDdImYUUAbsYlPjPyfOd2qF8ehVg5UmoT fFnkvmQO0Oi/vR1GMXtT2I92TEOLMJq836COhYYPyYkU7/boxYRRt7XL6cK3xpwv 51zNeQ4COR/8DGDydzuAunzjiiJUcPRFpPvf171AVZNg/ow+UMRvWLUtl076n5Pi Kf+7IIlXtHZzAoIBAD4ZLVSHK0a5hQhwygiTSbrfe8/6OuGG8/L3FV8Eqr17UlXa uzeJO+76E5Ae2Jg0I3b62wgKL9NfT8aR9j4JzTZg1wTKgOM004N+Y8DrtN9CLQia xPwzEP2kvT6sn2rQpA9MNrSmgA0Gmqe1qa45LFk23K+8dnuHCP36TupZGBuMj0vP /4kcrQENCfZnm8VPWnE/4pM1mBHiNWQ7b9fO93qV1cGmXIGD2Aj92bRHyAmsKk/n D3lMkohUI4JjePOdlu/hzjVvmcTS9d0UPc1VwTyHcaBA2Rb8yM16bvOu8580SgzR LpsUrVJi64X95a9u2MeyjF8quyWTh4s900wTzW0CggEAJrGNHMTKtJmfXAp4OoHv CHNs8Fd3a6zdIFQuulqxKGKgmyfyj0ZVmHmizLEm+GSnpqKk73u4u7jNSgF2w85u 2teg6BH23VN/roe/hRrWV5czegzOAj5ZSZjmWlmZYXJEyKwKdG89ZOhit7RkVe0x xBeyjWPDwoP0d1WbQGwyboflaEmcO8kOX8ITa9CMNokMkrScGvSlWYRlBiz1LzIE E0i3Uj90pFtoCpKv6JsAF88bnHHrltOjnK3oTdAontTLZNuFjbsOBGmWd9XK5tGd yPaor0EknPNpW9OYsssDq9vVvqXHc+GERTkS+RsBW7JKyoCuqKlhdVmkFoAmgppS VwKCAQB7nOsjguXliXXpayr1ojg1T5gk+R+JJMbOw7fuhexavVLi2I/yGqAq9gfQ KoumYrd8EYb0WddqK0rdfjZyPmiqCNr72w3QKiEDx8o3FHUajSL1+eXpJJ03shee BqN6QWlRz8fu7MAZ0oqv06Cln+3MZRUvc6vtMHAEzD7y65HV+Do7z61YmvwVZ2N2 +30kckNnDVdggOklBmlSk5duej+RVoAKP8U5wV3Z/bS5J0OI75fxhuzybPcVfkwE JiY98T5oN1X0C/qAXxJfSvklbru9fipwGt3dho5Tm6Ee3cYf+plnk4WZhSnqyef4 PITGdT9dgN88nHPCle0B1+OY+OZ5 -----END PRIVATE KEY-----"#; let params = CertificateParams::from_ca_cert_pem(ca_cert).unwrap(); let ca_ski = vec![ 0x97, 0xD4, 0x76, 0xA1, 0x9B, 0x1A, 0x71, 0x35, 0x2A, 0xC7, 0xF4, 0xA1, 0x84, 0x12, 0x56, 0x06, 0xBA, 0x5D, 0x61, 0x84, ]; assert_eq!( KeyIdMethod::PreSpecified(ca_ski.clone()), params.key_identifier_method ); let ca_kp = KeyPair::from_pem(ca_key).unwrap(); let ca_cert = params.self_signed(&ca_kp).unwrap(); assert_eq!(&ca_ski, &ca_cert.key_identifier()); let (_, x509_ca) = x509_parser::parse_x509_certificate(ca_cert.der()).unwrap(); assert_eq!( &ca_ski, &x509_ca .iter_extensions() .find_map(|ext| match ext.parsed_extension() { x509_parser::extensions::ParsedExtension::SubjectKeyIdentifier(key_id) => { Some(key_id.0.to_vec()) }, _ => None, }) .unwrap() ); let ee_key = KeyPair::generate().unwrap(); let mut ee_params = CertificateParams::default(); ee_params.use_authority_key_identifier_extension = true; let ee_cert = ee_params.signed_by(&ee_key, &ca_cert, &ee_key).unwrap(); let (_, x509_ee) = x509_parser::parse_x509_certificate(ee_cert.der()).unwrap(); assert_eq!( &ca_ski, &x509_ee .iter_extensions() .find_map(|ext| match ext.parsed_extension() { x509_parser::extensions::ParsedExtension::AuthorityKeyIdentifier(aki) => { aki.key_identifier.as_ref().map(|ki| ki.0.to_vec()) }, _ => None, }) .unwrap() ); } } } rcgen-0.13.2/src/crl.rs000064400000000000000000000345031046102023000127270ustar 00000000000000#[cfg(feature = "pem")] use pem::Pem; use pki_types::CertificateRevocationListDer; use time::OffsetDateTime; use yasna::DERWriter; use yasna::Tag; #[cfg(feature = "pem")] use crate::ENCODE_CONFIG; use crate::{ oid, write_distinguished_name, write_dt_utc_or_generalized, write_x509_authority_key_identifier, write_x509_extension, Certificate, Error, Issuer, KeyIdMethod, KeyPair, KeyUsagePurpose, SerialNumber, }; /// A certificate revocation list (CRL) /// /// ## Example /// /// ``` /// extern crate rcgen; /// use rcgen::*; /// /// #[cfg(not(feature = "crypto"))] /// struct MyKeyPair { public_key: Vec } /// #[cfg(not(feature = "crypto"))] /// impl RemoteKeyPair for MyKeyPair { /// fn public_key(&self) -> &[u8] { &self.public_key } /// fn sign(&self, _: &[u8]) -> Result, rcgen::Error> { Ok(vec![]) } /// fn algorithm(&self) -> &'static SignatureAlgorithm { &PKCS_ED25519 } /// } /// # fn main () { /// // Generate a CRL issuer. /// let mut issuer_params = CertificateParams::new(vec!["crl.issuer.example.com".to_string()]).unwrap(); /// issuer_params.serial_number = Some(SerialNumber::from(9999)); /// issuer_params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); /// issuer_params.key_usages = vec![KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::DigitalSignature, KeyUsagePurpose::CrlSign]; /// #[cfg(feature = "crypto")] /// let key_pair = KeyPair::generate().unwrap(); /// #[cfg(not(feature = "crypto"))] /// let remote_key_pair = MyKeyPair { public_key: vec![] }; /// #[cfg(not(feature = "crypto"))] /// let key_pair = KeyPair::from_remote(Box::new(remote_key_pair)).unwrap(); /// let issuer = issuer_params.self_signed(&key_pair).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], /// #[cfg(feature = "crypto")] /// key_identifier_method: KeyIdMethod::Sha256, /// #[cfg(not(feature = "crypto"))] /// key_identifier_method: KeyIdMethod::PreSpecified(vec![]), /// }.signed_by(&issuer, &key_pair).unwrap(); ///# } pub struct CertificateRevocationList { params: CertificateRevocationListParams, der: CertificateRevocationListDer<'static>, } impl CertificateRevocationList { /// Returns the certificate revocation list (CRL) parameters. pub fn params(&self) -> &CertificateRevocationListParams { &self.params } /// Get the CRL in PEM encoded format. #[cfg(feature = "pem")] pub fn pem(&self) -> Result { let p = Pem::new("X509 CRL", &*self.der); Ok(pem::encode_config(&p, ENCODE_CONFIG)) } /// Get the CRL in DER encoded format. /// /// [`CertificateRevocationListDer`] implements `Deref` and `AsRef<[u8]>`, /// so you can easily extract the DER bytes from the return value. pub fn der(&self) -> &CertificateRevocationListDer<'static> { &self.der } } impl From for CertificateRevocationListDer<'static> { fn from(crl: CertificateRevocationList) -> Self { crl.der } } /// 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, /// Method to generate key identifiers from public keys /// /// Defaults to SHA-256. pub key_identifier_method: KeyIdMethod, } impl CertificateRevocationListParams { /// Serializes the certificate revocation list (CRL). /// /// Including a signature from the issuing certificate authority's key. pub fn signed_by( self, issuer: &Certificate, issuer_key: &KeyPair, ) -> Result { if self.next_update.le(&self.this_update) { return Err(Error::InvalidCrlNextUpdate); } let issuer = Issuer { distinguished_name: &issuer.params.distinguished_name, key_identifier_method: &issuer.params.key_identifier_method, key_usages: &issuer.params.key_usages, key_pair: issuer_key, }; if !issuer.key_usages.is_empty() && !issuer.key_usages.contains(&KeyUsagePurpose::CrlSign) { return Err(Error::IssuerNotCrlSigner); } Ok(CertificateRevocationList { der: self.serialize_der(issuer)?.into(), params: self, }) } fn serialize_der(&self, issuer: Issuer) -> Result, Error> { issuer.key_pair.sign_der(|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 issuer.key_pair.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(), &issuer.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(), self.key_identifier_method .derive(issuer.key_pair.public_key_der()), ); // 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. if let Some(reason_code) = self.reason_code { write_x509_extension(writer.next(), oid::CRL_REASONS, false, |writer| { writer.write_enum(reason_code as i64); }); } // Write invalidity date if present. if let Some(invalidity_date) = self.invalidity_date { write_x509_extension( writer.next(), oid::CRL_INVALIDITY_DATE, false, |writer| { write_dt_utc_or_generalized(writer, invalidity_date); }, ) } }); } }) } } rcgen-0.13.2/src/csr.rs000064400000000000000000000156731046102023000127450ustar 00000000000000use std::hash::Hash; #[cfg(feature = "pem")] use pem::Pem; use pki_types::CertificateSigningRequestDer; #[cfg(feature = "pem")] use crate::ENCODE_CONFIG; use crate::{ key_pair::serialize_public_key_der, Certificate, CertificateParams, Error, Issuer, KeyPair, PublicKeyData, SignatureAlgorithm, }; #[cfg(feature = "x509-parser")] use crate::{DistinguishedName, SanType}; /// A public key, extracted from a CSR #[derive(Debug, PartialEq, Eq, Hash)] pub struct PublicKey { raw: Vec, alg: &'static SignatureAlgorithm, } impl PublicKey { /// The algorithm used to generate the public key and sign the CSR. pub fn algorithm(&self) -> &SignatureAlgorithm { self.alg } } impl PublicKeyData for PublicKey { fn der_bytes(&self) -> &[u8] { &self.raw } fn algorithm(&self) -> &SignatureAlgorithm { self.alg } } /// A certificate signing request (CSR) that can be encoded to PEM or DER. pub struct CertificateSigningRequest { pub(crate) der: CertificateSigningRequestDer<'static>, } impl CertificateSigningRequest { /// Get the PEM-encoded bytes of the certificate signing request. #[cfg(feature = "pem")] pub fn pem(&self) -> Result { let p = Pem::new("CERTIFICATE REQUEST", &*self.der); Ok(pem::encode_config(&p, ENCODE_CONFIG)) } /// Get the DER-encoded bytes of the certificate signing request. /// /// [`CertificateSigningRequestDer`] implements `Deref` and `AsRef<[u8]>`, /// so you can easily extract the DER bytes from the return value. pub fn der(&self) -> &CertificateSigningRequestDer<'static> { &self.der } } impl From for CertificateSigningRequestDer<'static> { fn from(csr: CertificateSigningRequest) -> Self { csr.der } } /// Parameters for a certificate signing request pub struct CertificateSigningRequestParams { /// Parameters for the certificate to be signed. pub params: CertificateParams, /// Public key to include in the certificate signing request. pub public_key: PublicKey, } impl CertificateSigningRequestParams { /// 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(Error::CouldNotParseCertificationRequest))?; Self::from_der(&csr.contents().into()) } /// 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. /// /// [`rustls_pemfile::csr()`] is often used to obtain a [`CertificateSigningRequestDer`] from /// PEM input. If you already have a byte slice containing DER, it can trivially be converted /// into [`CertificateSigningRequestDer`] using the [`Into`] trait. /// /// [`rustls_pemfile::csr()`]: https://docs.rs/rustls-pemfile/latest/rustls_pemfile/fn.csr.html #[cfg(feature = "x509-parser")] pub fn from_der(csr: &CertificateSigningRequestDer<'_>) -> Result { use crate::KeyUsagePurpose; use x509_parser::prelude::FromDer; let csr = x509_parser::certification_request::X509CertificationRequest::from_der(csr) .map_err(|_| Error::CouldNotParseCertificationRequest)? .1; csr.verify_signature().map_err(|_| Error::RingUnspecified)?; let alg_oid = csr .signature_algorithm .algorithm .iter() .ok_or(Error::CouldNotParseCertificationRequest)? .collect::>(); let alg = SignatureAlgorithm::from_oid(&alg_oid)?; let info = &csr.certification_request_info; let mut params = CertificateParams { distinguished_name: DistinguishedName::from_name(&info.subject)?, ..CertificateParams::default() }; 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::KeyUsage(key_usage) => { // This x509 parser stores flags in reversed bit BIT STRING order params.key_usages = KeyUsagePurpose::from_u16(key_usage.flags.reverse_bits()); }, x509_parser::extensions::ParsedExtension::SubjectAlternativeName(san) => { for name in &san.general_names { params .subject_alt_names .push(SanType::try_from_general(name)?); } }, x509_parser::extensions::ParsedExtension::ExtendedKeyUsage(eku) => { if eku.any { params.insert_extended_key_usage(crate::ExtendedKeyUsagePurpose::Any); } if eku.server_auth { params.insert_extended_key_usage( crate::ExtendedKeyUsagePurpose::ServerAuth, ); } if eku.client_auth { params.insert_extended_key_usage( crate::ExtendedKeyUsagePurpose::ClientAuth, ); } if eku.code_signing { params.insert_extended_key_usage( crate::ExtendedKeyUsagePurpose::CodeSigning, ); } if eku.email_protection { params.insert_extended_key_usage( crate::ExtendedKeyUsagePurpose::EmailProtection, ); } if eku.time_stamping { params.insert_extended_key_usage( crate::ExtendedKeyUsagePurpose::TimeStamping, ); } if eku.ocsp_signing { params.insert_extended_key_usage( crate::ExtendedKeyUsagePurpose::OcspSigning, ); } if !eku.other.is_empty() { return Err(Error::UnsupportedExtension); } }, _ => return Err(Error::UnsupportedExtension), } } } // Not yet handled: // * is_ca // * extended_key_usages // * name_constraints // and any other extensions. Ok(Self { params, public_key: PublicKey { alg, raw }, }) } /// Generate a new certificate based on the requested parameters, signed by the provided /// issuer. /// /// The returned certificate will have its issuer field set to the subject of the provided /// `issuer`, and the authority key identifier extension will be populated using the subject /// public key of `issuer`. It will be signed by `issuer_key`. /// /// Note that no validation of the `issuer` certificate is performed. Rcgen will not require /// the certificate to be a CA certificate, or have key usage extensions that allow signing. /// /// The returned [`Certificate`] may be serialized using [`Certificate::der`] and /// [`Certificate::pem`]. pub fn signed_by( self, issuer: &Certificate, issuer_key: &KeyPair, ) -> Result { let issuer = Issuer { distinguished_name: &issuer.params.distinguished_name, key_identifier_method: &issuer.params.key_identifier_method, key_usages: &issuer.params.key_usages, key_pair: issuer_key, }; let der = self .params .serialize_der_with_signer(&self.public_key, issuer)?; let subject_public_key_info = yasna::construct_der(|writer| { serialize_public_key_der(&self.public_key, writer); }); Ok(Certificate { params: self.params, subject_public_key_info, der, }) } } rcgen-0.13.2/src/error.rs000064400000000000000000000111511046102023000132720ustar 00000000000000use std::fmt; #[derive(Debug, PartialEq, Eq)] #[non_exhaustive] /// The error type of the rcgen crate pub enum Error { /// 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, /// Invalid ASN.1 string InvalidAsn1String(InvalidAsn1String), /// 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(String), /// Time conversion related errors Time, #[cfg(feature = "pem")] /// Error from the pem crate PemError(String), /// 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, #[cfg(not(feature = "crypto"))] /// Missing serial number MissingSerialNumber, /// X509 parsing error #[cfg(feature = "x509-parser")] X509(String), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::Error::*; 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")?, InvalidAsn1String(e) => write!(f, "{}", e)?, 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)?, 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" )?, #[cfg(not(feature = "crypto"))] MissingSerialNumber => write!(f, "A serial number must be specified")?, #[cfg(feature = "x509-parser")] X509(e) => write!(f, "X.509 parsing error: {e}")?, }; Ok(()) } } impl std::error::Error for Error {} /// Invalid ASN.1 string type #[derive(Debug, PartialEq, Eq)] #[non_exhaustive] pub enum InvalidAsn1String { /// Invalid PrintableString type PrintableString(String), /// Invalid UniversalString type UniversalString(String), /// Invalid Ia5String type Ia5String(String), /// Invalid TeletexString type TeletexString(String), /// Invalid BmpString type BmpString(String), } impl fmt::Display for InvalidAsn1String { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use InvalidAsn1String::*; match self { PrintableString(s) => write!(f, "Invalid PrintableString: '{}'", s)?, Ia5String(s) => write!(f, "Invalid IA5String: '{}'", s)?, BmpString(s) => write!(f, "Invalid BMPString: '{}'", s)?, UniversalString(s) => write!(f, "Invalid UniversalString: '{}'", s)?, TeletexString(s) => write!(f, "Invalid TeletexString: '{}'", s)?, }; Ok(()) } } /// A trait describing an error that can be converted into an `rcgen::Error`. /// /// We use this trait to avoid leaking external error types into the public API /// through a `From for Error` implementation. #[cfg(any(feature = "crypto", feature = "pem"))] pub(crate) trait ExternalError: Sized { fn _err(self) -> Result; } rcgen-0.13.2/src/key_pair.rs000064400000000000000000000636711046102023000137620ustar 00000000000000use std::fmt; #[cfg(feature = "pem")] use pem::Pem; #[cfg(feature = "crypto")] use pki_types::{PrivateKeyDer, PrivatePkcs8KeyDer}; use yasna::{DERWriter, DERWriterSeq}; #[cfg(any(feature = "crypto", feature = "pem"))] use crate::error::ExternalError; #[cfg(all(feature = "crypto", feature = "aws_lc_rs"))] use crate::ring_like::ecdsa_from_private_key_der; #[cfg(all(feature = "crypto", feature = "aws_lc_rs"))] use crate::ring_like::rsa::KeySize; #[cfg(feature = "crypto")] use crate::ring_like::{ error as ring_error, rand::SystemRandom, signature::{ self, EcdsaKeyPair, Ed25519KeyPair, KeyPair as RingKeyPair, RsaEncoding, RsaKeyPair, }, {ecdsa_from_pkcs8, rsa_key_pair_public_modulus_len}, }; #[cfg(feature = "crypto")] use crate::sign_algo::{algo::*, SignAlgo}; #[cfg(feature = "pem")] use crate::ENCODE_CONFIG; use crate::{sign_algo::SignatureAlgorithm, Error}; /// A key pair variant #[allow(clippy::large_enum_variant)] pub(crate) enum KeyPairKind { /// A Ecdsa key pair #[cfg(feature = "crypto")] Ec(EcdsaKeyPair), /// A Ed25519 key pair #[cfg(feature = "crypto")] Ed(Ed25519KeyPair), /// A RSA key pair #[cfg(feature = "crypto")] 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 { #[cfg(feature = "crypto")] Self::Ec(key_pair) => write!(f, "{:?}", key_pair), #[cfg(feature = "crypto")] Self::Ed(key_pair) => write!(f, "{:?}", key_pair), #[cfg(feature = "crypto")] 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 { /// Generate a new random [`PKCS_ECDSA_P256_SHA256`] key pair #[cfg(feature = "crypto")] pub fn generate() -> Result { Self::generate_for(&PKCS_ECDSA_P256_SHA256) } /// Generate a new random key pair for the specified signature algorithm /// /// If you're not sure which algorithm to use, [`PKCS_ECDSA_P256_SHA256`] is a good choice. /// If passed an RSA signature algorithm, it depends on the backend whether we return /// a generated key or an error for key generation being unavailable. /// Currently, only `aws-lc-rs` supports RSA key generation. #[cfg(feature = "crypto")] pub fn generate_for(alg: &'static SignatureAlgorithm) -> Result { let rng = &SystemRandom::new(); match alg.sign_alg { SignAlgo::EcDsa(sign_alg) => { let key_pair_doc = EcdsaKeyPair::generate_pkcs8(sign_alg, rng)._err()?; let key_pair_serialized = key_pair_doc.as_ref().to_vec(); let key_pair = ecdsa_from_pkcs8(sign_alg, key_pair_doc.as_ref(), rng).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(rng)._err()?; 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, }) }, #[cfg(feature = "aws_lc_rs")] SignAlgo::Rsa(sign_alg) => Self::generate_rsa_inner(alg, sign_alg, KeySize::Rsa2048), // Ring doesn't have RSA key generation yet: // https://github.com/briansmith/ring/issues/219 // https://github.com/briansmith/ring/pull/733 #[cfg(all(feature = "ring", not(feature = "aws_lc_rs")))] SignAlgo::Rsa(_sign_alg) => Err(Error::KeyGenerationUnavailable), } } /// Generates a new random RSA key pair for the specified key size /// /// If passed a signature algorithm that is not RSA, it will return /// [`Error::KeyGenerationUnavailable`]. #[cfg(all(feature = "crypto", feature = "aws_lc_rs"))] pub fn generate_rsa_for( alg: &'static SignatureAlgorithm, key_size: RsaKeySize, ) -> Result { match alg.sign_alg { SignAlgo::Rsa(sign_alg) => { let key_size = match key_size { RsaKeySize::_2048 => KeySize::Rsa2048, RsaKeySize::_3072 => KeySize::Rsa3072, RsaKeySize::_4096 => KeySize::Rsa4096, }; Self::generate_rsa_inner(alg, sign_alg, key_size) }, _ => Err(Error::KeyGenerationUnavailable), } } #[cfg(all(feature = "crypto", feature = "aws_lc_rs"))] fn generate_rsa_inner( alg: &'static SignatureAlgorithm, sign_alg: &'static dyn RsaEncoding, key_size: KeySize, ) -> Result { use aws_lc_rs::encoding::AsDer; let key_pair = RsaKeyPair::generate(key_size)._err()?; let key_pair_serialized = key_pair.as_der()._err()?.as_ref().to_vec(); Ok(KeyPair { kind: KeyPairKind::Rsa(key_pair, sign_alg), alg, serialized_der: key_pair_serialized, }) } /// Returns the key pair's signature algorithm pub fn algorithm(&self) -> &'static SignatureAlgorithm { self.alg } /// Parses the key pair from the ASCII PEM format /// /// If `aws_lc_rs` feature is used, then the key must be a DER-encoded plaintext private key; as specified in PKCS #8/RFC 5958, SEC1/RFC 5915, or PKCS#1/RFC 3447; /// Appears as "PRIVATE KEY", "RSA PRIVATE KEY", or "EC PRIVATE KEY" in PEM files. /// /// Otherwise if the `ring` feature is used, then the key must be a DER-encoded plaintext private key; as specified in PKCS #8/RFC 5958; /// Appears as "PRIVATE KEY" in PEM files. #[cfg(all(feature = "pem", feature = "crypto"))] pub fn from_pem(pem_str: &str) -> Result { let private_key = pem::parse(pem_str)._err()?; Self::try_from(private_key.contents()) } /// 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`] /// /// The key must be a DER-encoded plaintext private key; as specified in PKCS #8/RFC 5958; /// /// Appears as "PRIVATE KEY" in PEM files /// Same as [from_pkcs8_pem_and_sign_algo](Self::from_pkcs8_pem_and_sign_algo). #[cfg(all(feature = "pem", feature = "crypto"))] pub fn from_pkcs8_pem_and_sign_algo( pem_str: &str, alg: &'static SignatureAlgorithm, ) -> Result { let private_key = pem::parse(pem_str)._err()?; let private_key_der: &[_] = private_key.contents(); Self::from_pkcs8_der_and_sign_algo(&PrivatePkcs8KeyDer::from(private_key_der), alg) } /// Obtains the key pair from a DER formatted key using the specified [`SignatureAlgorithm`] /// /// If you have a [`PrivatePkcs8KeyDer`], you can usually rely on the [`TryFrom`] implementation /// to obtain a [`KeyPair`] -- it will determine the correct [`SignatureAlgorithm`] for you. /// However, sometimes multiple signature algorithms fit for the same DER key. In those instances, /// you can use this function to precisely specify the `SignatureAlgorithm`. /// /// [`rustls_pemfile::private_key()`] is often used to obtain a [`PrivateKeyDer`] from PEM /// input. If the obtained [`PrivateKeyDer`] is a `Pkcs8` variant, you can use its contents /// as input for this function. Alternatively, if you already have a byte slice containing DER, /// it can trivially be converted into [`PrivatePkcs8KeyDer`] using the [`Into`] trait. /// /// [`rustls_pemfile::private_key()`]: https://docs.rs/rustls-pemfile/latest/rustls_pemfile/fn.private_key.html /// [`PrivateKeyDer`]: https://docs.rs/rustls-pki-types/latest/rustls_pki_types/enum.PrivateKeyDer.html #[cfg(feature = "crypto")] pub fn from_pkcs8_der_and_sign_algo( pkcs8: &PrivatePkcs8KeyDer<'_>, alg: &'static SignatureAlgorithm, ) -> Result { let rng = &SystemRandom::new(); let serialized_der = pkcs8.secret_pkcs8_der().to_vec(); let kind = if alg == &PKCS_ED25519 { KeyPairKind::Ed(Ed25519KeyPair::from_pkcs8_maybe_unchecked(&serialized_der)._err()?) } else if alg == &PKCS_ECDSA_P256_SHA256 { KeyPairKind::Ec(ecdsa_from_pkcs8( &signature::ECDSA_P256_SHA256_ASN1_SIGNING, &serialized_der, rng, )?) } else if alg == &PKCS_ECDSA_P384_SHA384 { KeyPairKind::Ec(ecdsa_from_pkcs8( &signature::ECDSA_P384_SHA384_ASN1_SIGNING, &serialized_der, rng, )?) } else if alg == &PKCS_RSA_SHA256 { let rsakp = RsaKeyPair::from_pkcs8(&serialized_der)._err()?; KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA256) } else if alg == &PKCS_RSA_SHA384 { let rsakp = RsaKeyPair::from_pkcs8(&serialized_der)._err()?; KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA384) } else if alg == &PKCS_RSA_SHA512 { let rsakp = RsaKeyPair::from_pkcs8(&serialized_der)._err()?; KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA512) } else if alg == &PKCS_RSA_PSS_SHA256 { let rsakp = RsaKeyPair::from_pkcs8(&serialized_der)._err()?; KeyPairKind::Rsa(rsakp, &signature::RSA_PSS_SHA256) } else { #[cfg(feature = "aws_lc_rs")] if alg == &PKCS_ECDSA_P521_SHA512 { KeyPairKind::Ec(ecdsa_from_pkcs8( &signature::ECDSA_P521_SHA512_ASN1_SIGNING, &serialized_der, rng, )?) } else { panic!("Unknown SignatureAlgorithm specified!"); } #[cfg(all(feature = "ring", not(feature = "aws_lc_rs")))] panic!("Unknown SignatureAlgorithm specified!"); }; Ok(KeyPair { kind, alg, serialized_der, }) } /// Obtains the key pair from a PEM formatted key /// using the specified [`SignatureAlgorithm`] /// /// If `aws_lc_rs` feature is used, then the key must be a DER-encoded plaintext private key; as specified in PKCS #8/RFC 5958, SEC1/RFC 5915, or PKCS#1/RFC 3447; /// Appears as "PRIVATE KEY", "RSA PRIVATE KEY", or "EC PRIVATE KEY" in PEM files. /// /// Otherwise if the `ring` feature is used, then the key must be a DER-encoded plaintext private key; as specified in PKCS #8/RFC 5958; /// Appears as "PRIVATE KEY" in PEM files. /// /// Same as [from_pem_and_sign_algo](Self::from_pem_and_sign_algo). #[cfg(all(feature = "pem", feature = "crypto"))] pub fn from_pem_and_sign_algo( pem_str: &str, alg: &'static SignatureAlgorithm, ) -> Result { let private_key = pem::parse(pem_str)._err()?; let private_key: &[_] = private_key.contents(); Self::from_der_and_sign_algo( &PrivateKeyDer::try_from(private_key).map_err(|_| Error::CouldNotParseKeyPair)?, alg, ) } /// Obtains the key pair from a DER formatted key /// using the specified [`SignatureAlgorithm`] /// /// Note that using the `ring` feature, this function only support [`PrivateKeyDer::Pkcs8`] variant. /// Consider using the `aws_lc_rs` features to support [`PrivateKeyDer`] fully. /// /// If you have a [`PrivateKeyDer`], you can usually rely on the [`TryFrom`] implementation /// to obtain a [`KeyPair`] -- it will determine the correct [`SignatureAlgorithm`] for you. /// However, sometimes multiple signature algorithms fit for the same DER key. In those instances, /// you can use this function to precisely specify the `SignatureAlgorithm`. /// /// You can use [`rustls_pemfile::private_key`] to get the `key` input. If /// you have already a byte slice, just calling `try_into()` will convert it to a [`PrivateKeyDer`]. /// /// [`rustls_pemfile::private_key`]: https://docs.rs/rustls-pemfile/latest/rustls_pemfile/fn.private_key.html #[cfg(feature = "crypto")] pub fn from_der_and_sign_algo( key: &PrivateKeyDer<'_>, alg: &'static SignatureAlgorithm, ) -> Result { #[cfg(all(feature = "ring", not(feature = "aws_lc_rs")))] { if let PrivateKeyDer::Pkcs8(key) = key { Self::from_pkcs8_der_and_sign_algo(key, alg) } else { Err(Error::CouldNotParseKeyPair) } } #[cfg(feature = "aws_lc_rs")] { let is_pkcs8 = matches!(key, PrivateKeyDer::Pkcs8(_)); let rsa_key_pair_from = if is_pkcs8 { RsaKeyPair::from_pkcs8 } else { RsaKeyPair::from_der }; let serialized_der = key.secret_der().to_vec(); let kind = if alg == &PKCS_ED25519 { KeyPairKind::Ed(Ed25519KeyPair::from_pkcs8_maybe_unchecked(&serialized_der)._err()?) } else if alg == &PKCS_ECDSA_P256_SHA256 { KeyPairKind::Ec(ecdsa_from_private_key_der( &signature::ECDSA_P256_SHA256_ASN1_SIGNING, &serialized_der, )?) } else if alg == &PKCS_ECDSA_P384_SHA384 { KeyPairKind::Ec(ecdsa_from_private_key_der( &signature::ECDSA_P384_SHA384_ASN1_SIGNING, &serialized_der, )?) } else if alg == &PKCS_ECDSA_P521_SHA512 { KeyPairKind::Ec(ecdsa_from_private_key_der( &signature::ECDSA_P521_SHA512_ASN1_SIGNING, &serialized_der, )?) } else if alg == &PKCS_RSA_SHA256 { let rsakp = rsa_key_pair_from(&serialized_der)._err()?; KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA256) } else if alg == &PKCS_RSA_SHA384 { let rsakp = rsa_key_pair_from(&serialized_der)._err()?; KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA384) } else if alg == &PKCS_RSA_SHA512 { let rsakp = rsa_key_pair_from(&serialized_der)._err()?; KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA512) } else if alg == &PKCS_RSA_PSS_SHA256 { let rsakp = rsa_key_pair_from(&serialized_der)._err()?; KeyPairKind::Rsa(rsakp, &signature::RSA_PSS_SHA256) } else { panic!("Unknown SignatureAlgorithm specified!"); }; Ok(KeyPair { kind, alg, serialized_der, }) } } /// 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.der_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_der( &self, f: impl FnOnce(&mut DERWriterSeq<'_>) -> Result<(), Error>, ) -> Result, Error> { yasna::try_construct_der(|writer| { writer.write_sequence(|writer| { let data = yasna::try_construct_der(|writer| writer.write_sequence(f))?; writer.next().write_der(&data); // Write signatureAlgorithm self.alg.write_alg_ident(writer.next()); // Write signature self.sign(&data, writer.next())?; Ok(()) }) }) } pub(crate) fn sign(&self, msg: &[u8], writer: DERWriter) -> Result<(), Error> { match &self.kind { #[cfg(feature = "crypto")] KeyPairKind::Ec(kp) => { let system_random = SystemRandom::new(); let signature = kp.sign(&system_random, msg)._err()?; let sig = &signature.as_ref(); writer.write_bitvec_bytes(sig, &sig.len() * 8); }, #[cfg(feature = "crypto")] KeyPairKind::Ed(kp) => { let signature = kp.sign(msg); let sig = &signature.as_ref(); writer.write_bitvec_bytes(sig, &sig.len() * 8); }, #[cfg(feature = "crypto")] KeyPairKind::Rsa(kp, padding_alg) => { let system_random = SystemRandom::new(); let mut signature = vec![0; rsa_key_pair_public_modulus_len(kp)]; kp.sign(*padding_alg, &system_random, msg, &mut signature) ._err()?; 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| serialize_public_key_der(self, 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 { #[cfg_attr(not(feature = "crypto"), allow(irrefutable_let_patterns))] 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] { #[cfg_attr(not(feature = "crypto"), allow(irrefutable_let_patterns))] 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)> { #[cfg_attr(not(feature = "crypto"), allow(irrefutable_let_patterns))] 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) } } #[cfg(feature = "crypto")] impl TryFrom<&[u8]> for KeyPair { type Error = Error; fn try_from(key: &[u8]) -> Result { let key = &PrivateKeyDer::try_from(key).map_err(|_| Error::CouldNotParseKeyPair)?; key.try_into() } } #[cfg(feature = "crypto")] impl TryFrom> for KeyPair { type Error = Error; fn try_from(key: Vec) -> Result { let key = &PrivateKeyDer::try_from(key).map_err(|_| Error::CouldNotParseKeyPair)?; key.try_into() } } #[cfg(feature = "crypto")] impl TryFrom<&PrivatePkcs8KeyDer<'_>> for KeyPair { type Error = Error; fn try_from(key: &PrivatePkcs8KeyDer) -> Result { key.secret_pkcs8_der().try_into() } } #[cfg(feature = "crypto")] impl TryFrom<&PrivateKeyDer<'_>> for KeyPair { type Error = Error; fn try_from(key: &PrivateKeyDer) -> Result { #[cfg(all(feature = "ring", not(feature = "aws_lc_rs")))] let (kind, alg) = { let PrivateKeyDer::Pkcs8(pkcs8) = key else { return Err(Error::CouldNotParseKeyPair); }; let pkcs8 = pkcs8.secret_pkcs8_der(); let rng = SystemRandom::new(); let (kind, alg) = if let Ok(edkp) = Ed25519KeyPair::from_pkcs8_maybe_unchecked(pkcs8) { (KeyPairKind::Ed(edkp), &PKCS_ED25519) } else if let Ok(eckp) = ecdsa_from_pkcs8(&signature::ECDSA_P256_SHA256_ASN1_SIGNING, pkcs8, &rng) { (KeyPairKind::Ec(eckp), &PKCS_ECDSA_P256_SHA256) } else if let Ok(eckp) = ecdsa_from_pkcs8(&signature::ECDSA_P384_SHA384_ASN1_SIGNING, pkcs8, &rng) { (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(Error::CouldNotParseKeyPair); }; (kind, alg) }; #[cfg(feature = "aws_lc_rs")] let (kind, alg) = { let is_pkcs8 = matches!(key, PrivateKeyDer::Pkcs8(_)); let key = key.secret_der(); let rsa_key_pair_from = if is_pkcs8 { RsaKeyPair::from_pkcs8 } else { RsaKeyPair::from_der }; let (kind, alg) = if let Ok(edkp) = Ed25519KeyPair::from_pkcs8_maybe_unchecked(key) { (KeyPairKind::Ed(edkp), &PKCS_ED25519) } else if let Ok(eckp) = ecdsa_from_private_key_der(&signature::ECDSA_P256_SHA256_ASN1_SIGNING, key) { (KeyPairKind::Ec(eckp), &PKCS_ECDSA_P256_SHA256) } else if let Ok(eckp) = ecdsa_from_private_key_der(&signature::ECDSA_P384_SHA384_ASN1_SIGNING, key) { (KeyPairKind::Ec(eckp), &PKCS_ECDSA_P384_SHA384) } else if let Ok(eckp) = ecdsa_from_private_key_der(&signature::ECDSA_P521_SHA512_ASN1_SIGNING, key) { (KeyPairKind::Ec(eckp), &PKCS_ECDSA_P521_SHA512) } else if let Ok(rsakp) = rsa_key_pair_from(key) { ( KeyPairKind::Rsa(rsakp, &signature::RSA_PKCS1_SHA256), &PKCS_RSA_SHA256, ) } else { return Err(Error::CouldNotParseKeyPair); }; (kind, alg) }; Ok(KeyPair { kind, alg, serialized_der: key.secret_der().into(), }) } } /// The key size used for RSA key generation #[cfg(all(feature = "crypto", feature = "aws_lc_rs"))] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum RsaKeySize { /// 2048 bits _2048, /// 3072 bits _3072, /// 4096 bits _4096, } impl PublicKeyData for KeyPair { fn der_bytes(&self) -> &[u8] { match &self.kind { #[cfg(feature = "crypto")] KeyPairKind::Ec(kp) => kp.public_key().as_ref(), #[cfg(feature = "crypto")] KeyPairKind::Ed(kp) => kp.public_key().as_ref(), #[cfg(feature = "crypto")] KeyPairKind::Rsa(kp, _) => kp.public_key().as_ref(), KeyPairKind::Remote(kp) => kp.public_key(), } } fn algorithm(&self) -> &SignatureAlgorithm { self.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, Error>; /// Reveals the algorithm to be used when calling `sign()` fn algorithm(&self) -> &'static SignatureAlgorithm; } #[cfg(feature = "crypto")] impl ExternalError for Result { fn _err(self) -> Result { self.map_err(|e| Error::RingKeyRejected(e.to_string())) } } #[cfg(feature = "crypto")] impl ExternalError for Result { fn _err(self) -> Result { self.map_err(|_| Error::RingUnspecified) } } #[cfg(feature = "pem")] impl ExternalError for Result { fn _err(self) -> Result { self.map_err(|e| Error::PemError(e.to_string())) } } /// A public key #[derive(Clone, Debug, Eq, PartialEq)] pub struct SubjectPublicKeyInfo { pub(crate) alg: &'static SignatureAlgorithm, pub(crate) subject_public_key: Vec, } impl SubjectPublicKeyInfo { /// Create a `SubjectPublicKey` value from a PEM-encoded SubjectPublicKeyInfo string #[cfg(all(feature = "x509-parser", feature = "pem"))] pub fn from_pem(pem_str: &str) -> Result { Self::from_der(&pem::parse(pem_str)._err()?.into_contents()) } /// Create a `SubjectPublicKey` value from DER-encoded SubjectPublicKeyInfo bytes #[cfg(feature = "x509-parser")] pub fn from_der(spki_der: &[u8]) -> Result { use x509_parser::{ prelude::FromDer, x509::{AlgorithmIdentifier, SubjectPublicKeyInfo}, }; let (rem, spki) = SubjectPublicKeyInfo::from_der(spki_der).map_err(|e| Error::X509(e.to_string()))?; if !rem.is_empty() { return Err(Error::X509( "trailing bytes in SubjectPublicKeyInfo".to_string(), )); } let alg = SignatureAlgorithm::iter() .find(|alg| { let bytes = yasna::construct_der(|writer| { alg.write_oids_sign_alg(writer); }); let Ok((rest, aid)) = AlgorithmIdentifier::from_der(&bytes) else { return false; }; if !rest.is_empty() { return false; } aid == spki.algorithm }) .ok_or(Error::UnsupportedSignatureAlgorithm)?; Ok(Self { alg, subject_public_key: Vec::from(spki.subject_public_key.as_ref()), }) } } impl PublicKeyData for SubjectPublicKeyInfo { fn der_bytes(&self) -> &[u8] { &self.subject_public_key } fn algorithm(&self) -> &SignatureAlgorithm { self.alg } } /// The public key data of a key pair pub trait PublicKeyData { /// The public key in DER format fn der_bytes(&self) -> &[u8]; /// The algorithm used by the key pair fn algorithm(&self) -> &SignatureAlgorithm; } pub(crate) fn serialize_public_key_der(key: &impl PublicKeyData, writer: DERWriter) { writer.write_sequence(|writer| { key.algorithm().write_oids_sign_alg(writer.next()); let pk = key.der_bytes(); writer.next().write_bitvec_bytes(pk, pk.len() * 8); }) } #[cfg(all(test, feature = "crypto"))] mod test { use super::*; use crate::ring_like::{ rand::SystemRandom, signature::{EcdsaKeyPair, ECDSA_P256_SHA256_FIXED_SIGNING}, }; #[cfg(all(feature = "x509-parser", feature = "pem"))] #[test] fn test_subject_public_key_parsing() { for alg in [ &PKCS_ED25519, &PKCS_ECDSA_P256_SHA256, &PKCS_ECDSA_P384_SHA384, #[cfg(feature = "aws_lc_rs")] &PKCS_ECDSA_P521_SHA512, #[cfg(feature = "aws_lc_rs")] &PKCS_RSA_SHA256, ] { let kp = KeyPair::generate_for(alg).expect("keygen"); let pem = kp.public_key_pem(); let der = kp.public_key_der(); let pkd_pem = SubjectPublicKeyInfo::from_pem(&pem).expect("from pem"); assert_eq!(kp.der_bytes(), pkd_pem.der_bytes()); let pkd_der = SubjectPublicKeyInfo::from_der(&der).expect("from der"); assert_eq!(kp.der_bytes(), pkd_der.der_bytes()); } } #[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::try_from(der).unwrap(); assert_eq!(key_pair.algorithm(), &PKCS_ECDSA_P256_SHA256); } } rcgen-0.13.2/src/lib.rs000064400000000000000000000614431046102023000127200ustar 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, construct a [`CertificateParams`] and a key pair to call [`CertificateParams::signed_by()`] or [`CertificateParams::self_signed()`]. */ #![cfg_attr( feature = "pem", doc = r##" ## Example ``` use rcgen::{generate_simple_self_signed, CertifiedKey}; # 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 CertifiedKey { cert, key_pair } = generate_simple_self_signed(subject_alt_names).unwrap(); println!("{}", cert.pem()); println!("{}", key_pair.serialize_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))] #![warn(unreachable_pub)] use std::collections::HashMap; use std::fmt; use std::hash::Hash; use std::net::IpAddr; #[cfg(feature = "x509-parser")] use std::net::{Ipv4Addr, Ipv6Addr}; use time::{OffsetDateTime, 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 certificate::{ date_time_ymd, Attribute, BasicConstraints, Certificate, CertificateParams, CidrSubnet, CustomExtension, DnType, ExtendedKeyUsagePurpose, GeneralSubtree, IsCa, NameConstraints, }; pub use crl::{ CertificateRevocationList, CertificateRevocationListParams, CrlDistributionPoint, CrlIssuingDistributionPoint, CrlScope, RevocationReason, RevokedCertParams, }; pub use csr::{CertificateSigningRequest, CertificateSigningRequestParams, PublicKey}; pub use error::{Error, InvalidAsn1String}; pub use key_pair::PublicKeyData; #[cfg(all(feature = "crypto", feature = "aws_lc_rs"))] pub use key_pair::RsaKeySize; pub use key_pair::{KeyPair, RemoteKeyPair, SubjectPublicKeyInfo}; #[cfg(feature = "crypto")] use ring_like::digest; pub use sign_algo::algo::*; pub use sign_algo::SignatureAlgorithm; pub use string_types::*; mod certificate; mod crl; mod csr; mod error; mod key_pair; mod oid; mod ring_like; mod sign_algo; mod string_types; /// Type-alias for the old name of [`Error`]. #[deprecated( note = "Renamed to `Error`. We recommend to refer to it by fully-qualifying the crate: `rcgen::Error`." )] pub type RcgenError = Error; /// An issued certificate, together with the subject keypair. pub struct CertifiedKey { /// An issued certificate. pub cert: Certificate, /// The certificate's subject key pair. pub 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 and key pair as output. */ #[cfg(feature = "crypto")] #[cfg_attr( feature = "pem", doc = r##" ## Example ``` use rcgen::{generate_simple_self_signed, CertifiedKey}; # 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 CertifiedKey { cert, key_pair } = generate_simple_self_signed(subject_alt_names).unwrap(); // The certificate is now valid for localhost and the domain "hello.world.example" println!("{}", cert.pem()); println!("{}", key_pair.serialize_pem()); # } ``` "## )] pub fn generate_simple_self_signed( subject_alt_names: impl Into>, ) -> Result { let key_pair = KeyPair::generate()?; let cert = CertificateParams::new(subject_alt_names)?.self_signed(&key_pair)?; Ok(CertifiedKey { cert, key_pair }) } struct Issuer<'a> { distinguished_name: &'a DistinguishedName, key_identifier_method: &'a KeyIdMethod, key_usages: &'a [KeyUsagePurpose], key_pair: &'a KeyPair, } // https://tools.ietf.org/html/rfc5280#section-4.1.1 // 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(Ia5String), DnsName(Ia5String), URI(Ia5String), IpAddress(IpAddr), OtherName((Vec, OtherNameValue)), } /// An `OtherName` value, defined in [RFC 5280§4.1.2.4]. /// /// While the standard specifies this could be any ASN.1 type rcgen limits /// the value to a UTF-8 encoded string as this will cover the most common /// use cases, for instance smart card user principal names (UPN). /// /// [RFC 5280§4.1.2.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.4 #[derive(Debug, PartialEq, Eq, Hash, Clone)] #[non_exhaustive] pub enum OtherNameValue { /// A string encoded using UTF-8 Utf8String(String), } impl OtherNameValue { fn write_der(&self, writer: DERWriter) { writer.write_tagged(Tag::context(0), |writer| match self { OtherNameValue::Utf8String(s) => writer.write_utf8_string(s), }); } } impl From for OtherNameValue where T: Into, { fn from(t: T) -> Self { OtherNameValue::Utf8String(t.into()) } } #[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(Error::InvalidIpAddressOctetLength(octets.len())) } } impl SanType { #[cfg(feature = "x509-parser")] fn try_from_general(name: &x509_parser::extensions::GeneralName<'_>) -> Result { use x509_parser::der_parser::asn1_rs::{self, FromDer, Tag, TaggedExplicit}; Ok(match name { x509_parser::extensions::GeneralName::RFC822Name(name) => { SanType::Rfc822Name((*name).try_into()?) }, x509_parser::extensions::GeneralName::DNSName(name) => { SanType::DnsName((*name).try_into()?) }, x509_parser::extensions::GeneralName::URI(name) => SanType::URI((*name).try_into()?), x509_parser::extensions::GeneralName::IPAddress(octets) => { SanType::IpAddress(ip_addr_from_octets(octets)?) }, x509_parser::extensions::GeneralName::OtherName(oid, value) => { let oid = oid.iter().ok_or(Error::CouldNotParseCertificate)?; // We first remove the explicit tag ([0] EXPLICIT) let (_, other_name) = TaggedExplicit::::from_der(value) .map_err(|_| Error::CouldNotParseCertificate)?; let other_name = other_name.into_inner(); let other_name_value = match other_name.tag() { Tag::Utf8String => OtherNameValue::Utf8String( std::str::from_utf8(other_name.data) .map_err(|_| Error::CouldNotParseCertificate)? .to_owned(), ), _ => return Err(Error::CouldNotParseCertificate), }; SanType::OtherName((oid.collect(), other_name_value)) }, _ => return Err(Error::InvalidNameType), }) } fn tag(&self) -> u64 { // Defined in the GeneralName list in // https://tools.ietf.org/html/rfc5280#page-38 const TAG_OTHER_NAME: u64 = 0; 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, Self::OtherName(_oid) => TAG_OTHER_NAME, } } } /// A distinguished name entry #[derive(Debug, PartialEq, Eq, Hash, Clone)] #[non_exhaustive] pub enum DnValue { /// A string encoded using UCS-2 BmpString(BmpString), /// An ASCII string. Ia5String(Ia5String), /// An ASCII string containing only A-Z, a-z, 0-9, '()+,-./:=? and `` PrintableString(PrintableString), /// A string of characters from the T.61 character set TeletexString(TeletexString), /// A string encoded using UTF-32 UniversalString(UniversalString), /// A string encoded using UTF-8 Utf8String(String), } impl From for DnValue where T: Into, { fn from(t: T) -> Self { DnValue::Utf8String(t.into()) } } #[derive(Debug, Default, 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::default() } /// 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".try_into().unwrap())); /// 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".try_into().unwrap()))); /// ``` 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(Error::CouldNotParseCertificate); } else { dn } } else { panic!("x509-parser distinguished name set is empty"); }; let attr_type_oid = attr .attr_type() .iter() .ok_or(Error::CouldNotParseCertificate)?; let dn_type = DnType::from_oid(&attr_type_oid.collect::>()); let data = attr.attr_value().data; let try_str = |data| std::str::from_utf8(data).map_err(|_| Error::CouldNotParseCertificate); let dn_value = match attr.attr_value().header.tag() { Tag::BmpString => DnValue::BmpString(BmpString::from_utf16be(data.to_vec())?), Tag::Ia5String => DnValue::Ia5String(try_str(data)?.try_into()?), Tag::PrintableString => DnValue::PrintableString(try_str(data)?.try_into()?), Tag::T61String => DnValue::TeletexString(try_str(data)?.try_into()?), Tag::UniversalString => { DnValue::UniversalString(UniversalString::from_utf32be(data.to_vec())?) }, Tag::Utf8String => DnValue::Utf8String(try_str(data)?.to_owned()), _ => return Err(Error::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))) } } /// 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, Copy)] pub enum KeyUsagePurpose { /// digitalSignature DigitalSignature, /// contentCommitment / nonRepudiation ContentCommitment, /// keyEncipherment KeyEncipherment, /// dataEncipherment DataEncipherment, /// keyAgreement KeyAgreement, /// keyCertSign KeyCertSign, /// cRLSign CrlSign, /// encipherOnly EncipherOnly, /// decipherOnly DecipherOnly, } impl KeyUsagePurpose { /// Encode a key usage as the value of a BIT STRING as defined by RFC 5280. /// [`u16`] is sufficient to encode the largest possible key usage value (two bytes). fn to_u16(&self) -> u16 { const FLAG: u16 = 0b1000_0000_0000_0000; FLAG >> match self { 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, } } /// Parse a collection of key usages from a [`u16`] representing the value /// of a KeyUsage BIT STRING as defined by RFC 5280. #[cfg(feature = "x509-parser")] fn from_u16(value: u16) -> Vec { [ KeyUsagePurpose::DigitalSignature, KeyUsagePurpose::ContentCommitment, KeyUsagePurpose::KeyEncipherment, KeyUsagePurpose::DataEncipherment, KeyUsagePurpose::KeyAgreement, KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::CrlSign, KeyUsagePurpose::EncipherOnly, KeyUsagePurpose::DecipherOnly, ] .iter() .filter_map(|key_usage| { let present = key_usage.to_u16() & value != 0; present.then_some(*key_usage) }) .collect() } } /// Method to generate key identifiers from public keys. /// /// Key identifiers should be derived from the public key data. [RFC 7093] defines /// three methods to do so using a choice of SHA256 (method 1), SHA384 (method 2), or SHA512 /// (method 3). In each case the first 160 bits of the hash are used as the key identifier /// to match the output length that would be produced were SHA1 used (a legacy option defined /// in RFC 5280). /// /// In addition to the RFC 7093 mechanisms, rcgen supports using a pre-specified key identifier. /// This can be helpful when working with an existing `Certificate`. /// /// [RFC 7093]: https://www.rfc-editor.org/rfc/rfc7093 #[derive(Debug, PartialEq, Eq, Hash, Clone)] #[non_exhaustive] pub enum KeyIdMethod { /// RFC 7093 method 1 - a truncated SHA256 digest. #[cfg(feature = "crypto")] Sha256, /// RFC 7093 method 2 - a truncated SHA384 digest. #[cfg(feature = "crypto")] Sha384, /// RFC 7093 method 3 - a truncated SHA512 digest. #[cfg(feature = "crypto")] Sha512, /// Pre-specified identifier. The exact given value is used as the key identifier. PreSpecified(Vec), } impl KeyIdMethod { /// Derive a key identifier for the provided subject public key info using the key ID method. /// /// Typically this is a truncated hash over the raw subject public key info, but may /// be a pre-specified value. /// /// This key identifier is used in the SubjectKeyIdentifier and AuthorityKeyIdentifier /// X.509v3 extensions. #[allow(unused_variables)] pub(crate) fn derive(&self, subject_public_key_info: impl AsRef<[u8]>) -> Vec { let digest_method = match &self { #[cfg(feature = "crypto")] Self::Sha256 => &digest::SHA256, #[cfg(feature = "crypto")] Self::Sha384 => &digest::SHA384, #[cfg(feature = "crypto")] Self::Sha512 => &digest::SHA512, Self::PreSpecified(b) => { return b.to_vec(); }, }; #[cfg(feature = "crypto")] { let digest = digest::digest(digest_method, subject_public_key_info.as_ref()); digest.as_ref()[0..20].to_vec() } } } 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::BmpString(s) => writer .next() .write_tagged_implicit(TAG_BMPSTRING, |writer| { writer.write_bytes(s.as_bytes()) }), DnValue::Ia5String(s) => writer.next().write_ia5_string(s.as_str()), DnValue::PrintableString(s) => { writer.next().write_printable_string(s.as_str()) }, DnValue::TeletexString(s) => writer .next() .write_tagged_implicit(TAG_TELETEXSTRING, |writer| { writer.write_bytes(s.as_bytes()) }), DnValue::UniversalString(s) => writer .next() .write_tagged_implicit(TAG_UNIVERSALSTRING, |writer| { writer.write_bytes(s.as_bytes()) }), DnValue::Utf8String(s) => writer.next().write_utf8_string(s), } }); }); } }); } /// 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, aki: Vec) { // 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(&aki)) }); }); } #[cfg(feature = "zeroize")] impl zeroize::Zeroize for KeyPair { fn zeroize(&mut self) { self.serialized_der.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 std::panic::catch_unwind; use time::{Date, Month, PrimitiveDateTime}; use super::*; fn 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 = 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 = times(); for dt in times { let _gt = dt_to_generalized(dt); } } #[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 = "x509-parser")] mod test_ip_address_from_octets { use super::super::ip_addr_from_octets; use super::super::Error; 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::from_iter(0..10); let actual = ip_addr_from_octets(&incorrect).unwrap_err(); assert_eq!(Error::InvalidIpAddressOctetLength(10), actual); } #[test] fn none() { let actual = ip_addr_from_octets(&[]).unwrap_err(); assert_eq!(Error::InvalidIpAddressOctetLength(0), actual); } #[test] fn too_many() { let incorrect = Vec::from_iter(0..20); let actual = ip_addr_from_octets(&incorrect).unwrap_err(); assert_eq!(Error::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.13.2/src/oid.rs000064400000000000000000000101041046102023000127110ustar 00000000000000/// pkcs-9-at-extensionRequest in [RFC 2985](https://www.rfc-editor.org/rfc/rfc2985#appendix-A) pub(crate) const 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(crate) const COUNTRY_NAME: &[u64] = &[2, 5, 4, 6]; /// id-at-localityName in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub(crate) const LOCALITY_NAME: &[u64] = &[2, 5, 4, 7]; /// id-at-stateOrProvinceName in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub(crate) const 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(crate) const ORG_NAME: &[u64] = &[2, 5, 4, 10]; /// id-at-organizationalUnitName in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub(crate) const ORG_UNIT_NAME: &[u64] = &[2, 5, 4, 11]; /// id-at-commonName in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub(crate) const COMMON_NAME: &[u64] = &[2, 5, 4, 3]; /// id-ecPublicKey in [RFC 5480](https://datatracker.ietf.org/doc/html/rfc5480#appendix-A) pub(crate) const EC_PUBLIC_KEY: &[u64] = &[1, 2, 840, 10045, 2, 1]; /// secp256r1 in [RFC 5480](https://datatracker.ietf.org/doc/html/rfc5480#appendix-A) pub(crate) const 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(crate) const EC_SECP_384_R1: &[u64] = &[1, 3, 132, 0, 34]; /// secp521r1 in [RFC 5480](https://datatracker.ietf.org/doc/html/rfc5480#appendix-A) /// Currently this is only supported with the `aws_lc_rs` feature #[cfg(feature = "aws_lc_rs")] pub(crate) const EC_SECP_521_R1: &[u64] = &[1, 3, 132, 0, 35]; /// rsaEncryption in [RFC 4055](https://www.rfc-editor.org/rfc/rfc4055#section-6) pub(crate) const 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(crate) const 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(crate) const KEY_USAGE: &[u64] = &[2, 5, 29, 15]; /// id-ce-subjectAltName in [RFC 5280](https://tools.ietf.org/html/rfc5280#appendix-A.2) pub(crate) const SUBJECT_ALT_NAME: &[u64] = &[2, 5, 29, 17]; /// id-ce-basicConstraints in [RFC 5280](https://tools.ietf.org/html/rfc5280#appendix-A.2) pub(crate) const BASIC_CONSTRAINTS: &[u64] = &[2, 5, 29, 19]; /// id-ce-subjectKeyIdentifier in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub(crate) const SUBJECT_KEY_IDENTIFIER: &[u64] = &[2, 5, 29, 14]; /// id-ce-authorityKeyIdentifier in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub(crate) const AUTHORITY_KEY_IDENTIFIER: &[u64] = &[2, 5, 29, 35]; /// id-ce-extKeyUsage in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub(crate) const EXT_KEY_USAGE: &[u64] = &[2, 5, 29, 37]; /// id-ce-nameConstraints in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub(crate) const NAME_CONSTRAINTS: &[u64] = &[2, 5, 29, 30]; /// id-ce-cRLDistributionPoints in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub(crate) const 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(crate) const 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(crate) const CRL_NUMBER: &[u64] = &[2, 5, 29, 20]; /// id-ce-cRLReasons in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub(crate) const CRL_REASONS: &[u64] = &[2, 5, 29, 21]; /// id-ce-invalidityDate in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub(crate) const CRL_INVALIDITY_DATE: &[u64] = &[2, 5, 29, 24]; /// id-ce-issuingDistributionPoint in [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280#appendix-A) pub(crate) const CRL_ISSUING_DISTRIBUTION_POINT: &[u64] = &[2, 5, 29, 28]; rcgen-0.13.2/src/ring_like.rs000064400000000000000000000027121046102023000141070ustar 00000000000000#[cfg(all(feature = "crypto", feature = "aws_lc_rs"))] pub(crate) use aws_lc_rs::*; #[cfg(all(feature = "crypto", feature = "ring", not(feature = "aws_lc_rs")))] pub(crate) use ring::*; #[cfg(feature = "crypto")] use crate::error::ExternalError; #[cfg(feature = "crypto")] use crate::Error; #[cfg(feature = "crypto")] pub(crate) fn ecdsa_from_pkcs8( alg: &'static signature::EcdsaSigningAlgorithm, pkcs8: &[u8], _rng: &dyn rand::SecureRandom, ) -> Result { #[cfg(all(feature = "ring", not(feature = "aws_lc_rs")))] { signature::EcdsaKeyPair::from_pkcs8(alg, pkcs8, _rng)._err() } #[cfg(feature = "aws_lc_rs")] { Ok(signature::EcdsaKeyPair::from_pkcs8(alg, pkcs8)._err()?) } } #[cfg(all(feature = "crypto", feature = "aws_lc_rs"))] pub(crate) fn ecdsa_from_private_key_der( alg: &'static signature::EcdsaSigningAlgorithm, key: &[u8], ) -> Result { Ok(signature::EcdsaKeyPair::from_private_key_der(alg, key)._err()?) } #[cfg(feature = "crypto")] pub(crate) fn rsa_key_pair_public_modulus_len(kp: &signature::RsaKeyPair) -> usize { #[cfg(all(feature = "ring", not(feature = "aws_lc_rs")))] { kp.public().modulus_len() } #[cfg(feature = "aws_lc_rs")] { kp.public_modulus_len() } } #[cfg(all(feature = "crypto", not(any(feature = "ring", feature = "aws_lc_rs"))))] compile_error!("At least one of the 'ring' or 'aws_lc_rs' features must be activated when the 'crypto' feature is enabled"); rcgen-0.13.2/src/sign_algo.rs000064400000000000000000000225241046102023000141110ustar 00000000000000use std::fmt; use std::hash::{Hash, Hasher}; use yasna::models::ObjectIdentifier; use yasna::DERWriter; use yasna::Tag; #[cfg(feature = "crypto")] use crate::ring_like::signature::{self, EcdsaSigningAlgorithm, EdDSAParameters, RsaEncoding}; use crate::Error; #[cfg(feature = "crypto")] pub(crate) enum SignAlgo { EcDsa(&'static EcdsaSigningAlgorithm), EdDsa(&'static EdDSAParameters), Rsa(&'static dyn RsaEncoding), } #[derive(PartialEq, Eq, Hash)] 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]], #[cfg(feature = "crypto")] 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 { #[cfg(feature = "aws_lc_rs")] if self == &PKCS_ECDSA_P521_SHA512 { return write!(f, "PKCS_ECDSA_P521_SHA512"); } 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, #[cfg(feature = "aws_lc_rs")] &PKCS_ECDSA_P521_SHA512, &PKCS_ED25519, ]; ALGORITHMS.iter() } /// Retrieve the SignatureAlgorithm for the provided OID pub fn from_oid(oid: &[u64]) -> Result<&'static SignatureAlgorithm, Error> { for algo in Self::iter() { if algo.oid_components == oid { return Ok(algo); } } Err(Error::UnsupportedSignatureAlgorithm) } } /// The list of supported signature algorithms pub(crate) mod algo { use crate::oid::*; 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: &[&RSA_ENCRYPTION], #[cfg(feature = "crypto")] sign_alg: SignAlgo::Rsa(&signature::RSA_PKCS1_SHA256), // 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: &[&RSA_ENCRYPTION], #[cfg(feature = "crypto")] sign_alg: SignAlgo::Rsa(&signature::RSA_PKCS1_SHA384), // 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: &[&RSA_ENCRYPTION], #[cfg(feature = "crypto")] sign_alg: SignAlgo::Rsa(&signature::RSA_PKCS1_SHA512), // 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 RSA_ENCRYPTION here, but it's recommended // to use ID-RSASSA-PSS if possible. oids_sign_alg: &[&RSASSA_PSS], #[cfg(feature = "crypto")] sign_alg: SignAlgo::Rsa(&signature::RSA_PSS_SHA256), oid_components: 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: &[&EC_PUBLIC_KEY, &EC_SECP_256_R1], #[cfg(feature = "crypto")] 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: &[&EC_PUBLIC_KEY, &EC_SECP_384_R1], #[cfg(feature = "crypto")] 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, }; /// ECDSA signing using the P-521 curves and SHA-512 hashing as per [RFC 5758](https://tools.ietf.org/html/rfc5758#section-3.2) /// Currently this is only supported with the `aws_lc_rs` feature #[cfg(feature = "aws_lc_rs")] pub static PKCS_ECDSA_P521_SHA512: SignatureAlgorithm = SignatureAlgorithm { oids_sign_alg: &[&EC_PUBLIC_KEY, &EC_SECP_521_R1], #[cfg(feature = "crypto")] sign_alg: SignAlgo::EcDsa(&signature::ECDSA_P521_SHA512_ASN1_SIGNING), // ecdsa-with-SHA512 in RFC 5758 oid_components: &[1, 2, 840, 10045, 4, 3, 4], params: SignatureAlgorithmParams::None, }; /// 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]], #[cfg(feature = "crypto")] 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.13.2/src/string_types.rs000064400000000000000000000437061046102023000147060ustar 00000000000000use std::{fmt, str::FromStr}; use crate::{Error, InvalidAsn1String}; /// ASN.1 `PrintableString` type. /// /// Supports a subset of the ASCII printable characters (described below). /// /// For the full ASCII character set, use /// [`Ia5String`][`crate::Ia5String`]. /// /// # Examples /// /// You can create a `PrintableString` from [a literal string][`&str`] with [`PrintableString::try_from`]: /// /// ``` /// use rcgen::PrintableString; /// let hello = PrintableString::try_from("hello").unwrap(); /// ``` /// /// # Supported characters /// /// PrintableString is a subset of the [ASCII printable characters]. /// For instance, `'@'` is a printable character as per ASCII but can't be part of [ASN.1's `PrintableString`]. /// /// The following ASCII characters/ranges are supported: /// /// - `A..Z` /// - `a..z` /// - `0..9` /// - "` `" (i.e. space) /// - `\` /// - `(` /// - `)` /// - `+` /// - `,` /// - `-` /// - `.` /// - `/` /// - `:` /// - `=` /// - `?` /// /// [ASCII printable characters]: https://en.wikipedia.org/wiki/ASCII#Printable_characters /// [ASN.1's `PrintableString`]: https://en.wikipedia.org/wiki/PrintableString #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct PrintableString(String); impl PrintableString { /// Extracts a string slice containing the entire `PrintableString`. pub fn as_str(&self) -> &str { &self.0 } } impl TryFrom<&str> for PrintableString { type Error = Error; /// Converts a `&str` to a [`PrintableString`]. /// /// Any character not in the [`PrintableString`] charset will be rejected. /// See [`PrintableString`] documentation for more information. /// /// The result is allocated on the heap. fn try_from(input: &str) -> Result { input.to_string().try_into() } } impl TryFrom for PrintableString { type Error = Error; /// Converts a [`String`][`std::string::String`] into a [`PrintableString`] /// /// Any character not in the [`PrintableString`] charset will be rejected. /// See [`PrintableString`] documentation for more information. /// /// This conversion does not allocate or copy memory. fn try_from(value: String) -> Result { for &c in value.as_bytes() { match c { b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b' ' | b'\'' | b'(' | b')' | b'+' | b',' | b'-' | b'.' | b'/' | b':' | b'=' | b'?' => (), _ => { return Err(Error::InvalidAsn1String( InvalidAsn1String::PrintableString(value), )) }, } } Ok(Self(value)) } } impl FromStr for PrintableString { type Err = Error; fn from_str(s: &str) -> Result { s.try_into() } } impl AsRef for PrintableString { fn as_ref(&self) -> &str { &self.0 } } impl fmt::Display for PrintableString { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self.as_str(), f) } } impl PartialEq for PrintableString { fn eq(&self, other: &str) -> bool { self.as_str() == other } } impl PartialEq for PrintableString { fn eq(&self, other: &String) -> bool { self.as_str() == other.as_str() } } impl PartialEq<&str> for PrintableString { fn eq(&self, other: &&str) -> bool { self.as_str() == *other } } impl PartialEq<&String> for PrintableString { fn eq(&self, other: &&String) -> bool { self.as_str() == other.as_str() } } /// ASN.1 `IA5String` type. /// /// # Examples /// /// You can create a `Ia5String` from [a literal string][`&str`] with [`Ia5String::try_from`]: /// /// ``` /// use rcgen::Ia5String; /// let hello = Ia5String::try_from("hello").unwrap(); /// ``` /// /// # Supported characters /// /// Supports the [International Alphabet No. 5 (IA5)] character encoding, i.e. /// the 128 characters of the ASCII alphabet. (Note: IA5 is now /// technically known as the International Reference Alphabet or IRA as /// specified in the ITU-T's T.50 recommendation). /// /// For UTF-8, use [`String`][`std::string::String`]. /// /// [International Alphabet No. 5 (IA5)]: https://en.wikipedia.org/wiki/T.50_(standard) #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct Ia5String(String); impl Ia5String { /// Extracts a string slice containing the entire `Ia5String`. pub fn as_str(&self) -> &str { &self.0 } } impl TryFrom<&str> for Ia5String { type Error = Error; /// Converts a `&str` to a [`Ia5String`]. /// /// Any character not in the [`Ia5String`] charset will be rejected. /// See [`Ia5String`] documentation for more information. /// /// The result is allocated on the heap. fn try_from(input: &str) -> Result { input.to_string().try_into() } } impl TryFrom for Ia5String { type Error = Error; /// Converts a [`String`][`std::string::String`] into a [`Ia5String`] /// /// Any character not in the [`Ia5String`] charset will be rejected. /// See [`Ia5String`] documentation for more information. fn try_from(input: String) -> Result { if !input.is_ascii() { return Err(Error::InvalidAsn1String(InvalidAsn1String::Ia5String( input, ))); } Ok(Self(input)) } } impl FromStr for Ia5String { type Err = Error; fn from_str(s: &str) -> Result { s.try_into() } } impl AsRef for Ia5String { fn as_ref(&self) -> &str { &self.0 } } impl fmt::Display for Ia5String { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self.as_str(), f) } } impl PartialEq for Ia5String { fn eq(&self, other: &str) -> bool { self.as_str() == other } } impl PartialEq for Ia5String { fn eq(&self, other: &String) -> bool { self.as_str() == other.as_str() } } impl PartialEq<&str> for Ia5String { fn eq(&self, other: &&str) -> bool { self.as_str() == *other } } impl PartialEq<&String> for Ia5String { fn eq(&self, other: &&String) -> bool { self.as_str() == other.as_str() } } /// ASN.1 `TeletexString` type. /// /// # Examples /// /// You can create a `TeletexString` from [a literal string][`&str`] with [`TeletexString::try_from`]: /// /// ``` /// use rcgen::TeletexString; /// let hello = TeletexString::try_from("hello").unwrap(); /// ``` /// /// # Supported characters /// /// The standard defines a complex character set allowed in this type. However, quoting the ASN.1 /// [mailing list], "a sizable volume of software in the world treats TeletexString (T61String) as a /// simple 8-bit string with mostly Windows Latin 1 (superset of iso-8859-1) encoding". /// /// `TeletexString` is included for backward compatibility, [RFC 5280] say it /// SHOULD NOT be used for certificates for new subjects. /// /// [mailing list]: https://www.mail-archive.com/asn1@asn1.org/msg00460.html /// [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280#page-25 #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct TeletexString(String); impl TeletexString { /// Extracts a string slice containing the entire `TeletexString`. pub fn as_str(&self) -> &str { &self.0 } /// Returns a byte slice of this `TeletexString`’s contents. pub fn as_bytes(&self) -> &[u8] { self.0.as_bytes() } } impl TryFrom<&str> for TeletexString { type Error = Error; /// Converts a `&str` to a [`TeletexString`]. /// /// Any character not in the [`TeletexString`] charset will be rejected. /// See [`TeletexString`] documentation for more information. /// /// The result is allocated on the heap. fn try_from(input: &str) -> Result { input.to_string().try_into() } } impl TryFrom for TeletexString { type Error = Error; /// Converts a [`String`][`std::string::String`] into a [`TeletexString`] /// /// Any character not in the [`TeletexString`] charset will be rejected. /// See [`TeletexString`] documentation for more information. /// /// This conversion does not allocate or copy memory. fn try_from(input: String) -> Result { // Check all bytes are visible if !input.as_bytes().iter().all(|b| (0x20..=0x7f).contains(b)) { return Err(Error::InvalidAsn1String(InvalidAsn1String::TeletexString( input, ))); } Ok(Self(input)) } } impl FromStr for TeletexString { type Err = Error; fn from_str(s: &str) -> Result { s.try_into() } } impl AsRef for TeletexString { fn as_ref(&self) -> &str { &self.0 } } impl fmt::Display for TeletexString { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self.as_str(), f) } } impl PartialEq for TeletexString { fn eq(&self, other: &str) -> bool { self.as_str() == other } } impl PartialEq for TeletexString { fn eq(&self, other: &String) -> bool { self.as_str() == other.as_str() } } impl PartialEq<&str> for TeletexString { fn eq(&self, other: &&str) -> bool { self.as_str() == *other } } impl PartialEq<&String> for TeletexString { fn eq(&self, other: &&String) -> bool { self.as_str() == other.as_str() } } /// ASN.1 `BMPString` type. /// /// # Examples /// /// You can create a `BmpString` from [a literal string][`&str`] with [`BmpString::try_from`]: /// /// ``` /// use rcgen::BmpString; /// let hello = BmpString::try_from("hello").unwrap(); /// ``` /// /// # Supported characters /// /// Encodes Basic Multilingual Plane (BMP) subset of Unicode (ISO 10646), /// a.k.a. UCS-2. /// /// Bytes are encoded as UTF-16 big-endian. /// /// `BMPString` is included for backward compatibility, [RFC 5280] say it /// SHOULD NOT be used for certificates for new subjects. /// /// [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280#page-25 #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct BmpString(Vec); impl BmpString { /// Returns a byte slice of this `BmpString`'s contents. /// /// The inverse of this method is [`from_utf16be`]. /// /// [`from_utf16be`]: BmpString::from_utf16be /// /// # Examples /// /// ``` /// use rcgen::BmpString; /// let s = BmpString::try_from("hello").unwrap(); /// /// assert_eq!(&[0, 104, 0, 101, 0, 108, 0, 108, 0, 111], s.as_bytes()); /// ``` pub fn as_bytes(&self) -> &[u8] { &self.0 } /// Decode a UTF-16BE–encoded vector `vec` into a `BmpString`, returning [Err](`std::result::Result::Err`) if `vec` contains any invalid data. pub fn from_utf16be(vec: Vec) -> Result { if vec.len() % 2 != 0 { return Err(Error::InvalidAsn1String(InvalidAsn1String::BmpString( "Invalid UTF-16 encoding".to_string(), ))); } // FIXME: Update this when `array_chunks` is stabilized. for maybe_char in char::decode_utf16( vec.chunks_exact(2) .map(|chunk| u16::from_be_bytes([chunk[0], chunk[1]])), ) { // We check we only use the BMP subset of Unicode (the first 65 536 code points) match maybe_char { // Character is in the Basic Multilingual Plane Ok(c) if (c as u64) < u64::from(u16::MAX) => (), // Characters outside Basic Multilingual Plane or unpaired surrogates _ => { return Err(Error::InvalidAsn1String(InvalidAsn1String::BmpString( "Invalid UTF-16 encoding".to_string(), ))); }, } } Ok(Self(vec.to_vec())) } } impl TryFrom<&str> for BmpString { type Error = Error; /// Converts a `&str` to a [`BmpString`]. /// /// Any character not in the [`BmpString`] charset will be rejected. /// See [`BmpString`] documentation for more information. /// /// The result is allocated on the heap. fn try_from(value: &str) -> Result { let capacity = value.len().checked_mul(2).ok_or_else(|| { Error::InvalidAsn1String(InvalidAsn1String::BmpString(value.to_string())) })?; let mut bytes = Vec::with_capacity(capacity); for code_point in value.encode_utf16() { bytes.extend(code_point.to_be_bytes()); } BmpString::from_utf16be(bytes) } } impl TryFrom for BmpString { type Error = Error; /// Converts a [`String`][`std::string::String`] into a [`BmpString`] /// /// Any character not in the [`BmpString`] charset will be rejected. /// See [`BmpString`] documentation for more information. /// /// Parsing a `BmpString` allocates memory since the UTF-8 to UTF-16 conversion requires a memory allocation. fn try_from(value: String) -> Result { value.as_str().try_into() } } impl FromStr for BmpString { type Err = Error; fn from_str(s: &str) -> Result { s.try_into() } } /// ASN.1 `UniversalString` type. /// /// # Examples /// /// You can create a `UniversalString` from [a literal string][`&str`] with [`UniversalString::try_from`]: /// /// ``` /// use rcgen::UniversalString; /// let hello = UniversalString::try_from("hello").unwrap(); /// ``` /// /// # Supported characters /// /// The characters which can appear in the `UniversalString` type are any of the characters allowed by /// ISO/IEC 10646 (Unicode). /// /// Bytes are encoded like UTF-32 big-endian. /// /// `UniversalString` is included for backward compatibility, [RFC 5280] say it /// SHOULD NOT be used for certificates for new subjects. /// /// [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280#page-25 #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub struct UniversalString(Vec); impl UniversalString { /// Returns a byte slice of this `UniversalString`'s contents. /// /// The inverse of this method is [`from_utf32be`]. /// /// [`from_utf32be`]: UniversalString::from_utf32be /// /// # Examples /// /// ``` /// use rcgen::UniversalString; /// let s = UniversalString::try_from("hello").unwrap(); /// /// assert_eq!(&[0, 0, 0, 104, 0, 0, 0, 101, 0, 0, 0, 108, 0, 0, 0, 108, 0, 0, 0, 111], s.as_bytes()); /// ``` pub fn as_bytes(&self) -> &[u8] { &self.0 } /// Decode a UTF-32BE–encoded vector `vec` into a `UniversalString`, returning [Err](`std::result::Result::Err`) if `vec` contains any invalid data. pub fn from_utf32be(vec: Vec) -> Result { if vec.len() % 4 != 0 { return Err(Error::InvalidAsn1String( InvalidAsn1String::UniversalString("Invalid UTF-32 encoding".to_string()), )); } // FIXME: Update this when `array_chunks` is stabilized. for maybe_char in vec .chunks_exact(4) .map(|chunk| u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]])) { if core::char::from_u32(maybe_char).is_none() { return Err(Error::InvalidAsn1String( InvalidAsn1String::UniversalString("Invalid UTF-32 encoding".to_string()), )); } } Ok(Self(vec)) } } impl TryFrom<&str> for UniversalString { type Error = Error; /// Converts a `&str` to a [`UniversalString`]. /// /// Any character not in the [`UniversalString`] charset will be rejected. /// See [`UniversalString`] documentation for more information. /// /// The result is allocated on the heap. fn try_from(value: &str) -> Result { let capacity = value.len().checked_mul(4).ok_or_else(|| { Error::InvalidAsn1String(InvalidAsn1String::UniversalString(value.to_string())) })?; let mut bytes = Vec::with_capacity(capacity); // A `char` is any ‘Unicode code point’ other than a surrogate code point. // The code units for UTF-32 correspond exactly to Unicode code points. // (https://www.unicode.org/reports/tr19/tr19-9.html#Introduction) // So any `char` is a valid UTF-32, we just cast it to perform the convertion. for char in value.chars().map(|char| char as u32) { bytes.extend(char.to_be_bytes()) } UniversalString::from_utf32be(bytes) } } impl TryFrom for UniversalString { type Error = Error; /// Converts a [`String`][`std::string::String`] into a [`UniversalString`] /// /// Any character not in the [`UniversalString`] charset will be rejected. /// See [`UniversalString`] documentation for more information. /// /// Parsing a `UniversalString` allocates memory since the UTF-8 to UTF-32 conversion requires a memory allocation. fn try_from(value: String) -> Result { value.as_str().try_into() } } #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { use crate::{BmpString, Ia5String, PrintableString, TeletexString, UniversalString}; #[test] fn printable_string() { const EXAMPLE_UTF8: &str = "CertificateTemplate"; let printable_string = PrintableString::try_from(EXAMPLE_UTF8).unwrap(); assert_eq!(printable_string, EXAMPLE_UTF8); assert!(PrintableString::try_from("@").is_err()); assert!(PrintableString::try_from("*").is_err()); } #[test] fn ia5_string() { const EXAMPLE_UTF8: &str = "CertificateTemplate"; let ia5_string = Ia5String::try_from(EXAMPLE_UTF8).unwrap(); assert_eq!(ia5_string, EXAMPLE_UTF8); assert!(Ia5String::try_from(String::from('\u{7F}')).is_ok()); assert!(Ia5String::try_from(String::from('\u{8F}')).is_err()); } #[test] fn teletext_string() { const EXAMPLE_UTF8: &str = "CertificateTemplate"; let teletext_string = TeletexString::try_from(EXAMPLE_UTF8).unwrap(); assert_eq!(teletext_string, EXAMPLE_UTF8); assert!(Ia5String::try_from(String::from('\u{7F}')).is_ok()); assert!(Ia5String::try_from(String::from('\u{8F}')).is_err()); } #[test] fn bmp_string() { const EXPECTED_BYTES: &[u8] = &[ 0x00, 0x43, 0x00, 0x65, 0x00, 0x72, 0x00, 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, 0x00, 0x63, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, 0x54, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, ]; const EXAMPLE_UTF8: &str = "CertificateTemplate"; let bmp_string = BmpString::try_from(EXAMPLE_UTF8).unwrap(); assert_eq!(bmp_string.as_bytes(), EXPECTED_BYTES); assert!(BmpString::try_from(String::from('\u{FFFE}')).is_ok()); assert!(BmpString::try_from(String::from('\u{FFFF}')).is_err()); } #[test] fn universal_string() { const EXPECTED_BYTES: &[u8] = &[ 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x6d, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x65, ]; const EXAMPLE_UTF8: &str = "CertificateTemplate"; let universal_string = UniversalString::try_from(EXAMPLE_UTF8).unwrap(); assert_eq!(universal_string.as_bytes(), EXPECTED_BYTES); } } rcgen-0.13.2/tests/botan.rs000064400000000000000000000175701046102023000136320ustar 00000000000000#![cfg(all(feature = "crypto", feature = "x509-parser"))] use time::{Duration, OffsetDateTime}; use rcgen::{BasicConstraints, Certificate, CertificateParams, DnType, IsCa}; use rcgen::{CertificateRevocationListParams, RevocationReason, RevokedCertParams}; use rcgen::{DnValue, KeyPair}; use rcgen::{KeyUsagePurpose, SerialNumber}; mod util; fn default_params() -> (CertificateParams, KeyPair) { let (mut params, key_pair) = util::default_params(); // Botan has a sanity check that enforces a maximum expiration date params.not_after = rcgen::date_time_ymd(3016, 1, 1); (params, key_pair) } fn check_cert(cert_der: &[u8], cert: &Certificate) { println!("{}", cert.pem()); check_cert_ca(cert_der, cert, cert_der); } fn check_cert_ca(cert_der: &[u8], _cert: &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, key_pair) = default_params(); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate. check_cert(cert.der(), &cert); } #[test] fn test_botan_256() { let (params, _) = default_params(); let key_pair = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate. check_cert(cert.der(), &cert); } #[test] fn test_botan_384() { let (params, _) = default_params(); let key_pair = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P384_SHA384).unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate. check_cert(cert.der(), &cert); } #[test] #[cfg(feature = "aws_lc_rs")] fn test_botan_521() { let (params, _) = default_params(); let key_pair = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P521_SHA512).unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate. check_cert(cert.der(), &cert); } #[test] fn test_botan_25519() { let (params, _) = default_params(); let key_pair = KeyPair::generate_for(&rcgen::PKCS_ED25519).unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate. check_cert(cert.der(), &cert); } #[test] fn test_botan_25519_v1_given() { let (params, _) = default_params(); let key_pair = KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V1).unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate. check_cert(cert.der(), &cert); } #[test] fn test_botan_25519_v2_given() { let (params, _) = default_params(); let key_pair = KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V2).unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate. check_cert(cert.der(), &cert); } #[test] fn test_botan_rsa_given() { let (params, _) = default_params(); let key_pair = KeyPair::from_pem(util::RSA_TEST_KEY_PAIR_PEM).unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate. check_cert(cert.der(), &cert); } #[test] fn test_botan_separate_ca() { let (mut params, ca_key) = default_params(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); let ca_cert = params.self_signed(&ca_key).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap(); 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, 1, 1); let key_pair = KeyPair::generate().unwrap(); let cert = params.signed_by(&key_pair, &ca_cert, &ca_key).unwrap(); check_cert_ca(cert.der(), &cert, ca_cert.der()); } #[cfg(feature = "x509-parser")] #[test] fn test_botan_imported_ca() { let (mut params, ca_key) = default_params(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); let ca_cert = params.self_signed(&ca_key).unwrap(); let ca_cert_der = ca_cert.der(); let imported_ca_cert_params = CertificateParams::from_ca_cert_der(ca_cert_der).unwrap(); let imported_ca_cert = imported_ca_cert_params.self_signed(&ca_key).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap(); 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, 1, 1); let key_pair = KeyPair::generate().unwrap(); let cert = params .signed_by(&key_pair, &imported_ca_cert, &ca_key) .unwrap(); check_cert_ca(cert.der(), &cert, ca_cert_der); } #[cfg(feature = "x509-parser")] #[test] fn test_botan_imported_ca_with_printable_string() { let (mut params, imported_ca_key) = default_params(); params.distinguished_name.push( DnType::CountryName, DnValue::PrintableString("US".try_into().unwrap()), ); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); let ca_cert = params.self_signed(&imported_ca_key).unwrap(); let ca_cert_der = ca_cert.der(); let imported_ca_cert_params = CertificateParams::from_ca_cert_der(ca_cert_der).unwrap(); let imported_ca_cert = imported_ca_cert_params .self_signed(&imported_ca_key) .unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap(); 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, 1, 1); let key_pair = KeyPair::generate().unwrap(); let cert = params .signed_by(&key_pair, &imported_ca_cert, &imported_ca_key) .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, ]; let issuer_key = KeyPair::generate_for(alg).unwrap(); let issuer = issuer.self_signed(&issuer_key).unwrap(); // Create an end entity cert issued by the issuer. let (mut ee, _) = util::default_params(); 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, 1, 1); let ee_key = KeyPair::generate_for(alg).unwrap(); let ee = ee.signed_by(&ee_key, &issuer, &issuer_key).unwrap(); let botan_ee = botan::Certificate::load(ee.der()).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.params().serial_number.clone().unwrap(), revocation_time: now, reason_code: Some(RevocationReason::KeyCompromise), invalidity_date: None, }], key_identifier_method: rcgen::KeyIdMethod::Sha256, }; let crl = crl.signed_by(&issuer, &issuer_key).unwrap(); // We should be able to load the CRL in both serializations. botan::CRL::load(crl.pem().unwrap().as_ref()).unwrap(); let crl = botan::CRL::load(crl.der()).unwrap(); // We should find the EE cert revoked. assert!(crl.is_revoked(&botan_ee).unwrap()); } rcgen-0.13.2/tests/generic.rs000064400000000000000000000420471046102023000141400ustar 00000000000000#![cfg(feature = "crypto")] mod util; #[cfg(feature = "pem")] mod test_key_params_mismatch { 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, #[cfg(feature = "aws_lc_rs")] &rcgen::PKCS_ECDSA_P521_SHA512, &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)); } } } } #[cfg(feature = "x509-parser")] mod test_convert_x509_subject_alternative_name { use rcgen::{BasicConstraints, CertificateParams, IsCa, SanType}; 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, ca_key) = 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 = params.self_signed(&ca_key).unwrap(); // Serialize our cert that has our chosen san, so we can testing parsing/deserializing it. let ca_der = cert.der(); let actual = CertificateParams::from_ca_cert_der(ca_der).unwrap(); assert!(actual.subject_alt_names.contains(&ip_san)); } } #[cfg(feature = "x509-parser")] mod test_x509_custom_ext { use crate::util; use rcgen::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, test_key) = 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 = params.self_signed(&test_key).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!(favorite_drink_ext.critical); assert_eq!(favorite_drink_ext.value, test_ext); // Generate a CSR with the custom extension, parse it with x509-parser. let test_cert_csr = test_cert.params().serialize_request(&test_key).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!(custom_ext.critical); assert_eq!(custom_ext.value, test_ext); } } #[cfg(feature = "x509-parser")] mod test_csr_custom_attributes { use rcgen::{Attribute, CertificateParams, KeyPair}; use x509_parser::{ der_parser::Oid, prelude::{FromDer, X509CertificationRequest}, }; /// Test serializing a CSR with custom attributes. /// This test case uses `challengePassword` from [RFC 2985], a simple /// ATTRIBUTE that contains a single UTF8String. /// /// [RFC 2985]: #[test] fn test_csr_custom_attributes() { // OID for challengePassword const CHALLENGE_PWD_OID: &[u64] = &[1, 2, 840, 113549, 1, 9, 7]; // Attribute values for challengePassword let challenge_pwd_values = yasna::try_construct_der::<_, ()>(|writer| { // Reminder: CSR attribute values are contained in a SET writer.write_set(|writer| { // Challenge passwords only have one value, a UTF8String writer .next() .write_utf8_string("nobody uses challenge passwords anymore"); Ok(()) }) }) .unwrap(); // Challenge password attribute let challenge_password_attribute = Attribute { oid: CHALLENGE_PWD_OID, values: challenge_pwd_values.clone(), }; // Serialize a DER-encoded CSR let params = CertificateParams::default(); let key_pair = KeyPair::generate().unwrap(); let csr = params .serialize_request_with_attributes(&key_pair, vec![challenge_password_attribute]) .unwrap(); // Parse the CSR let (_, x509_csr) = X509CertificationRequest::from_der(csr.der()).unwrap(); let parsed_attribute_value = x509_csr .certification_request_info .attributes_map() .unwrap() .get(&Oid::from(CHALLENGE_PWD_OID).unwrap()) .unwrap() .value; assert_eq!(parsed_attribute_value, challenge_pwd_values); } } #[cfg(feature = "x509-parser")] mod test_x509_parser_crl { use crate::util; use x509_parser::extensions::{DistributionPointName, ParsedExtension}; use x509_parser::num_bigint::BigUint; use x509_parser::prelude::{FromDer, GeneralName, IssuingDistributionPoint, 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.params().revoked_certs.first().unwrap(); let revoked_cert_serial = BigUint::from_bytes_be(revoked_cert.serial_number.as_ref()); let (_, x509_issuer) = X509Certificate::from_der(issuer.der()).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.params().this_update.unix_timestamp() ); assert_eq!( x509_crl .next_update() .unwrap() .to_datetime() .unix_timestamp(), crl.params().next_update.unix_timestamp() ); let crl_number = BigUint::from_bytes_be(crl.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); // The parsed issuing distribution point extension should match expected. let ParsedExtension::IssuingDistributionPoint(idp) = issuing_dp_ext.parsed_extension() else { panic!("missing parsed CRL IDP ext"); }; assert_eq!( idp, &IssuingDistributionPoint { only_contains_user_certs: true, only_contains_ca_certs: false, only_contains_attribute_certs: false, indirect_crl: false, only_some_reasons: None, distribution_point: Some(DistributionPointName::FullName(vec![GeneralName::URI( "http://example.com/crl", )])), } ); // 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" ] ); } } #[cfg(feature = "x509-parser")] mod test_parse_ia5string_subject { use crate::util; use rcgen::DnType::CustomDnType; use rcgen::{CertificateParams, DistinguishedName, DnValue}; #[test] fn parse_ia5string_subject() { // Create and serialize a certificate with a subject containing an IA5String email address. let email_address_dn_type = CustomDnType(vec![1, 2, 840, 113549, 1, 9, 1]); // id-emailAddress let email_address_dn_value = DnValue::Ia5String("foo@bar.com".try_into().unwrap()); let (mut params, key_pair) = util::default_params(); params.distinguished_name = DistinguishedName::new(); params.distinguished_name.push( email_address_dn_type.clone(), email_address_dn_value.clone(), ); let cert = params.self_signed(&key_pair).unwrap(); let cert_der = cert.der(); // We should be able to parse the certificate with x509-parser. assert!(x509_parser::parse_x509_certificate(cert_der).is_ok()); // We should be able to reconstitute params from the DER using x509-parser. let params_from_cert = CertificateParams::from_ca_cert_der(cert_der).unwrap(); // We should find the expected distinguished name in the reconstituted params. let expected_names = &[(&email_address_dn_type, &email_address_dn_value)]; let names = params_from_cert .distinguished_name .iter() .collect::>(); assert_eq!(names, expected_names); } } #[cfg(feature = "x509-parser")] mod test_parse_other_name_alt_name { use rcgen::{CertificateParams, KeyPair, SanType}; #[test] fn parse_other_name_alt_name() { // Create and serialize a certificate with an alternative name containing an "OtherName". let mut params = CertificateParams::default(); let other_name = SanType::OtherName((vec![1, 2, 3, 4], "Foo".into())); params.subject_alt_names.push(other_name.clone()); let key_pair = KeyPair::generate().unwrap(); let cert = params.self_signed(&key_pair).unwrap(); let cert_der = cert.der(); // We should be able to parse the certificate with x509-parser. assert!(x509_parser::parse_x509_certificate(cert_der).is_ok()); // We should be able to reconstitute params from the DER using x509-parser. let params_from_cert = CertificateParams::from_ca_cert_der(cert_der).unwrap(); // We should find the expected distinguished name in the reconstituted params. let expected_alt_names = &[&other_name]; let subject_alt_names = params_from_cert .subject_alt_names .iter() .collect::>(); assert_eq!(subject_alt_names, expected_alt_names); } } #[cfg(feature = "x509-parser")] mod test_csr_extension_request { use rcgen::{CertificateParams, ExtendedKeyUsagePurpose, KeyPair, KeyUsagePurpose}; use x509_parser::prelude::{FromDer, ParsedExtension, X509CertificationRequest}; #[test] fn dont_write_sans_extension_if_no_sans_are_present() { let mut params = CertificateParams::default(); params.key_usages.push(KeyUsagePurpose::DigitalSignature); let key_pair = KeyPair::generate().unwrap(); let csr = params.serialize_request(&key_pair).unwrap(); let (_, parsed_csr) = X509CertificationRequest::from_der(csr.der()).unwrap(); assert!(!parsed_csr .requested_extensions() .unwrap() .any(|ext| matches!(ext, ParsedExtension::SubjectAlternativeName(_)))); } #[test] fn write_extension_request_if_ekus_are_present() { let mut params = CertificateParams::default(); params .extended_key_usages .push(ExtendedKeyUsagePurpose::ClientAuth); let key_pair = KeyPair::generate().unwrap(); let csr = params.serialize_request(&key_pair).unwrap(); let (_, parsed_csr) = X509CertificationRequest::from_der(csr.der()).unwrap(); let requested_extensions = parsed_csr .requested_extensions() .unwrap() .collect::>(); assert!(matches!( requested_extensions.first().unwrap(), ParsedExtension::ExtendedKeyUsage(_) )); } } #[cfg(feature = "x509-parser")] mod test_csr { use rcgen::{ CertificateParams, CertificateSigningRequestParams, ExtendedKeyUsagePurpose, KeyPair, KeyUsagePurpose, }; #[test] fn test_csr_roundtrip() { // We should be able to serialize a CSR, and then parse the CSR. let params = CertificateParams::default(); generate_and_test_parsed_csr(¶ms); } #[test] fn test_csr_with_key_usages_roundtrip() { let mut params = CertificateParams::default(); params.key_usages = vec![ KeyUsagePurpose::DigitalSignature, KeyUsagePurpose::ContentCommitment, KeyUsagePurpose::KeyEncipherment, KeyUsagePurpose::DataEncipherment, KeyUsagePurpose::KeyAgreement, KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::CrlSign, // It doesn't make sense to have both encipher and decipher only // So we'll take this opportunity to test omitting a key usage // KeyUsagePurpose::EncipherOnly, KeyUsagePurpose::DecipherOnly, ]; generate_and_test_parsed_csr(¶ms); } #[test] fn test_csr_with_extended_key_usages_roundtrip() { let mut params = CertificateParams::default(); params.extended_key_usages = vec![ ExtendedKeyUsagePurpose::ServerAuth, ExtendedKeyUsagePurpose::ClientAuth, ]; generate_and_test_parsed_csr(¶ms); } #[test] fn test_csr_with_key_usgaes_and_extended_key_usages_roundtrip() { let mut params = CertificateParams::default(); params.key_usages = vec![KeyUsagePurpose::DigitalSignature]; params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ClientAuth]; generate_and_test_parsed_csr(¶ms); } fn generate_and_test_parsed_csr(params: &CertificateParams) { // Generate a key pair for the CSR let key_pair = KeyPair::generate().unwrap(); // Serialize the CSR into DER from the given parameters let csr = params.serialize_request(&key_pair).unwrap(); // Parse the CSR we just serialized let csrp = CertificateSigningRequestParams::from_der(csr.der()).unwrap(); // Ensure algorithms match. assert_eq!(key_pair.algorithm(), csrp.public_key.algorithm()); // Assert that our parsed parameters match our initial parameters assert_eq!(*params, csrp.params); } } rcgen-0.13.2/tests/openssl.rs000064400000000000000000000374161046102023000142130ustar 00000000000000#![cfg(feature = "pem")] use std::cell::RefCell; use std::io::{Error, ErrorKind, Read, Result as ioResult, Write}; use std::rc::Rc; 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, KeyPair, NameConstraints, }; mod util; fn verify_cert_basic(cert: &Certificate) { let cert_pem = cert.pem(); 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.is_empty() { 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, key_pair: &KeyPair) { verify_cert_basic(cert); let key = key_pair.serialize_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, key_pair: &KeyPair) { let csr = cert .params() .serialize_request(key_pair) .and_then(|csr| csr.pem()) .unwrap(); println!("{csr}"); let key = key_pair.serialize_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, key_pair) = util::default_params(); let cert = params.self_signed(&key_pair).unwrap(); verify_cert(&cert, &key_pair); } #[test] fn test_request() { let (params, key_pair) = util::default_params(); let cert = params.self_signed(&key_pair).unwrap(); verify_csr(&cert, &key_pair); } #[test] fn test_openssl_256() { let (params, _) = util::default_params(); let key_pair = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate. verify_cert(&cert, &key_pair); verify_csr(&cert, &key_pair); } #[test] fn test_openssl_384() { let (params, _) = util::default_params(); let key_pair = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P384_SHA384).unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate. verify_cert(&cert, &key_pair); verify_csr(&cert, &key_pair); } #[test] #[cfg(feature = "aws_lc_rs")] fn test_openssl_521() { let (params, _) = util::default_params(); let key_pair = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P521_SHA512).unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate. verify_cert(&cert, &key_pair); verify_csr(&cert, &key_pair); } #[test] fn test_openssl_25519() { let (params, _) = util::default_params(); let key_pair = KeyPair::generate_for(&rcgen::PKCS_ED25519).unwrap(); let cert = params.self_signed(&key_pair).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 (params, _) = util::default_params(); let key_pair = rcgen::KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V1).unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate as well as CSR, // but only on OpenSSL >= 1.1.1 // On prior versions, only do basic verification #[allow(clippy::unusual_byte_groupings)] if openssl::version::number() >= 0x1_01_01_00_f { verify_cert(&cert, &key_pair); verify_csr(&cert, &key_pair); } else { verify_cert_basic(&cert); } } #[test] fn test_openssl_25519_v2_given() { let (params, _) = util::default_params(); let key_pair = rcgen::KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V2).unwrap(); let cert = params.self_signed(&key_pair).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 (params, _) = util::default_params(); let key_pair = KeyPair::from_pem(util::RSA_TEST_KEY_PAIR_PEM).unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate. verify_cert(&cert, &key_pair); verify_csr(&cert, &key_pair); } #[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 (params, _) = util::default_params(); let key_pair = KeyPair::from_pkcs8_pem_and_sign_algo(util::RSA_TEST_KEY_PAIR_PEM, alg).unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate. if i >= 4 { verify_cert(&cert, &key_pair); verify_csr(&cert, &key_pair); } 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, ca_key) = util::default_params(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); let ca_cert = params.self_signed(&ca_key).unwrap(); let ca_cert_pem = ca_cert.pem(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap(); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Dev domain"); let cert_key = KeyPair::generate().unwrap(); let cert = params.signed_by(&cert_key, &ca_cert, &ca_key).unwrap(); let key = cert_key.serialize_der(); verify_cert_ca(&cert.pem(), &key, &ca_cert_pem); } #[test] fn test_openssl_separate_ca_with_printable_string() { let (mut params, ca_key) = util::default_params(); params.distinguished_name.push( DnType::CountryName, DnValue::PrintableString("US".try_into().unwrap()), ); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); let ca_cert = params.self_signed(&ca_key).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap(); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Dev domain"); let cert_key = KeyPair::generate().unwrap(); let cert = params.signed_by(&cert_key, &ca_cert, &ca_key).unwrap(); let key = cert_key.serialize_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.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); let ca_key = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap(); let ca_cert = params.self_signed(&ca_key).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap(); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Dev domain"); let cert_key = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P384_SHA384).unwrap(); let cert = params.signed_by(&cert_key, &ca_cert, &ca_key).unwrap(); let key = cert_key.serialize_der(); verify_cert_ca(&cert.pem(), &key, &ca_cert.pem()); } #[test] fn test_openssl_separate_ca_name_constraints() { let (mut params, ca_key) = 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 = params.self_signed(&ca_key).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap(); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Dev domain"); let cert_key = KeyPair::generate().unwrap(); let cert = params.signed_by(&cert_key, &ca_cert, &ca_key).unwrap(); let key = cert_key.serialize_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.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.pem().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.der()).unwrap(); // Asn1Time::from_unix takes i64 or i32 (depending on CPU architecture) #[allow(clippy::useless_conversion)] let expected_last_update = Asn1Time::from_unix( crl.params() .this_update .unix_timestamp() .try_into() .unwrap(), ) .unwrap(); assert!(openssl_crl.last_update().eq(&expected_last_update)); // Asn1Time::from_unix takes i64 or i32 (depending on CPU architecture) #[allow(clippy::useless_conversion)] let expected_next_update = Asn1Time::from_unix( crl.params() .next_update .unix_timestamp() .try_into() .unwrap(), ) .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" ] ); } #[test] #[cfg(all(feature = "crypto", feature = "aws_lc_rs"))] fn test_openssl_pkcs1_and_sec1_keys() { use openssl::ec::{EcGroup, EcKey}; use openssl::nid::Nid; use openssl::pkey::PKey; use openssl::rsa::Rsa; use pki_types::PrivateKeyDer; let rsa = Rsa::generate(2048).unwrap(); let rsa = PKey::from_rsa(rsa).unwrap(); let pkcs1_rsa_key_der = PrivateKeyDer::try_from(rsa.private_key_to_der().unwrap()).unwrap(); KeyPair::try_from(&pkcs1_rsa_key_der).unwrap(); let pkcs8_rsa_key_der = PrivateKeyDer::try_from(rsa.private_key_to_pkcs8().unwrap()).unwrap(); KeyPair::try_from(&pkcs8_rsa_key_der).unwrap(); let group = EcGroup::from_curve_name(Nid::SECP521R1).unwrap(); let ec_key = EcKey::generate(&group).unwrap(); let ec_key = PKey::from_ec_key(ec_key).unwrap(); let sec1_ec_key_der = PrivateKeyDer::try_from(ec_key.private_key_to_der().unwrap()).unwrap(); KeyPair::try_from(&sec1_ec_key_der).unwrap(); let pkcs8_ec_key_der = PrivateKeyDer::try_from(ec_key.private_key_to_pkcs8().unwrap()).unwrap(); KeyPair::try_from(&pkcs8_ec_key_der).unwrap(); } rcgen-0.13.2/tests/util.rs000064400000000000000000000122721046102023000134760ustar 00000000000000#![cfg(feature = "crypto")] use time::{Duration, OffsetDateTime}; use rcgen::{BasicConstraints, Certificate, CertificateParams, KeyPair}; use rcgen::{ CertificateRevocationList, CrlDistributionPoint, CrlIssuingDistributionPoint, CrlScope, }; use rcgen::{CertificateRevocationListParams, DnType, IsCa, KeyIdMethod}; use rcgen::{KeyUsagePurpose, RevocationReason, RevokedCertParams, SerialNumber}; // 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 */ #[allow(dead_code)] // Used in some but not all test compilation units. #[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, KeyPair) { let mut params = CertificateParams::new(vec!["crabs.crabs".to_string(), "localhost".to_string()]).unwrap(); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Master CA"); let key_pair = KeyPair::generate().unwrap(); (params, key_pair) } #[allow(unused)] // Used by openssl + x509-parser features. pub fn test_crl() -> (CertificateRevocationList, Certificate) { let (mut issuer, key_pair) = default_params(); issuer.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); issuer.key_usages = vec![ KeyUsagePurpose::KeyCertSign, KeyUsagePurpose::DigitalSignature, KeyUsagePurpose::CrlSign, ]; let issuer = issuer.self_signed(&key_pair).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], key_identifier_method: KeyIdMethod::Sha256, } .signed_by(&issuer, &key_pair) .unwrap(); (crl, issuer) } #[allow(unused)] // Used by openssl + x509-parser features. pub fn cert_with_crl_dps() -> Vec { let (mut params, key_pair) = 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()], }, ]; params.self_signed(&key_pair).unwrap().der().to_vec() } rcgen-0.13.2/tests/webpki.rs000064400000000000000000000506131046102023000140030ustar 00000000000000#![cfg(feature = "crypto")] use std::time::Duration as StdDuration; use pki_types::{CertificateDer, ServerName, SignatureVerificationAlgorithm, UnixTime}; use ring::rand::SystemRandom; use ring::signature::{self, EcdsaKeyPair, EcdsaSigningAlgorithm, Ed25519KeyPair, KeyPair as _}; #[cfg(feature = "pem")] use ring::signature::{RsaEncoding, RsaKeyPair}; use time::{Duration, OffsetDateTime}; use webpki::{ anchor_from_trusted_cert, BorrowedCertRevocationList, CertRevocationList, EndEntityCert, KeyUsage, RevocationOptionsBuilder, }; use rcgen::{ BasicConstraints, Certificate, CertificateParams, DnType, Error, IsCa, KeyPair, RemoteKeyPair, }; use rcgen::{CertificateRevocationListParams, RevocationReason, RevokedCertParams}; #[cfg(feature = "x509-parser")] use rcgen::{CertificateSigningRequestParams, DnValue}; use rcgen::{ExtendedKeyUsagePurpose, KeyUsagePurpose, SerialNumber}; mod util; fn sign_msg_ecdsa(key_pair: &KeyPair, msg: &[u8], alg: &'static EcdsaSigningAlgorithm) -> Vec { let pk_der = key_pair.serialize_der(); let key_pair = EcdsaKeyPair::from_pkcs8(alg, &pk_der, &ring::rand::SystemRandom::new()).unwrap(); let system_random = SystemRandom::new(); let signature = key_pair.sign(&system_random, msg).unwrap(); signature.as_ref().to_vec() } fn sign_msg_ed25519(key_pair: &KeyPair, msg: &[u8]) -> Vec { let pk_der = key_pair.serialize_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(key_pair: &KeyPair, msg: &[u8], encoding: &'static dyn RsaEncoding) -> Vec { let pk_der = key_pair.serialize_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: &CertificateDer<'_>, cert: &'a Certificate, cert_key: &'a KeyPair, alg: &dyn SignatureVerificationAlgorithm, sign_fn: impl FnOnce(&'a KeyPair, &'b [u8]) -> Vec, ) { #[cfg(feature = "pem")] { println!("{}", cert.pem()); } check_cert_ca(cert_der, cert_key, cert_der, alg, alg, sign_fn); } fn check_cert_ca<'a, 'b>( cert_der: &CertificateDer<'_>, cert_key: &'a KeyPair, ca_der: &CertificateDer<'_>, cert_alg: &dyn SignatureVerificationAlgorithm, ca_alg: &dyn SignatureVerificationAlgorithm, sign_fn: impl FnOnce(&'a KeyPair, &'b [u8]) -> Vec, ) { let trust_anchor = anchor_from_trusted_cert(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 = UnixTime::since_unix_epoch(StdDuration::from_secs(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(), None, None, ) .expect("valid TLS server cert"); // (2/3) Check that the cert is valid for the given DNS name let dns_name = ServerName::try_from("crabs.crabs").unwrap(); end_entity_cert .verify_is_valid_for_subject_name(&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_key, msg); end_entity_cert .verify_signature(cert_alg, msg, &signature) .expect("signature is valid"); } #[test] fn test_webpki() { let (params, key_pair) = util::default_params(); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate. let sign_fn = |key_pair, msg| sign_msg_ecdsa(key_pair, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); check_cert( cert.der(), &cert, &key_pair, webpki::ring::ECDSA_P256_SHA256, sign_fn, ); } #[test] fn test_webpki_256() { let (params, _) = util::default_params(); let key_pair = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate. let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); check_cert( cert.der(), &cert, &key_pair, webpki::ring::ECDSA_P256_SHA256, sign_fn, ); } #[test] fn test_webpki_384() { let (params, _) = util::default_params(); let key_pair = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P384_SHA384).unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate. let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P384_SHA384_ASN1_SIGNING); check_cert( cert.der(), &cert, &key_pair, webpki::ring::ECDSA_P384_SHA384, sign_fn, ); } #[test] fn test_webpki_25519() { let (params, _) = util::default_params(); let key_pair = KeyPair::generate_for(&rcgen::PKCS_ED25519).unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate. check_cert( cert.der(), &cert, &key_pair, webpki::ring::ED25519, sign_msg_ed25519, ); } #[cfg(feature = "pem")] #[test] fn test_webpki_25519_v1_given() { let (params, _) = util::default_params(); let key_pair = rcgen::KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V1).unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate. check_cert( cert.der(), &cert, &key_pair, webpki::ring::ED25519, sign_msg_ed25519, ); } #[cfg(feature = "pem")] #[test] fn test_webpki_25519_v2_given() { let (params, _) = util::default_params(); let key_pair = rcgen::KeyPair::from_pem(util::ED25519_TEST_KEY_PAIR_PEM_V2).unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate. check_cert( cert.der(), &cert, &key_pair, webpki::ring::ED25519, sign_msg_ed25519, ); } #[cfg(feature = "pem")] #[test] fn test_webpki_rsa_given() { let (params, _) = util::default_params(); let key_pair = rcgen::KeyPair::from_pem(util::RSA_TEST_KEY_PAIR_PEM).unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate. check_cert( cert.der(), &cert, &key_pair, webpki::ring::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::ring::RSA_PKCS1_2048_8192_SHA256, &signature::RSA_PKCS1_SHA256, ), ( &rcgen::PKCS_RSA_SHA384, webpki::ring::RSA_PKCS1_2048_8192_SHA384, &signature::RSA_PKCS1_SHA384, ), ( &rcgen::PKCS_RSA_SHA512, webpki::ring::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 (params, _) = util::default_params(); let key_pair = rcgen::KeyPair::from_pkcs8_pem_and_sign_algo(util::RSA_TEST_KEY_PAIR_PEM, c.0).unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate. check_cert(cert.der(), &cert, &key_pair, c.1, |msg, cert| { sign_msg_rsa(msg, cert, c.2) }); } } #[test] fn test_webpki_separate_ca() { let (mut params, ca_key) = util::default_params(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); let ca_cert = params.self_signed(&ca_key).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap(); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Dev domain"); let key_pair = KeyPair::generate().unwrap(); let cert = params.signed_by(&key_pair, &ca_cert, &ca_key).unwrap(); let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); check_cert_ca( cert.der(), &key_pair, ca_cert.der(), webpki::ring::ECDSA_P256_SHA256, webpki::ring::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); let ca_key = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap(); let ca_cert = params.self_signed(&ca_key).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap(); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Dev domain"); let key_pair = KeyPair::generate_for(&rcgen::PKCS_ED25519).unwrap(); let cert = params.signed_by(&key_pair, &ca_cert, &ca_key).unwrap(); check_cert_ca( cert.der(), &key_pair, ca_cert.der(), webpki::ring::ED25519, webpki::ring::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::Error> { let system_random = SystemRandom::new(); self.0 .sign(&system_random, msg) .map(|s| s.as_ref().to_owned()) .map_err(|_| Error::RingUnspecified) } fn algorithm(&self) -> &'static rcgen::SignatureAlgorithm { &rcgen::PKCS_ECDSA_P256_SHA256 } } let rng = ring::rand::SystemRandom::new(); let key_pair = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap(); let remote = EcdsaKeyPair::from_pkcs8( &signature::ECDSA_P256_SHA256_ASN1_SIGNING, &key_pair.serialize_der(), &rng, ) .unwrap(); let key_pair = EcdsaKeyPair::from_pkcs8( &signature::ECDSA_P256_SHA256_ASN1_SIGNING, &key_pair.serialize_der(), &rng, ) .unwrap(); let remote = KeyPair::from_remote(Box::new(Remote(remote))).unwrap(); let (params, _) = util::default_params(); let cert = params.self_signed(&remote).unwrap(); // Now verify the certificate. 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, &remote, webpki::ring::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() { let (mut params, ca_key) = util::default_params(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); params.key_usages.push(KeyUsagePurpose::KeyCertSign); let ca_cert = params.self_signed(&ca_key).unwrap(); let ca_cert_der = ca_cert.der(); let imported_ca_cert_params = CertificateParams::from_ca_cert_der(ca_cert_der).unwrap(); assert_eq!( imported_ca_cert_params.key_usages, vec![KeyUsagePurpose::KeyCertSign] ); let imported_ca_cert = imported_ca_cert_params.self_signed(&ca_key).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap(); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Dev domain"); let cert_key = KeyPair::generate().unwrap(); let cert = params .signed_by(&cert_key, &imported_ca_cert, &ca_key) .unwrap(); let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); check_cert_ca( cert.der(), &cert_key, ca_cert_der, webpki::ring::ECDSA_P256_SHA256, webpki::ring::ECDSA_P256_SHA256, sign_fn, ); } #[cfg(feature = "x509-parser")] #[test] fn test_webpki_imported_ca_with_printable_string() { let (mut params, ca_key) = util::default_params(); params.distinguished_name.push( DnType::CountryName, DnValue::PrintableString("US".try_into().unwrap()), ); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); let ca_cert = params.self_signed(&ca_key).unwrap(); let ca_cert_der = ca_cert.der(); let imported_ca_cert_params = CertificateParams::from_ca_cert_der(ca_cert_der).unwrap(); let imported_ca_cert = imported_ca_cert_params.self_signed(&ca_key).unwrap(); let mut params = CertificateParams::new(vec!["crabs.crabs".to_string()]).unwrap(); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Dev domain"); let cert_key = KeyPair::generate().unwrap(); let cert = params .signed_by(&cert_key, &imported_ca_cert, &ca_key) .unwrap(); let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); check_cert_ca( cert.der(), &cert_key, ca_cert_der, webpki::ring::ECDSA_P256_SHA256, webpki::ring::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()]).unwrap(); params .distinguished_name .push(DnType::OrganizationName, "Crab widgits SE"); params .distinguished_name .push(DnType::CommonName, "Dev domain"); let eku_test = vec![ ExtendedKeyUsagePurpose::Any, ExtendedKeyUsagePurpose::ClientAuth, ExtendedKeyUsagePurpose::CodeSigning, ExtendedKeyUsagePurpose::EmailProtection, ExtendedKeyUsagePurpose::OcspSigning, ExtendedKeyUsagePurpose::ServerAuth, ExtendedKeyUsagePurpose::TimeStamping, ]; for eku in &eku_test { params.insert_extended_key_usage(eku.clone()); } let cert_key = KeyPair::generate().unwrap(); let csr = params.serialize_request(&cert_key).unwrap(); let csr = CertificateSigningRequestParams::from_der(csr.der()).unwrap(); let ekus_contained = &csr.params.extended_key_usages; for eku in &eku_test { assert!(ekus_contained.contains(eku)); } let (mut params, ca_key) = util::default_params(); params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained); for eku in &eku_test { params.insert_extended_key_usage(eku.clone()); } let ekus_contained = ¶ms.extended_key_usages; for eku in &eku_test { assert!(ekus_contained.contains(eku)); } let ca_cert = params.self_signed(&ca_key).unwrap(); let ekus_contained = &ca_cert.params().extended_key_usages; for eku in &eku_test { assert!(ekus_contained.contains(eku)); } let cert = csr.signed_by(&ca_cert, &ca_key).unwrap(); let ekus_contained = &cert.params().extended_key_usages; for eku in &eku_test { assert!(ekus_contained.contains(eku)); } let eku_cert = &ca_cert.params().extended_key_usages; for eku in &eku_test { assert!(eku_cert.contains(eku)); } let sign_fn = |key_pair, msg| sign_msg_ecdsa(key_pair, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); check_cert_ca( cert.der(), &cert_key, ca_cert.der(), webpki::ring::ECDSA_P256_SHA256, webpki::ring::ECDSA_P256_SHA256, sign_fn, ); } #[test] fn test_webpki_serial_number() { let (mut params, key_pair) = util::default_params(); params.serial_number = Some(vec![0, 1, 2].into()); let cert = params.self_signed(&key_pair).unwrap(); // Now verify the certificate. let sign_fn = |cert, msg| sign_msg_ecdsa(cert, msg, &signature::ECDSA_P256_SHA256_ASN1_SIGNING); check_cert( cert.der(), &cert, &key_pair, webpki::ring::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, _) = util::test_crl(); let revoked_cert = crl.params().revoked_certs.first().unwrap(); // We should be able to parse the CRL DER without error. let webpki_crl = CertRevocationList::from( BorrowedCertRevocationList::from_der(crl.der()).expect("failed to parse CRL DER"), ); // 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, 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, UnixTime::since_unix_epoch(StdDuration::from_secs( 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, ]; let issuer_key = KeyPair::generate_for(alg).unwrap(); let issuer = issuer.self_signed(&issuer_key).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.serial_number = Some(SerialNumber::from(99999)); let ee_key = KeyPair::generate_for(alg).unwrap(); let ee = ee.signed_by(&ee_key, &issuer, &issuer_key).unwrap(); // Set up webpki's verification requirements. let trust_anchor = anchor_from_trusted_cert(issuer.der()).unwrap(); let trust_anchor_list = &[trust_anchor]; let end_entity_cert = EndEntityCert::try_from(ee.der()).unwrap(); let unix_time = 0x40_00_00_00; let time = UnixTime::since_unix_epoch(StdDuration::from_secs(unix_time)); // The end entity cert should validate with the issuer without error. end_entity_cert .verify_for_usage( &[webpki::ring::ECDSA_P256_SHA256], &trust_anchor_list[..], &[], time, KeyUsage::client_auth(), None, None, ) .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.params().serial_number.clone().unwrap(), revocation_time: now, reason_code: Some(RevocationReason::KeyCompromise), invalidity_date: None, }], key_identifier_method: rcgen::KeyIdMethod::Sha256, } .signed_by(&issuer, &issuer_key) .unwrap(); let crl = CertRevocationList::from(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::ring::ECDSA_P256_SHA256], &trust_anchor_list[..], &[], time, KeyUsage::client_auth(), Some(RevocationOptionsBuilder::new(&[&crl]).unwrap().build()), None, ); assert!(matches!(result, Err(webpki::Error::CertRevoked))); } #[test] fn test_webpki_cert_crl_dps() { let der = util::cert_with_crl_dps(); let cert = CertificateDer::from(der); webpki::EndEntityCert::try_from(&cert).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. }