CSR with PublicKey only
Usecase:
- Client generates keypair
- Sends public key to server (in form of CSR, or CSR is generated on server itself)
- Server signs him a client cert with CA existing only on server
- Returns this cert back to client
Mb I'm missing something, but didn't find a way to do it. The only possible flow now is:
- Server generates both private and public key for client (
KeyPair::generate) - Signs a cert with existing CA
- Returns both cert and private key to a client.
If I understand correctly, a CSR must be signed by the subject's key, which requires the private key.
@djc Yes, for sure.
Actually i've found the possible solution: it's possible to construct rcgen::CertificateSigningRequestParams with only params and public key, and then use it to sign a client cert by CertificateSigningRequestParams::signed_by
Maybe it's worth adding this to the examples.
Hmmm I wonder whether we could make CertificateParams::signed_by take K: PublicKeyData instead? The private key is never used.
Huh, yeah -- I totally missed that, sorry.
Let me start by sketching out the workflow I'd expect to see here.
Client side:
- Client generates key pair w/
KeyPair::generate()or another fn as appropriate. - Client generates
rcgen::CertificateParams, customizing as required to describe attributes of the certificate it desires. - Client generates
rcgen::CertificateSigningRequestfrom thercgen::CertificateParamsby callingrcgen::CertificateParams::serialize_request(subject_key)wheresubject_keyis the public half of the client'sKeyPair. - Client serializes the
rcgen::CertificateSigningRequestto PEM or DER w/rcgen::CertificateSigningRequest::pem()/der()and transmits this to the CA over a trusted channel.
Server side:
- Server receives PEM/DER CSR from a trusted client.
- Server creates
rcgen::CertificateSigningRequestParamsusingCertificateSigningRequestParams::from_pem()|from_der(), providing the CSR data. Importantly this verifies the client's signature over the CSR and proves control of the private key corresponding to the public key that is the CSR subject. - Server verifies the
CertificateSigningRequestParams'sCertificateParamsmeets its profile for the client. E.g. what things is the client allow to specify be included in the generated cert? You must be very careful here or a client could request a cert withis_catrue as one example... - Server issues a certificate for the
CertificateSigningRequestParamsby callingCertificateSigningRequestParams::signed_by, providing the CACertificateandKeyPairof the server. - Server serializes the
Certificateit generated from the CSR to PEM/DER and sends it back to the client.
Maybe it's worth adding this to the examples.
Absolutely! I think discovering the above workflow from docs alone would be challenging.
Hmmm I wonder whether we could make CertificateParams::signed_by take K: PublicKeyData instead? The private key is never used.
I chatted about this with djc. I think the general idea of replacing the KeyPair argument in CertificateParams::signed_by makes sense to me but I think we want a new trait. PublicKeyData would bring yasna into the API.
This is actually pretty straightforward, we can just extract the serialize_public_key_der() method from the trait:
https://github.com/rustls/rcgen/compare/public-key?expand=1
I think that leaves some bikeshedding on the shape of the public trait.
- The trait's name: I guess
PublicKeyDatais okay? We already have a trivial implementation incsr::PublicKey. - The
alg()method: maybe expand this toalgorithm()? - The
raw_bytes()method: maybe rename this toder_bytes()?rawby itself feels a little vague/opaque.
Server verifies the CertificateSigningRequestParams's CertificateParams meets its profile for the client. E.g. what things is the client allow to specify be included in the generated cert? You must be very careful here or a client could request a cert with is_ca true as one example... ... I think the general idea of replacing the KeyPair argument in CertificateParams::signed_by makes sense to me
I think this change is important because it would allow the safest workflow and what I would do in the OP's shoes:
- Server receives PEM/DER CSR from a trusted client.
- Server creates
rcgen::CertificateSigningRequestParamsusingCertificateSigningRequestParams::from_pem()|from_der(), providing the CSR data. - Server creates its own
CertificateParamsfrom scratch based on its profile for the client, perhaps lifting one or two blessed/validated fields (like SANs) out of theCertificateSigningRequestParams'sCertificateParams. - Server uses
CertificateParams::signed_byon the params it generated, providing thePublicKeyfrom theCertificateSigningRequestParamsand its own issuerCertificateandKeyPair - Server serializes the
Certificateit generated from the CSR to PEM/DER and sends it back to the client.
This avoids the client being able to specify all sorts of stuff in its CSR parameters you might not want to allow and is a closer match to how a system like Let's Encrypt's CA software is implemented.
The trait's name: I guess PublicKeyData is okay? We already have a trivial implementation in csr::PublicKey.
Seems OK to me.
The alg() method: maybe expand this to algorithm()?
Agreed.
The raw_bytes() method: maybe rename this to der_bytes()? raw by itself feels a little vague/opaque.
Agreed :+1:
Thanks for running with this!
@Virviil Would you be interested in contributing an example based on some of the discussion above? If not I will try to do it myself but it may be on a longer timescale :-)
Server verifies the CertificateSigningRequestParams's CertificateParams meets its profile for the client. E.g. what things is the client allow to specify be included in the generated cert? You must be very careful here or a client could request a cert with is_ca true as one example...
...
I think the general idea of replacing the KeyPair argument in CertificateParams::signed_by makes sense to me
I think this change is important because it would allow the safest workflow and what I would do in the OP's shoes:
Server receives PEM/DER CSR from a trusted client.
Server creates
rcgen::CertificateSigningRequestParamsusingCertificateSigningRequestParams::from_pem()|from_der(), providing the CSR data.Server creates its own
CertificateParamsfrom scratch based on its profile for the client, perhaps lifting one or two blessed/validated fields (like SANs) out of theCertificateSigningRequestParams'sCertificateParams.Server uses
CertificateParams::signed_byon the params it generated, providing thePublicKeyfrom theCertificateSigningRequestParamsand its own issuerCertificateandKeyPairServer serializes the
Certificateit generated from the CSR to PEM/DER and sends it back to the client.This avoids the client being able to specify all sorts of stuff in its CSR parameters you might not want to allow and is a closer match to how a system like Let's Encrypt's CA software is implemented.
Yep, this is basically my workflow expectation.
While constructing new cert from CSR is possible to implement, I don't want neither to send any client specific attributes of the cert (via CSR) nor leverage on CSR verification mechanism.
I'm using another auth mechanism (let's say oauth) to ensure that client is trustful, and all the client cert attributes are generated on the server (or CA) side.
So I would prefer to pass only public key with my api request, and return only a cert.
The trait's name: I guess PublicKeyData is okay? We already have a trivial implementation in csr::PublicKey.
Seems OK to me.
The alg() method: maybe expand this to algorithm()?
Agreed.
The raw_bytes() method: maybe rename this to der_bytes()? raw by itself feels a little vague/opaque.
Agreed :+1:
Thanks for running with this!
@Virviil Would you be interested in contributing an example based on some of the discussion above? If not I will try to do it myself but it may be on a longer timescale :-)
Yep, I'll try to add the most straightforward example: just accepting CSR as is and produce a cert. While it's not save - it covers the most important library apis
Hmmm I wonder whether we could make
CertificateParams::signed_bytakeK: PublicKeyDatainstead? The private key is never used.
I made a branch that does this and adds a SubjectPublicKey type that impls PublicKeyData and can be parsed from PEM or DER. Public key values output by both rcgen and the openssl CLI are serialized SubjectPublicKeyInfo structs, hence the name of the type. Parsing uses the x509_parser crate, so this type is available just when the x509-parser feature is enabled.
https://github.com/kwantam/rcgen/tree/rsw/cert-from-pk-only
Side note: it is vaguely annoying to go from x509_parser::x509::AlgorithmIdentifier to rcgen::SignatureAlgorithm. Some changes to the latter type would help here---for example, maybe it could be modified to wrap an AlgorithmIdentifier<'static> (that might take some thinking, but this crate already indirectly depends on lazy_static so there's an obvious path in any case). That would make the x509_parser dep non-optional but might let you get rid of the entire oid module in favor of the oid_registry crate, which would anyway be an indirect dep via x509_parser.
That would make the x509_parser dep non-optional but might let you get rid of the entire oid module in favor of the oid_registry crate, which would anyway be an indirect dep via x509_parser.
Making x509-parser non-optional doesn't sound attractive.
Do you want to submit a PR?
Sure, I'll open it now but I'm completely buried for the next few days so I may not be responsive to review comments immediately.
EDIT: #288