Open sebastien-baillet opened 4 years ago
Hey @sebastien-baillet, thanks for opening the issue!
This is a great point, although I would not necessarily call it a "bug" (I won't relabel for now though).
Yes, you can create a leaf or subCA that has a longer validity window than the signing certificate. However, and this is key, that certificate will not be valid once the signing certificate has expired. As far as I know, most tools will allow you to create invalid certificates, and then it's on the validation/verification middlewares to throw an exception.
Now, having said that, It would certainly be nice to catch this at signing time so users don't shoot themselves in the foot. Just not sure what the expected behavior should be:
1) Just defaulting the lifetime of the leaf / subCA to be the remaining life time of the signing cert. E.g. If there's 10 days left of validity on intermediate.crt and I try to create foo.crt with a default validity of 30 days then this would transparently set the validity of foo.crt to 10 days. 2) Only transparently change the cert validity if the user did not request a validity for the new cert, otherwise fail. E.g. if I've explicitly requested 30 days but the signing cert is only valid for another 10 days, then throw an exception. 3) Throw an error if the default / requested validity for the new cert exceeds the signing certs validity.
Does it ever make sense to create a certificate whose lifetime is longer than the signing CA?
- Just defaulting the lifetime of the leaf / subCA to be the remaining life time of the signing cert. [...]
With ACME clients, this would result in daily renewals of shorter and shorter living certificates.
- Only transparently change the cert validity if the user did not request a validity for the new cert, [...]
At least with lego, we don't provide a way to specify the certificates NotAfter value. Not sure if this is even possible with the ACME protocol. However, if the issued certificate is only valid for 10 days, lego will happily accept it.
- Throw an error if the default / requested validity for the new cert exceeds the signing certs validity.
I'd go with this. In https://github.com/go-acme/lego/issues/1263#issuecomment-702566775 I'm arguing whether lego should fail and lay the blame onto the CA.
Does it ever make sense to create a certificate whose lifetime is longer than the signing CA?
Only if you want to force your users to skip certificate verification...
After some thought, I don't think something should be done client side, as lego is not the only ACME client we're using at the moment.
For now, I'm trying to find a way to track such case in my environment. In the step-ca database, is it possible to find out which certification authority has issued which certificate, apart from decoding the certificate leaf?
For the context: my step-ca instances are using mysql Cloud SQL instance, and I export some data to a BigQuery Data (domain name, contact, notBefore, notAfter...) with a data transfer (pure SQL); maybe I can add at least the CN of the subCA, if the information is easily available.
In the step-ca database, is it possible to find out which certification authority has issued which certificate, apart from decoding the certificate leaf?
Someone?
@sebastien-baillet I'm not sure I understand the question. It sounds like you're exporting issued certificates from the step-ca
database and would like to determine which intermediate issued each certificate? The most obvious way I can think of to do that is to use step certificate inspect
in a script and extract either the issuer common name or the issuer key id:
$ step certificate inspect .step/identity/identity.crt --format json | jq -r .issuer.common_name[0]
beta Intermediate CA
$ step certificate inspect .step/identity/identity.crt --format json | jq -r .extensions.authority_key_id
2c4a48d2d528b80ed5d71a0809d88ea347eca2c2
I don't think there's any way to get this information without decoding the PEM-encoded certificates in the database.
Returning to the original topic, handling expiry issues gracefully is pretty tricky. We need to balance two issues:
These are sort of conflicting requirements. The easiest way to make people aware that what they're doing is probably wrong is to refuse to do the thing they asked and return a useful error. But that seems super dangerous here: your CA may be working totally normally, then all of a sudden refuse to issue certificates at 2am on a Friday night. That said... I suppose it doesn't help much if we issue certificates that aren't gonna be trusted by relying parties...
We could very easily log this information, but I suspect pretty much no one will see these logs until it's too late.
Transparently reducing the lifetime of issued certificates makes some sense... but I don't think it fixes anything. If your intermediate is approaching expiry, what you probably actually want is a new intermediate.
There are (at least) two scenarios to deal with:
For the first scenario, one solution might be to have step-ca
refuse to start if intermediate lifetime < max leaf certificate lifetime. Unfortunately, I don't think there's any way for us to distinguish between these two scenarios (we could look at the intermediate certificate's issued at date, but that seems error-prone). So if we refuse to start for the first scenario, we run the risk of refusing to start on some production CA at an inopportune time and taking down someone's system.
Perhaps we could log loudly at step-ca
startup. Again, I'm worried people won't notice. Perhaps we could do something clever like delaying CA startup, which you're more likely to notice (e.g., we could wait 5 minutes before the CA starts listening).
Another thought: to avoid scenario #2 we could have step-ca
issue itself a new intermediate certificate if it needs one. The downside there is that we'd need access to $(step path)/secrets/root_ca_key
, which step-ca
doesn't currently use.
I'm still not sure what the right solution is here. I'm gonna keep noodling on it.
@sebastien-baillet I'm not sure I understand the question. It sounds like you're exporting issued certificates from the
step-ca
database and would like to determine which intermediate issued each certificate? The most obvious way I can think of to do that is to usestep certificate inspect
in a script and extract either the issuer common name or the issuer key id:$ step certificate inspect .step/identity/identity.crt --format json | jq -r .issuer.common_name[0] beta Intermediate CA $ step certificate inspect .step/identity/identity.crt --format json | jq -r .extensions.authority_key_id 2c4a48d2d528b80ed5d71a0809d88ea347eca2c2
I don't think there's any way to get this information without decoding the PEM-encoded certificates in the database.
It seems you understand my question, thx for your answer :+1:
At the moment, as I can't find anything for such case in RFC, I'm trying to find how other CA tools handle this point. I've found in EJBCA documentation: "There are some constraints for the validity of a certificate issued by the CA::
I'll look into the code to find if it's a blocking control, or if the notAfter is transparently reduced.
At the same time, I opened a ticket with the publisher of a software we use to find out how he handles this kind of case. I'll post here as soon as I have an answer.
I have my answer: in such a case, the commercial CA tool we're using transparently reduces the validity end date of the issued certificate so that it does not exceed the validity end date of the issuing subCA, without any notice for the operator. (This tool does not handle ACME protocol)
@sebastien-baillet That's super helpful. Thanks so much for doing this research. I'm now convinced that we should reduce the lifetime transparently so that the notAfter
of a leaf is <= min(notAfter for each certificate in the certificate path)
. This will get more complicated if we ever support more complex paths, but for now it'll be easy: just look at the min of the notAfter
for the root & intermediate.
Let's also warn the operator about what's happened in the step-ca
logs. I think it also makes sense to warn during step-ca
startup if the root or intermediate certificate's remaining lifetime is less than the maxTLSCertDuration
for any provisioner. It'd be nice if we could log a warning on the client side as well if the notAfter
is truncated. In the future perhaps we'll have other ways of alerting.
Technically this shouldn't change the behavior of the system at all: the issued certificates will be valid for precisely the same amount of time either way. That actually seems to be an argument in favor of making this change. This change shouldn't break anything and, all else equal, it seems better if the leaf certificate accurately reflects its effective expiry.
I doubt any relying parties would make the mistake of trusting a certificate after its intermediate has expired (I certainly hope they wouldn't... if they did it'd be a bug). So I don't think this change will break anything that wouldn't have broken due to the intermediate expiry.
That said, I could definitely see observability and automation tools overlooking the intermediate expiry (e.g., if you just inspect
a certificate and extract its notAfter
in order to determine whether it should be renewed you may miss the fact that the effective expiry is earlier because the intermediate has a shorter lifetime). In fact, I bet our step ca renew --daemon
tool makes this mistake. And I bet step-ca
will refuse to renew a certificate after intermediate expiry because step-ca
does full certificate path validation on renewal. So this change will probably fix some subtle defects in our current system.
Weirdly, I did a quick search and I can't find any guidance in RFC5280 or CA/Browser Forum baseline requirements on this topic. I'm not sure if there are any valid use cases for a leaf certificate to expire after its root or intermediate. I wonder if @rmhrisk has any thoughts on this? Do Google Trust Services or GCP CAS allow this?
To summarize, let's make the following changes:
step-ca
should transparently reduce the notAfter
of any issued certificates to min(requested-notAfter, intermediate-notAfter, root-notAfter)
notAfter
is reduced, step-ca
should log a warning: "Requested certificate expiration was reduced from <requested-notAfter>
to <effective-notAfter>
because leaf certificates cannot expire after [intermediate|root] certificate. You may need to issue a new [intermediate|root] with an extended lifetime."now() + maxTLSCertDuration
for any provisioner is >= <intermediate-notAfter>
or >= <root-notAfter>
we should log a warning: "Configured maxTLSCertDuration
for provisioner <provisioner-name>
allows leaf certificate expirations after [intermediate|root] certificate expiration. Leaf certificate cannot outlive intermediate or root certificates. Certificate expirations will be automatically reduced to <effective-notAfter>
. You may need to issue a new [intermediate|root] with an extended lifetime."step
), if the actual notAfter
of the issued certificate doesn't match the requested notAfter
, log a warning: "Issued certificate lifetime does not match requested lifetime. notAfter
has been reduced to <notAfter>
. Your CA may need a new [intermediate|root] with an extended lifetime."At some point we're gonna need to document the workflow to generate a new intermediate. When that's done, it'd probably be good to reference that documentation from these warnings.
If we're being complete, we should probably also not allow step certificate create
to generate an intermediate with an expiry greater than the root's (or any parent intermediates).
@sebastien-baillet will these changes satisfy your requirements?
@sebastien-baillet will these changes satisfy your requirements?
That is perfect
Certificate expiration date after subCA expiration date
step-ca issue certificates with expiration date later than the expiration date of the subCA issuing it.
Your environment
Steps to reproduce
Create a subCA (we didn't test with a root CA) with a short expiration date. Configure the subCA as an ACME provisioner, with a maxTLSCertDuration that will allow the provisioner to issue certificate with a expiration date later than the expiration date of the subCA. Request such a certificate. Check the leaf certificate expiration date.
Expected behaviour
step-ca should not issue certificates with expiration date later than the expiration date of the subCA issuing it. The expiration date of certificates should be limited by the expiration date of the certificate of the intermediate CA.
Actual behaviour
step-ca issue certificates with expiration date later than the expiration date of the subCA issuing it.
Additional context
We use only an ACME provisioner. We had the problem with at least Caddy and lego as ACME client.