rcgen-0.14.4/.cargo_vcs_info.json0000644000000001430000000000100122160ustar { "git": { "sha1": "9f7fbb653e505cf0f4a84d8eb25fc3827216a30f" }, "path_in_vcs": "rcgen" }rcgen-0.14.4/CHANGELOG.md000064400000000000000000000307571046102023000126350ustar 00000000000000 # Changes Newer releases can be found [on GitHub](https://github.com/rustls/rcgen/releases). ## 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.14.4/Cargo.lock0000644000000610230000000000100101750ustar # 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.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" dependencies = [ "asn1-rs-derive", "asn1-rs-impl", "displaydoc", "nom", "num-traits", "rusticata-macros", "thiserror", "time", ] [[package]] name = "asn1-rs-derive" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "asn1-rs-impl" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-fips-sys" version = "0.13.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2608e5a7965cc9d58c56234d346c9c89b824c4c8652b6f047b3bd0a777c0644f" dependencies = [ "bindgen", "cc", "cmake", "dunce", "fs_extra", "regex", ] [[package]] name = "aws-lc-rs" version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" dependencies = [ "aws-lc-fips-sys", "aws-lc-sys", "zeroize", ] [[package]] name = "aws-lc-sys" version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" dependencies = [ "bindgen", "cc", "cmake", "dunce", "fs_extra", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bindgen" version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" 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.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "cc" version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ "jobserver", "libc", "shlex", ] [[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.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[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.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" dependencies = [ "cc", ] [[package]] name = "data-encoding" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "der-parser" version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" dependencies = [ "asn1-rs", "displaydoc", "nom", "num-bigint", "num-traits", "rusticata-macros", ] [[package]] name = "deranged" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", ] [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "dunce" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "errno" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", "windows-sys 0.60.2", ] [[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 = "getrandom" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] name = "getrandom" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", ] [[package]] name = "glob" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "home" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ "windows-sys 0.59.0", ] [[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.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ "getrandom 0.3.3", "libc", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[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.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libloading" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", "windows-targets 0.53.2", ] [[package]] name = "linux-raw-sys" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "num-bigint" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "oid-registry" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" dependencies = [ "asn1-rs", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl" version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" 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.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "pem" version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" dependencies = [ "base64", "serde", ] [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "prettyplease" version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" dependencies = [ "proc-macro2", "syn", ] [[package]] name = "proc-macro2" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rcgen" version = "0.14.4" dependencies = [ "aws-lc-rs", "openssl", "pem", "ring", "rustls-pki-types", "time", "x509-parser", "yasna", "zeroize", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "ring" version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", ] [[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.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.59.0", ] [[package]] name = "rustls-pki-types" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "zeroize", ] [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 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 = "syn" version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "synstructure" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "time" version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", ] [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[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 = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] [[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 0.52.6", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ "windows-targets 0.53.2", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows-targets" version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", "windows_i686_gnullvm 0.53.0", "windows_i686_msvc 0.53.0", "windows_x86_64_gnu 0.53.0", "windows_x86_64_gnullvm 0.53.0", "windows_x86_64_msvc 0.53.0", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags", ] [[package]] name = "x509-parser" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb3e137310115a65136898d2079f003ce33331a6c4b0d51f1531d1be082b6425" 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" rcgen-0.14.4/Cargo.toml0000644000000046300000000000100102210ustar # 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" rust-version = "1.71" name = "rcgen" version = "0.14.4" 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"] [features] aws_lc_rs = [ "crypto", "dep:aws-lc-rs", "aws-lc-rs/aws-lc-sys", ] aws_lc_rs_unstable = [ "aws_lc_rs", "aws-lc-rs/unstable", ] crypto = [] default = [ "crypto", "pem", "ring", ] fips = [ "crypto", "dep:aws-lc-rs", "aws-lc-rs/fips", ] ring = [ "crypto", "dep:ring", ] [lib] name = "rcgen" path = "src/lib.rs" [[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", ] [dependencies.aws-lc-rs] version = "1.13.3" 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.18" features = ["verify"] optional = true [dependencies.yasna] version = "0.5.2" features = [ "time", "std", ] [dependencies.zeroize] version = "1.2" optional = true [target."cfg(unix)".dev-dependencies.openssl] version = "0.10" rcgen-0.14.4/Cargo.toml.orig000064400000000000000000000026111046102023000136770ustar 00000000000000[package] name = "rcgen" version = "0.14.4" documentation = "https://docs.rs/rcgen" description.workspace = true repository.workspace = true readme.workspace = true license.workspace = true edition.workspace = true rust-version.workspace = true keywords.workspace = true [features] default = ["crypto", "pem", "ring"] aws_lc_rs = ["crypto", "dep:aws-lc-rs", "aws-lc-rs/aws-lc-sys"] aws_lc_rs_unstable = ["aws_lc_rs", "aws-lc-rs/unstable"] fips = ["crypto", "dep:aws-lc-rs", "aws-lc-rs/fips"] crypto = [] ring = ["crypto", "dep:ring"] [dependencies] aws-lc-rs = { workspace = true, optional = true } pem = { workspace = true, optional = true } pki-types = { workspace = true } ring = { workspace = true, optional = true } time = { workspace = true } x509-parser = { workspace = true, features = ["verify"], optional = true } yasna = { workspace = true } zeroize = { workspace = true, optional = true } [target."cfg(unix)".dev-dependencies] openssl = { workspace = true } [[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"] [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::*", ] rcgen-0.14.4/LICENSE000064400000000000000000000266261046102023000120310ustar 00000000000000Copyright (c) 2019-2025 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-2025 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.14.4/README.md000064400000000000000000000035041046102023000122710ustar 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.14.4/docs/0.12-to-0.13.md000064400000000000000000000110071046102023000136400ustar 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.14.4/examples/rsa-irc-openssl.rs000064400000000000000000000032151046102023000162160ustar 00000000000000#[cfg(unix)] fn 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(()) } #[cfg(not(unix))] fn main() -> Result<(), Box> { // Due to the support burden of running OpenSSL on Windows, // we only support the OpenSSL backend on Unix-like systems. // It should still work on Windows if you have OpenSSL installed. unimplemented!("OpenSSL backend is not supported on Windows"); } rcgen-0.14.4/examples/sign-leaf-with-ca.rs000064400000000000000000000043711046102023000164000ustar 00000000000000use rcgen::{ BasicConstraints, Certificate, CertificateParams, DnType, DnValue::PrintableString, ExtendedKeyUsagePurpose, IsCa, Issuer, KeyPair, KeyUsagePurpose, }; use time::{Duration, OffsetDateTime}; /// Example demonstrating signing end-entity certificate with ca fn main() { let (ca, issuer) = new_ca(); let end_entity = new_end_entity(&issuer); 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, Issuer<'static, 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(); let cert = params.self_signed(&key_pair).unwrap(); (cert, Issuer::new(params, key_pair)) } fn new_end_entity(issuer: &Issuer<'static, 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, issuer).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.14.4/examples/simple.rs000064400000000000000000000022541046102023000144700ustar 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.14.4/src/certificate.rs000064400000000000000000001402161046102023000144330ustar 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, DERWriterSeq, Tag}; use crate::crl::CrlDistributionPoint; use crate::csr::CertificateSigningRequest; use crate::key_pair::{serialize_public_key_der, sign_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, KeyUsagePurpose, SanType, SerialNumber, SigningKey, }; /// An issued certificate #[derive(Debug, Clone, PartialEq, Eq)] pub struct Certificate { pub(crate) der: CertificateDer<'static>, } impl Certificate { /// 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, 1, 1); let not_after = date_time_ymd(4096, 1, 1); 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` (typically either a [`CertificateParams`] or /// [`Certificate`]). 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: &Issuer<'_, impl SigningKey>, ) -> Result { Ok(Certificate { der: self.serialize_der_with_signer(public_key, issuer)?, }) } /// 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, signing_key: &impl SigningKey) -> Result { let issuer = Issuer::from_params(self, signing_key); Ok(Certificate { der: self.serialize_der_with_signer(signing_key, &issuer)?, }) } /// 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, key: &impl PublicKeyData) -> Vec { self.key_identifier_method .derive(key.subject_public_key_info()) } #[cfg(all(test, feature = "x509-parser"))] pub(crate) fn from_ca_cert_der(ca_cert: &CertificateDer<'_>) -> Result { let (_remainder, x509) = x509_parser::parse_x509_certificate(ca_cert) .map_err(|_| Error::CouldNotParseCertificate)?; Ok(CertificateParams { is_ca: IsCa::from_x509(&x509)?, subject_alt_names: SanType::from_x509(&x509)?, key_usages: KeyUsagePurpose::from_x509(&x509)?, extended_key_usages: ExtendedKeyUsagePurpose::from_x509(&x509)?, name_constraints: NameConstraints::from_x509(&x509)?, serial_number: Some(x509.serial.to_bytes_be().into()), key_identifier_method: KeyIdMethod::from_x509(&x509)?, distinguished_name: DistinguishedName::from_name(&x509.tbs_certificate.subject)?, not_before: x509.validity().not_before.to_datetime(), not_after: x509.validity().not_after.to_datetime(), ..Default::default() }) } /// 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) { 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() }); match u16::BITS - bit_string.trailing_zeros() { bits @ 0..=8 => { writer.write_bitvec_bytes(&bit_string.to_be_bytes()[..1], bits as usize) }, bits @ 9..=16 => { writer.write_bitvec_bytes(&bit_string.to_be_bytes(), bits as usize) }, _ => unreachable!(), } }); } 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; } // Per https://tools.ietf.org/html/rfc5280#section-4.1.2.6, SAN must be marked // as critical if subject is empty. let critical = self.distinguished_name.entries.is_empty(); write_x509_extension(writer, oid::SUBJECT_ALT_NAME, critical, |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: &impl SigningKey, ) -> 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: &impl SigningKey, 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; // - subject_key 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 = sign_der(subject_key, |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<'_, impl SigningKey>, ) -> Result, Error> { let der = sign_der(&*issuer.signing_key, |writer| { let pub_key_spki = pub_key.subject_public_key_info(); // 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 .signing_key .algorithm() .write_alg_ident(writer.next()); // Write issuer name write_distinguished_name(writer.next(), issuer.distinguished_name.as_ref()); // 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| self.write_extensions(writer, &pub_key_spki, issuer)) })?; Ok(()) })?; Ok(der.into()) } fn write_extensions( &self, writer: &mut DERWriterSeq, pub_key_spki: &[u8], issuer: &Issuer<'_, impl SigningKey>, ) -> Result<(), Error> { if self.use_authority_key_identifier_extension { write_x509_authority_key_identifier( writer.next(), match issuer.key_identifier_method.as_ref() { KeyIdMethod::PreSpecified(aki) => aki.clone(), #[cfg(feature = "crypto")] _ => issuer .key_identifier_method .derive(issuer.signing_key.subject_public_key_info()), }, ); } // Write subject_alt_names 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(()) } /// 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); } } } impl AsRef for CertificateParams { fn as_ref(&self) -> &CertificateParams { self } } 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 { #[cfg(all(test, feature = "x509-parser"))] fn from_x509(x509: &x509_parser::certificate::X509Certificate<'_>) -> Result, Error> { let extended_key_usage = x509 .extended_key_usage() .map_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(Self::Any); } if extended_key_usage.server_auth { extended_key_usages.push(Self::ServerAuth); } if extended_key_usage.client_auth { extended_key_usages.push(Self::ClientAuth); } if extended_key_usage.code_signing { extended_key_usages.push(Self::CodeSigning); } if extended_key_usage.email_protection { extended_key_usages.push(Self::EmailProtection); } if extended_key_usage.time_stamping { extended_key_usages.push(Self::TimeStamping); } if extended_key_usage.ocsp_signing { extended_key_usages.push(Self::OcspSigning); } } Ok(extended_key_usages) } 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 { #[cfg(all(test, feature = "x509-parser"))] fn from_x509( x509: &x509_parser::certificate::X509Certificate<'_>, ) -> Result, Error> { let constraints = x509 .name_constraints() .map_err(|_| Error::CouldNotParseCertificate)? .map(|ext| ext.value); let Some(constraints) = constraints else { return Ok(None); }; let permitted_subtrees = if let Some(permitted) = &constraints.permitted_subtrees { GeneralSubtree::from_x509(permitted)? } else { Vec::new() }; let excluded_subtrees = if let Some(excluded) = &constraints.excluded_subtrees { GeneralSubtree::from_x509(excluded)? } else { Vec::new() }; Ok(Some(Self { permitted_subtrees, excluded_subtrees, })) } 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 { #[cfg(all(test, feature = "x509-parser"))] fn from_x509( 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) => Self::Rfc822Name(s.to_string()), GeneralName::DNSName(s) => Self::DnsName(s.to_string()), GeneralName::DirectoryName(n) => { Self::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(); Self::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(); Self::IpAddress(CidrSubnet::V6(addr, mask)) }, _ => continue, }; result.push(subtree); } Ok(result) } 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(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[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; 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(Clone, Copy, Debug, PartialEq, Eq, Hash)] 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), } impl IsCa { #[cfg(all(test, feature = "x509-parser"))] fn from_x509(x509: &x509_parser::certificate::X509Certificate<'_>) -> Result { use x509_parser::extensions::BasicConstraints as B; let basic_constraints = x509 .basic_constraints() .map_err(|_| Error::CouldNotParseCertificate)? .map(|ext| ext.value); Ok(match basic_constraints { Some(B { ca: true, path_len_constraint: Some(n), }) if *n <= u8::MAX as u32 => Self::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, }) => Self::Ca(BasicConstraints::Unconstrained), Some(B { ca: false, .. }) => Self::ExplicitNoCa, None => Self::NoCa, }) } } /// 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(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum BasicConstraints { /// No constraint Unconstrained, /// Constrain to the contained number of intermediate certificates Constrained(u8), } #[cfg(test)] mod tests { #[cfg(feature = "x509-parser")] use std::net::Ipv4Addr; #[cfg(feature = "x509-parser")] use pki_types::pem::PemObject; #[cfg(feature = "pem")] use super::*; #[cfg(feature = "x509-parser")] use crate::DnValue; #[cfg(feature = "crypto")] use crate::KeyPair; #[cfg(feature = "crypto")] #[test] fn test_with_key_usages() { let params = CertificateParams { // Set key usages key_usages: vec![ KeyUsagePurpose::DigitalSignature, KeyUsagePurpose::KeyEncipherment, KeyUsagePurpose::ContentCommitment, ], // This can sign things! is_ca: IsCa::Ca(BasicConstraints::Constrained(0)), ..CertificateParams::default() }; // 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() { // should have the minimal number of octets, and no extra trailing zero bytes // ref. https://github.com/rustls/rcgen/issues/368 assert_eq!(ext.value, vec![0x03, 0x02, 0x05, 0xe0]); if let x509_parser::extensions::ParsedExtension::KeyUsage(usage) = ext.parsed_extension() { assert!(usage.flags == 7); found = true; } } } assert!(found); } #[cfg(feature = "crypto")] #[test] fn test_with_key_usages_decipheronly_only() { let params = CertificateParams { // Set key usages key_usages: vec![KeyUsagePurpose::DecipherOnly], // This can sign things! is_ca: IsCa::Ca(BasicConstraints::Constrained(0)), ..CertificateParams::default() }; // 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() { if let x509_parser::extensions::ParsedExtension::KeyUsage(usage) = ext.parsed_extension() { assert!(usage.flags == 256); found = true; } } } assert!(found); } #[cfg(feature = "crypto")] #[test] fn test_with_extended_key_usages_any() { let params = CertificateParams { extended_key_usages: vec![ExtendedKeyUsagePurpose::Any], ..CertificateParams::default() }; // 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; const OID_1: &[u64] = &[1, 2, 3, 4]; const OID_2: &[u64] = &[1, 2, 3, 4, 5, 6]; let params = CertificateParams { extended_key_usages: vec![ ExtendedKeyUsagePurpose::Other(Vec::from(OID_1)), ExtendedKeyUsagePurpose::Other(Vec::from(OID_2)), ], ..CertificateParams::default() }; // 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(feature = "x509-parser")] #[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(); // 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")] #[test] fn parse_ia5string_subject() { // Create and serialize a certificate with a subject containing an IA5String email address. let email_address_dn_type = DnType::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 = CertificateParams::new(vec!["crabs".to_owned()]).unwrap(); params.distinguished_name = DistinguishedName::new(); params.distinguished_name.push( email_address_dn_type.clone(), email_address_dn_value.clone(), ); let key_pair = KeyPair::generate().unwrap(); let cert = params.self_signed(&key_pair).unwrap(); // 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")] #[test] fn converts_from_ip() { let ip = Ipv4Addr::new(2, 4, 6, 8); let ip_san = SanType::IpAddress(IpAddr::V4(ip)); let mut params = CertificateParams::new(vec!["crabs".to_owned()]).unwrap(); let ca_key = KeyPair::generate().unwrap(); // 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); // Serialize our cert that has our chosen san, so we can testing parsing/deserializing it. let cert = params.self_signed(&ca_key).unwrap(); let actual = CertificateParams::from_ca_cert_der(cert.der()).unwrap(); assert!(actual.subject_alt_names.contains(&ip_san)); } #[cfg(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 ca_kp = KeyPair::from_pem(ca_key).unwrap(); let ca = Issuer::from_ca_cert_pem(ca_cert, ca_kp).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()), ca.key_identifier_method.as_ref() ); let ca_cert_der = CertificateDer::from_pem_slice(ca_cert.as_bytes()).unwrap(); let (_, x509_ca) = x509_parser::parse_x509_certificate(ca_cert_der.as_ref()).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 ee_params = CertificateParams { use_authority_key_identifier_extension: true, ..CertificateParams::default() }; let ee_cert = ee_params.signed_by(&ee_key, &ca).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.14.4/src/crl.rs000064400000000000000000000341301046102023000127260ustar 00000000000000#[cfg(feature = "pem")] use pem::Pem; use pki_types::CertificateRevocationListDer; use time::OffsetDateTime; use yasna::DERWriter; use yasna::Tag; use crate::key_pair::sign_der; #[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, Error, Issuer, KeyIdMethod, KeyUsagePurpose, SerialNumber, SigningKey, }; /// 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 SigningKey for MyKeyPair { /// fn sign(&self, _: &[u8]) -> Result, rcgen::Error> { Ok(vec![]) } /// } /// #[cfg(not(feature = "crypto"))] /// impl PublicKeyData for MyKeyPair { /// fn der_bytes(&self) -> &[u8] { &self.public_key } /// 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 key_pair = MyKeyPair { public_key: vec![] }; /// let issuer = Issuer::new(issuer_params, key_pair); /// /// // 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).unwrap(); ///# } #[derive(Clone, Debug, PartialEq, Eq)] pub struct CertificateRevocationList { der: CertificateRevocationListDer<'static>, } impl CertificateRevocationList { /// 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 #[derive(Clone, Debug, PartialEq, Eq)] 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: &Issuer<'_, impl SigningKey>, ) -> Result { if self.next_update.le(&self.this_update) { return Err(Error::InvalidCrlNextUpdate); } if !issuer.key_usages.is_empty() && !issuer.key_usages.contains(&KeyUsagePurpose::CrlSign) { return Err(Error::IssuerNotCrlSigner); } Ok(CertificateRevocationList { der: self.serialize_der(issuer)?.into(), }) } fn serialize_der(&self, issuer: &Issuer<'_, impl SigningKey>) -> Result, Error> { sign_der(&*issuer.signing_key, |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 .signing_key .algorithm() .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.as_ref()); // 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.signing_key.subject_public_key_info()), ); // 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). #[derive(Clone, Debug, PartialEq, Eq)] 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`]. #[derive(Clone, Debug, PartialEq, Eq)] 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.14.4/src/csr.rs000064400000000000000000000176371046102023000127520ustar 00000000000000use std::hash::Hash; #[cfg(feature = "pem")] use pem::Pem; use pki_types::CertificateSigningRequestDer; #[cfg(feature = "pem")] use crate::ENCODE_CONFIG; use crate::{ Certificate, CertificateParams, Error, Issuer, PublicKeyData, SignatureAlgorithm, SigningKey, }; #[cfg(feature = "x509-parser")] use crate::{DistinguishedName, SanType}; /// A public key, extracted from a CSR #[derive(Clone, 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) -> &'static SignatureAlgorithm { self.alg } } /// A certificate signing request (CSR) that can be encoded to PEM or DER. #[derive(Clone, Debug, PartialEq, Eq)] 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 #[derive(Clone, Debug, PartialEq, Eq)] 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).map_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: &Issuer) -> Result { Ok(Certificate { der: self .params .serialize_der_with_signer(&self.public_key, issuer)?, }) } } #[cfg(all(test, feature = "x509-parser"))] mod tests { use crate::{CertificateParams, ExtendedKeyUsagePurpose, KeyPair, KeyUsagePurpose}; use x509_parser::certification_request::X509CertificationRequest; use x509_parser::prelude::{FromDer, ParsedExtension}; #[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(_) )); } } rcgen-0.14.4/src/error.rs000064400000000000000000000111471046102023000133020ustar 00000000000000use std::fmt; #[derive(Clone, 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(Clone, 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.14.4/src/key_pair.rs000064400000000000000000000617571046102023000137700ustar 00000000000000#[cfg(feature = "crypto")] use 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}; #[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))] use aws_lc_rs::unstable::signature::PqdsaKeyPair; /// A key pair variant #[allow(clippy::large_enum_variant)] #[cfg(feature = "crypto")] pub(crate) enum KeyPairKind { /// A Ecdsa key pair Ec(EcdsaKeyPair), /// A Ed25519 key pair Ed(Ed25519KeyPair), /// A Pqdsa key pair #[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))] Pq(PqdsaKeyPair), /// A RSA key pair Rsa(RsaKeyPair, &'static dyn RsaEncoding), } #[cfg(feature = "crypto")] impl fmt::Debug for KeyPairKind { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Ec(key_pair) => write!(f, "{key_pair:?}"), Self::Ed(key_pair) => write!(f, "{key_pair:?}"), #[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))] Self::Pq(key_pair) => write!(f, "{key_pair:?}"), Self::Rsa(key_pair, _) => write!(f, "{key_pair:?}"), } } } /// A key pair used to sign certificates and CSRs #[cfg(feature = "crypto")] pub struct KeyPair { pub(crate) kind: KeyPairKind, pub(crate) alg: &'static SignatureAlgorithm, pub(crate) serialized_der: Vec, } #[cfg(feature = "crypto")] impl fmt::Debug for KeyPair { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("KeyPair") .field("kind", &self.kind) .field("alg", &self.alg) .field("serialized_der", &"[secret key elided]") .finish() } } #[cfg(feature = "crypto")] 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(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))] SignAlgo::PqDsa(sign_alg) => { let key_pair = PqdsaKeyPair::generate(sign_alg)._err()?; let key_pair_serialized = key_pair.to_pkcs8()._err()?.as_ref().to_vec(); Ok(KeyPair { kind: KeyPairKind::Pq(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 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 [`KeyPair::public_key()`][public_key] /// would output, and how [`UnparsedPublicKey::verify()`][verify] /// would accept. /// /// [public_key]: crate::ring_like::signature::KeyPair::public_key() /// [verify]: crate::ring_like::signature::UnparsedPublicKey::verify() 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) } /// 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.subject_public_key_info(); 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 pub fn serialize_der(&self) -> Vec { self.serialized_der.clone() } /// Returns a reference to the serialized key pair (including the private key) /// in PKCS#8 format in DER pub fn serialized_der(&self) -> &[u8] { &self.serialized_der } /// 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 SigningKey for KeyPair { fn sign(&self, msg: &[u8]) -> Result, Error> { Ok(match &self.kind { KeyPairKind::Ec(kp) => { let system_random = SystemRandom::new(); let signature = kp.sign(&system_random, msg)._err()?; signature.as_ref().to_owned() }, KeyPairKind::Ed(kp) => kp.sign(msg).as_ref().to_owned(), #[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))] KeyPairKind::Pq(kp) => { let mut signature = vec![0; kp.algorithm().signature_len()]; kp.sign(msg, &mut signature)._err()?; signature }, 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()?; signature }, }) } } #[cfg(feature = "crypto")] impl PublicKeyData for KeyPair { fn der_bytes(&self) -> &[u8] { match &self.kind { KeyPairKind::Ec(kp) => kp.public_key().as_ref(), KeyPairKind::Ed(kp) => kp.public_key().as_ref(), #[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))] KeyPairKind::Pq(kp) => kp.public_key().as_ref(), KeyPairKind::Rsa(kp, _) => kp.public_key().as_ref(), } } fn algorithm(&self) -> &'static SignatureAlgorithm { self.alg } } #[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, } pub(crate) fn sign_der( key: &impl SigningKey, 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 key.algorithm().write_alg_ident(writer.next()); // Write signature let sig = key.sign(&data)?; let writer = writer.next(); writer.write_bitvec_bytes(&sig, sig.len() * 8); Ok(()) }) }) } /// A key that can be used to sign messages pub trait SigningKey: PublicKeyData { /// Signs `msg` using the selected algorithm fn sign(&self, msg: &[u8]) -> Result, Error>; } #[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) -> &'static SignatureAlgorithm { self.alg } } /// The public key data of a key pair pub trait PublicKeyData { /// The public key data in DER format /// /// The key is formatted according to the X.509 SubjectPublicKeyInfo struct. /// See [RFC 5280 section 4.1](https://tools.ietf.org/html/rfc5280#section-4.1). fn subject_public_key_info(&self) -> Vec { yasna::construct_der(|writer| serialize_public_key_der(self, writer)) } /// The public key in DER format fn der_bytes(&self) -> &[u8]; /// The algorithm used by the key pair fn algorithm(&self) -> &'static SignatureAlgorithm; } pub(crate) fn serialize_public_key_der(key: &(impl PublicKeyData + ?Sized), 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.subject_public_key_info(); 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.14.4/src/lib.rs000064400000000000000000000771531046102023000127300ustar 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, signing_key } = generate_simple_self_signed(subject_alt_names).unwrap(); println!("{}", cert.pem()); println!("{}", signing_key.serialize_pem()); # } ```"## )] #![forbid(unsafe_code)] #![forbid(non_ascii_idents)] #![deny(missing_docs)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![warn(unreachable_pub)] use std::borrow::Cow; 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 std::ops::Deref; #[cfg(feature = "pem")] use pem::Pem; use pki_types::CertificateDer; 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; use crate::string::{BmpString, Ia5String, PrintableString, TeletexString, UniversalString}; 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}; #[cfg(feature = "crypto")] pub use key_pair::KeyPair; pub use key_pair::PublicKeyData; #[cfg(all(feature = "crypto", feature = "aws_lc_rs"))] pub use key_pair::RsaKeySize; pub use key_pair::{SigningKey, SubjectPublicKeyInfo}; #[cfg(feature = "crypto")] use ring_like::digest; pub use sign_algo::algo::*; pub use sign_algo::SignatureAlgorithm; mod certificate; mod crl; mod csr; mod error; mod key_pair; mod oid; mod ring_like; mod sign_algo; pub mod string; /// 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. #[derive(PartialEq, Eq)] pub struct CertifiedKey { /// An issued certificate. pub cert: Certificate, /// The certificate's subject signing key. pub signing_key: S, } /** 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, signing_key } = 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!("{}", signing_key.serialize_pem()); # } ``` "## )] pub fn generate_simple_self_signed( subject_alt_names: impl Into>, ) -> Result, Error> { let signing_key = KeyPair::generate()?; let cert = CertificateParams::new(subject_alt_names)?.self_signed(&signing_key)?; Ok(CertifiedKey { cert, signing_key }) } /// An [`Issuer`] wrapper that also contains the issuer's [`Certificate`]. #[derive(Debug)] pub struct CertifiedIssuer<'a, S> { certificate: Certificate, issuer: Issuer<'a, S>, } impl<'a, S: SigningKey> CertifiedIssuer<'a, S> { /// Create a new issuer from the given parameters and key, with a self-signed certificate. pub fn self_signed(params: CertificateParams, signing_key: S) -> Result { Ok(Self { certificate: params.self_signed(&signing_key)?, issuer: Issuer::new(params, signing_key), }) } /// Create a new issuer from the given parameters and key, signed by the given `issuer`. pub fn signed_by( params: CertificateParams, signing_key: S, issuer: &Issuer<'_, impl SigningKey>, ) -> Result { Ok(Self { certificate: params.signed_by(&signing_key, issuer)?, issuer: Issuer::new(params, signing_key), }) } /// 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) } /// Get the certificate in DER encoded format. /// /// See also [`Certificate::der()`] pub fn der(&self) -> &CertificateDer<'static> { self.certificate.der() } } impl<'a, S> Deref for CertifiedIssuer<'a, S> { type Target = Issuer<'a, S>; fn deref(&self) -> &Self::Target { &self.issuer } } impl<'a, S> AsRef for CertifiedIssuer<'a, S> { fn as_ref(&self) -> &Certificate { &self.certificate } } /// An issuer that can sign certificates. /// /// Encapsulates the distinguished name, key identifier method, key usages and signing key /// of the issuing certificate. pub struct Issuer<'a, S> { distinguished_name: Cow<'a, DistinguishedName>, key_identifier_method: Cow<'a, KeyIdMethod>, key_usages: Cow<'a, [KeyUsagePurpose]>, signing_key: MaybeOwned<'a, S>, } impl<'a, S: SigningKey> Issuer<'a, S> { /// Create a new issuer from the given parameters and signing key. pub fn new(params: CertificateParams, signing_key: S) -> Self { Self { distinguished_name: Cow::Owned(params.distinguished_name), key_identifier_method: Cow::Owned(params.key_identifier_method), key_usages: Cow::Owned(params.key_usages), signing_key: MaybeOwned::Owned(signing_key), } } /// Create a new issuer from the given parameters and signing key references. /// /// Use [`Issuer::new`] instead if you want to obtain an [`Issuer`] that owns /// its parameters. pub fn from_params(params: &'a CertificateParams, signing_key: &'a S) -> Self { Self { distinguished_name: Cow::Borrowed(¶ms.distinguished_name), key_identifier_method: Cow::Borrowed(¶ms.key_identifier_method), key_usages: Cow::Borrowed(¶ms.key_usages), signing_key: MaybeOwned::Borrowed(signing_key), } } /// 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, signing_key: S) -> Result { let certificate = pem::parse(pem_str).map_err(|_| Error::CouldNotParseCertificate)?; Self::from_ca_cert_der(&certificate.contents().into(), signing_key) } /// Parses an existing CA certificate from the DER format. /// /// 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. /// /// If you already have a byte slice containing DER, it can trivially be converted into /// [`CertificateDer`] using the [`Into`] trait. #[cfg(feature = "x509-parser")] pub fn from_ca_cert_der(ca_cert: &CertificateDer<'_>, signing_key: S) -> Result { let (_remainder, x509) = x509_parser::parse_x509_certificate(ca_cert) .map_err(|_| Error::CouldNotParseCertificate)?; Ok(Self { key_usages: Cow::Owned(KeyUsagePurpose::from_x509(&x509)?), key_identifier_method: Cow::Owned(KeyIdMethod::from_x509(&x509)?), distinguished_name: Cow::Owned(DistinguishedName::from_name( &x509.tbs_certificate.subject, )?), signing_key: MaybeOwned::Owned(signing_key), }) } /// Allowed key usages for this issuer. pub fn key_usages(&self) -> &[KeyUsagePurpose] { &self.key_usages } /// Yield a reference to the signing key. pub fn key(&self) -> &S { &self.signing_key } } impl<'a, S> fmt::Debug for Issuer<'a, S> { /// Formats the issuer information without revealing the key pair. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // The key pair is omitted from the debug output as it contains secret information. let Issuer { distinguished_name, key_identifier_method, key_usages, signing_key: _, } = self; f.debug_struct("Issuer") .field("distinguished_name", distinguished_name) .field("key_identifier_method", key_identifier_method) .field("key_usages", key_usages) .field("signing_key", &"[elided]") .finish() } } enum MaybeOwned<'a, T> { Owned(T), Borrowed(&'a T), } impl Deref for MaybeOwned<'_, T> { type Target = T; fn deref(&self) -> &Self::Target { match self { MaybeOwned::Owned(t) => t, MaybeOwned::Borrowed(t) => t, } } } // 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)), } impl SanType { #[cfg(all(test, feature = "x509-parser"))] fn from_x509(x509: &x509_parser::certificate::X509Certificate<'_>) -> Result, Error> { let sans = x509 .subject_alternative_name() .map_err(|_| Error::CouldNotParseCertificate)? .map(|ext| &ext.value.general_names); let Some(sans) = sans else { return Ok(Vec::new()); }; let mut subject_alt_names = Vec::with_capacity(sans.len()); for san in sans { subject_alt_names.push(Self::try_from_general(san)?); } Ok(subject_alt_names) } } /// 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 */ #[derive(Clone, Debug)] 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 { #[cfg(feature = "x509-parser")] fn from_x509(x509: &x509_parser::certificate::X509Certificate<'_>) -> Result, Error> { let key_usage = x509 .key_usage() .map_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(Self::from_u16(flags)) } /// 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 { #[cfg(feature = "x509-parser")] fn from_x509(x509: &x509_parser::certificate::X509Certificate<'_>) -> Result { 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, }); Ok(match key_identifier_method { Some(method) => method, None => { #[cfg(not(feature = "crypto"))] return Err(Error::UnsupportedSignatureAlgorithm); #[cfg(feature = "crypto")] KeyIdMethod::Sha256 }, }) } /// 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 { #[cfg_attr(not(feature = "crypto"), expect(clippy::let_unit_value))] 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, } #[allow(clippy::len_without_is_empty)] 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!("{b:02x}")).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, "Algorithm relationship mismatch for algorithm index pair {i} and {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.14.4/src/oid.rs000064400000000000000000000107511046102023000127240ustar 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]; #[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))] pub(crate) const ML_DSA_44: &[u64] = &[2, 16, 840, 1, 101, 3, 4, 3, 17]; #[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))] pub(crate) const ML_DSA_65: &[u64] = &[2, 16, 840, 1, 101, 3, 4, 3, 18]; #[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))] pub(crate) const ML_DSA_87: &[u64] = &[2, 16, 840, 1, 101, 3, 4, 3, 19]; /// 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.14.4/src/ring_like.rs000064400000000000000000000027001046102023000141070ustar 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")] { 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 { 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.14.4/src/sign_algo.rs000064400000000000000000000261641046102023000141200ustar 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(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))] use aws_lc_rs::unstable::signature::{ PqdsaSigningAlgorithm, ML_DSA_44_SIGNING, ML_DSA_65_SIGNING, ML_DSA_87_SIGNING, }; #[cfg(feature = "crypto")] #[derive(Clone, Copy, Debug)] pub(crate) enum SignAlgo { EcDsa(&'static EcdsaSigningAlgorithm), EdDsa(&'static EdDSAParameters), #[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))] PqDsa(&'static PqdsaSigningAlgorithm), Rsa(&'static dyn RsaEncoding), } #[derive(Clone, Copy, Debug, 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 #[derive(Clone)] 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-384 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, }; /// ML-DSA-44 signing as per . #[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))] pub static PKCS_ML_DSA_44: SignatureAlgorithm = SignatureAlgorithm { oids_sign_alg: &[ML_DSA_44], #[cfg(all(feature = "crypto", feature = "aws_lc_rs_unstable"))] sign_alg: SignAlgo::PqDsa(&ML_DSA_44_SIGNING), oid_components: ML_DSA_44, params: SignatureAlgorithmParams::None, }; /// ML-DSA-44 signing as per . #[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))] pub static PKCS_ML_DSA_65: SignatureAlgorithm = SignatureAlgorithm { oids_sign_alg: &[ML_DSA_65], #[cfg(all(feature = "crypto", feature = "aws_lc_rs_unstable"))] sign_alg: SignAlgo::PqDsa(&ML_DSA_65_SIGNING), oid_components: ML_DSA_65, params: SignatureAlgorithmParams::None, }; /// ML-DSA-44 signing as per . #[cfg(all(feature = "aws_lc_rs_unstable", not(feature = "fips")))] pub static PKCS_ML_DSA_87: SignatureAlgorithm = SignatureAlgorithm { oids_sign_alg: &[ML_DSA_87], #[cfg(all(feature = "crypto", feature = "aws_lc_rs_unstable"))] sign_alg: SignAlgo::PqDsa(&ML_DSA_87_SIGNING), oid_components: ML_DSA_87, 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.14.4/src/string.rs000064400000000000000000000440261046102023000134610ustar 00000000000000//! ASN.1 string types use 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::string::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::string::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::string::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::string::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::string::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::string::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::string::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); } }