Rework Certificate issuance API, make DER/PEM serialization stable#205
Merged
Conversation
djc
approved these changes
Jan 11, 2024
djc
left a comment
Member
There was a problem hiding this comment.
I think this makes the API much better, nice!
796b5d7 to
f55ac1c
Compare
djc
approved these changes
Jan 12, 2024
The `Certificate` type as it exists at this commit has no signature, and it isn't self-signed. It _may_ be turned into a self signed certificate by calling `serialize_der()` or `serialize_pem()`, or it may be turned into a certificate signed by an issuer! Simplify the comment before more substantial refactoring.
This commit associates deriving a key identifier with the `KeyIdMethod` enum. This will make it easier to calculate a key identifier with only the public key in hand as opposed to the full parameters. Along the way the rustdocs associated with `KeyIdMethod` are tuned up for clarity/specificity.
Rather than have the `write_x509_authority_key_identifier` fn take a `Certificate` as input in order to calculate the AKI value, have the caller find the AKI and pass it to the write fn for writing. This simplifies generating a certificate with the issuer's `KeyPair`, and not a `Certificate`.
This commit updates the various `serialize_*` fns to take more targeted inputs as opposed to a `Certificate`. This will make it easier to perform subsequent refactors.
This will make it easier to have more than one callsite that performs on-demand key generation.
This commit removes `Certificate::from_params`, replacing it with `Certificate::generate_self_signed`. The `serialize_x` fns are replaced with `pem()` and `der()` accessors. To issue a certificate signed by an issuer, a new `Certificate::generate` constructor can be used. Unit tests, and the rustls-cert-gen utility, are updated accordingly.
In preparation for removing the `KeyPair` from `Certificate` we need to hold on to the subject public key info for use when generating key identifiers.
Rather than store `KeyPair` in `Certificate`, take `KeyPair` as a parameter when required for signing and return `KeyPair` from the generate methods as a new `CertifiedKey` type. This will allow unifying the API around the idea that a `Certificate` is public material. Tests and the cert gen utility are updated accordingly.
This commit updates the rustdoc comments on `Certificate::serialize_request_der` and `Certificate::serialize_request_pem`, emphasizing that they generate and sign CSRs for each invocation. The purpose of the `subject_key` argument is emphasized. Along the way, fix an inaccurate comment in `CertificateParams::write_request` - we're writing the _subject_ distinguished name, not the issuer. The `Name` field in the syntax in RFC 2986 is `subject`.
This unifies the creation of `Certificate`s to the `Certificate` constructors instead of being spread across both `Certificate` and `CertificateSigningRequest`.
This rename better emphasizes the role the CSR type plays, and aligns it with other `*Params` types that already exist in the API surface.
A quick pass through the rustdoc strings, adding more detail and clarifying text where appropriate.
f55ac1c to
f3daf4b
Compare
Member
|
I also really like the I'm also thinking about a |
Merged
github-merge-queue Bot
pushed a commit
that referenced
this pull request
Jan 23, 2024
As an alternative to #212, this goes further in the direction of #205, removing `alg` and `key_pair` from `CertificateParams` and requiring the caller to pass in a reference to them when signing. This seems to have a number of nice properties: * `alg` is derived from the passed in `KeyPair`, so key algorithm mismatch errors can no longer occur * No need for passing in a signing algorithm when parsing from pre-existing parameters or key pairs * Should make it easy to support long-lived (remote) key pairs The main downside as far as I can see is that the top-level API gets a bit more complicated, because generating a `KeyPair` must now be done by the caller, and for now we force them to pick a signing algorithm. I think we might mitigate this by adding a no-argument constructor like `generate_default()` (or use `generate()` for the no-argument variant and `generate_for()` for the variant that requires an argument). Generally, this feels like a clear improvement in the API's design to me.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Previously a
Certificatewas a container forCertificateParamsand aKeyPair, most commonly created from aCertificateParamsinstance. Serializing theCertificate(either as self signed withserialize_pemorserialize_der, or signed by an issuer withserialize_pem_with_signerorserialize_der_with_signer) would issue a certificate and produce the serialized form in one operation. The net result is that if a user wanted both DER and PEM serializations they would likely callserialize_der(_with_signer)and thenserialize_pem(_with_signer)and mistakenly end up with the encoding of two distinct certificates, not the PEM and DER encoding of the same cert. Since theKeyPaircontains a private key this API design also meant that theCertificatetype had to be handled with care, andZeroized.This branch reworks the issuance API and
Certificatetype to better match user expectation:Certificateis only public material and represents an issued certificate that can be serialized in a stable manner in DER or PEM encoding.I recommend reviewing this commit-by-commit, but here is a summary of the most notable API changes:
Certificate::from_paramsandCertificate::serialize_derandCertificate::serialize_pemfor issuing a self-signed certificate are replaced withCertificate::generate_self_signed()and callingderorpemon the result.Certificate::from_paramsandCertificate::serialize_der_with_signerandCertificate::serialize_pem_with_signerfor issuing a certificate signed by another certificate are replaced withCertificate::generate()and callingderorpemon the result.CertificateSigningRequest::serialize_der_with_signerandCertificateSigningRequest::serialize_pem_with_signerfor issuing a certificate from a CSR are replaced withCertificate::from_requestand callingderorpemon the result. TheCertificateSigningRequesttype is renamed toCertificateSigningRequestParamsto better emphasize its role and match the other*Paramstypes that already exist.Certificateconstruction time, thepemandderfns are now infallible.Certificateno longer holdsKeyPair, thegeneratefns now expect a&KeyPairargument for the signer when issuing a certificate signed by another certificate.CertifiedKeythat contains both aCertificateand aKeyPair. For params that specify a compatibleKeyPairit is passed through in theCertifiedKeyas-is. For params without aKeyPaira newly generatedKeyPairis used.In the future we should look at harmonizing the creation of
CertificateSigningRequestandCertificateRevocationListto better match this updated API. Unfortunately I don't have time to handle that at the moment. Since this API surface is relatively niche compared to theCertificateissuance flow it felt valuable to resolve #62 without blocking on this future work.Resolves #62