-
Notifications
You must be signed in to change notification settings - Fork 43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CRL should (probably) not include the CRL distribution point URL #64
Comments
I had a look at RFC 5280. According to the spec, it is fine to have in the CRL an Issuing Distribution Point that includes CRL URLs. Moreover, I quickly compared side-by-side the RFC and the django_ca implementation at |
I was able to reproduce the error message you described. However I was able to verify the CRL like this - this CRL includes the Distribution Point:
Not that this solves the issue, but at least it's not entirely wrong ;-). |
Update: Tested this with a second set of CA/CRL/Cert to - seems like
In this set of certs I used a localhost CRL to make sure I'd see any connection on the dev server, and there is no connection to it either. |
I think the reason for the problem may be this: https://trac.nginx.org/nginx/ticket/1094#comment:6 (meaning btw that Nginx client auth would also be affected).
It looks like the presence of CRL distribution points (DP) in the CRL and in the certificate of the CA that issues the CRL must be consistent. In the example I gave in the bug description, the CA does not show the CRL DP in its own certificate, but the CRL does. If this is correct, I assume that CRLs of root CAs should not have CRL DP. The alternative would be to have the CRL DP also in the root CA certificate, but this is not common practice. |
Hmm. I don't think I fully understood this yet. This makes sense:
I tested this with the CA/CRL/Cert I generated for this comment, and:
But I do not understand what you mean by this:
If that is correct, wouldn't this mean that the same is true for intermediate CAs? The intermediate CAs CRL DP provides information on CAs revoked by the root CA (the issuer). So it's not the same URL in the CAs DP is not the same in the certificates signed by the intermediate CA. Basically:
The two CRLs would then have the following IDPs:
So that means verification of the end-user cert (only with regards to CRLs) would mean that CRLs are still not consistent. :-(
In that, in any case, you appear to be correct: https://django-ca.readthedocs.io/en/latest/ca_examples.html#id12 - but I'd guess this is also because public CAs usually never sign a client certificiate. |
I guess it's not encouraging that that the Estionian CA still has not changed their setup at all. |
I have now created a more test cases to see how OpenSSL handles CRLs and DP/IDP and if I can make any sense of it related to RFC 5280. It seems the issue comes down to - as the headline suggests - the Note that I'm limiting this analysis to validating the Root CA itself. I do not generate any client certs or intermediate CAs. Current behaviourFirst, I created a Root CA and CRL as-is with the current code, meaning no CRLDistributionPoint in Root CA and created a CRL for it. This is basically just reproducing your initial report. $ ./manage.py init_ca --pathlen=2 Root /CN=Root
$ ./manage.py dump_ca Root > Root.pem
$ ./manage.py dump_crl -f PEM --ca Root Root.crl.pem Now, let's try some validation: # first, no CRL checks at all:
$ openssl verify -CAfile Root.pem Root.pem
Root.pem: OK
# Even just -crl_check fails, not just -crl_check_all:
$ openssl verify -CAfile Root.pem -CRLfile Root.crl.pem -crl_check Root.pem
CN = Root
error 44 at 0 depth lookup: Different CRL scope
error Root.pem: verification failed
$ openssl verify -CAfile Root.pem -CRLfile Root.crl.pem -crl_check_all Root.pem
CN = Root
error 44 at 0 depth lookup: Different CRL scope
error Root.pem: verification failed This matches your intial report, the only thing (a little bit) new is the simplification of just validating the root CA itself, so now just Remove IDP from CRLVia a manual source code edit, I ensured that the CRL does not contain an IDP. So CA: No cRLDP, CRL: No IDP. $ ./manage.py dump_crl -f PEM --ca Root Root-no-idp.crl.pem # manual source code edit to ensure no IDP in CRL
$ openssl verify -CAfile Root.pem -CRLfile Root-no-idp.crl.pem -crl_check Root.pem
Root.pem: OK
$ openssl verify -CAfile Root.pem -CRLfile Root-no-idp.crl.pem -crl_check_all Root.pem
Root.pem: OK So it seems that the IDP extension (in its current form) in combination with no cRLDP indeed breaks validation. CA with cRLDPA theory above was: "The alternative would be to have the CRL DP also in the root CA certificate, but this is not common practice.", and the nginx issue says that the CRL has an IDP but the Root CA is missing a CRL DP. So lets test this theory. So I tried to create a CA with - as you and the linked issue for NGINX suggested - a DP in the root CA. Again, I made sure of this via a manual source code edit. $ ./manage.py init_ca WithCRLDP /CN=WithCRLDP
$ ./manage.py dump_ca WithCRLDP > WithCRLDP.pem
$ openssl x509 -noout -text -in WithCRLDP.pem
...
X509v3 CRL Distribution Points:
Full Name:
URI:http://localhost:8000/django_ca/crl/ca/56AD2C5809D194D32CFBC6604D19E78D96126073/
...
$ ./manage.py dump_crl -f PEM --ca WithCRLDP > WithCRLDP.crl.pem
$ openssl crl -in WithCRLDP.crl.pem -noout -text
...
CRL extensions:
X509v3 Issuing Distribution Point: critical
Full Name:
URI:http://localhost:8000/django_ca/crl/56AD2C5809D194D32CFBC6604D19E78D96126073/ ... So now the Root CA and the CRL have a matching DP. While I consider a CRL DP in a Root CA nonsensical, still: If the theory is correct, validation should work now... $ openssl verify -CAfile WithCRLDP.pem -CRLfile WithCRLDP.crl.pem -crl_check WithCRLDP.pem
CN = WithCRLDP
error 44 at 0 depth lookup: Different CRL scope
error WithCRLDP.pem: verification failed ... so simply adding a CRL DP is not a solution, even if it would make sense. Intermediate CAs & enduser certificatesNext, I wanted to test if removing the IDP from CRLs for the Root CA solves the issue also for intermediate CAs and enduser certificates. (Note that CRLs for child CAs again include an IDP!) # Create a Child and grandchild CA and a signed certificate
$ ./manage.py init_ca --pathlen=1 --parent=Root Child /CN=Child
$ ./manage.py dump_ca Child > Child.pem
$ ./manage.py init_ca --parent=Child Grandchild /CN=Grandchild
$ ./manage.py dump_ca Grandchild > Grandchild.pem
# Create two enduser certs, one for Child, one for Grandchild
$ ./manage.py sign_cert --subject /CN=example.com --cn-in-san --csr hostname.csr --out child-cert.pem --ca Child
$ ./manage.py sign_cert --subject /CN=example.com --cn-in-san --csr hostname.csr --out grandchild-cert.pem --ca Grandchild
# Create CRLs for Child and Grandchild
$ ./manage.py dump_crl --ca Child > Child.crl.pem
$ ./manage.py dump_crl --ca Grandchild > Grandchild.crl.pem So, finally, let's try to validate the Child CA and the cert signed by it. $ openssl verify -CAfile Root.pem -CRLfile Root-no-idp.crl.pem -crl_check_all Child.pem
Child.pem: OK
$ openssl verify -CAfile Root.pem -CRLfile Root-no-idp.crl.pem -untrusted Child.pem -CRLfile Child.crl.pem -crl_check_all child-cert.pem
child-cert.pem: OK Looks promising, but now let's look at the Grandchild CA: $ openssl verify -CAfile Root.pem -CRLfile Root-no-idp.crl.pem \
> -untrusted Child.pem -CRLfile Child.crl.pem \
> -untrusted Grandchild.pem -CRLfile Grandchild.pem \
> -crl_check_all Grandchild.pem
CN = Grandchild
error 44 at 0 depth lookup: Different CRL scope
error Grandchild.pem: verification failed
$ openssl verify -CAfile Root.pem -CRLfile Root-no-idp.crl.pem \
> -untrusted Child.pem -CRLfile Child.crl.pem \
> -untrusted Grandchild.pem -CRLfile Grandchild.crl.pem \
> -crl_check_all grandchild-cert.pem
CN = Grandchild
error 44 at 1 depth lookup: Different CRL scope
error grandchild-cert.pem: verification failed ... sigh,
Alright, as we can see here, CAs contain a
For the Grandchild case, the first two steps in validation are identical, but after that:
Intermediate CAs & enduser certificates - fixed DPTo test above theory, I again modified the source to generate CAs without the $ ./manage.py init_ca --pathlen=1 --parent=Root ChildFixedDP /CN=ChildFixedDP
$ ./manage.py init_ca --parent=ChildFixedDP GrandchildFixedDP /CN=GrandchildFixedDP
$ ./manage.py dump_ca ChildFixedDP > ChildFixedDP.pem
$ ./manage.py dump_ca GrandchildFixedDP > GrandchildFixedDP.pem
# Notice how the /ca is now gone from the path
$ openssl x509 -noout -text -in ChildFixedDP.pem | grep django_ca/crl
URI:http://localhost:8000/django_ca/crl/0F30C839/
$ openssl x509 -noout -text -in GrandchildFixedDP.pem | grep django_ca/crl
URI:http://localhost:8000/django_ca/crl/1648A015/ Next, we create CRLs and certs again: $ ./manage.py dump_crl --ca ChildFixedDP > ChildFixedDP.crl.pem
$ ./manage.py dump_crl --ca GrandchildFixedDP > GrandchildFixedDP.crl.pem
$ ./manage.py sign_cert --subject /CN=example.com --cn-in-san --csr hostname.csr --out child-fixed-dp-cert.pem --ca ChildFixedDP
$ ./manage.py sign_cert --subject /CN=example.com --cn-in-san --csr hostname.csr --out grandchild-fixed-dp-cert.pem --ca GrandchildFixedDP ... finally, let's try validation again: # This was working before, just to be sure
$ openssl verify -CAfile Root.pem -CRLfile Root-no-idp.crl.pem -crl_check_all ChildFixedDP.pem
ChildFixedDP.pem: OK
$ openssl verify -CAfile Root.pem -CRLfile Root-no-idp.crl.pem -untrusted ChildFixedDP.pem -CRLfile ChildFixedDP.crl.pem -crl_check_all child-fixed-dp-cert.pem
child-fixed-dp-cert.pem: OK
# *drumroll*
$ openssl verify -CAfile Root.pem -CRLfile Root-no-idp.crl.pem \
> -untrusted ChildFixedDP.pem -CRLfile ChildFixedDP.crl.pem \
> -crl_check_all GrandchildFixedDP.pem
GrandchildFixedDP.pem: OK
$ openssl verify -CAfile Root.pem -CRLfile Root-no-idp.crl.pem \
> -untrusted ChildFixedDP.pem -CRLfile ChildFixedDP.crl.pem \
> -untrusted GrandchildFixedDP.pem -CRLfile GrandchildFixedDP.crl.pem \
> -crl_check_all grandchild-fixed-dp-cert.pem
grandchild-fixed-dp-cert.pem: OK OKAY. Finally :-). Validation bliss! ;-) Intermediate CAs & enduser certificates - scoped CRLsAs you can perhaps guess, the initial motiviation for separate CRLs for CAs and Certificates was that one is small and rarely updated, while the latter is potentially bigger and frequently updated. But CRLs provide a scope attribute allowing us to distinguish from CRLs for CAs and enduser certificates. So let's try this out, again with a small, manual patch to modify the URL depending on the scope. We can use the CAs and certificates from before (those without the fixed DP!) but have to dump CRLs again, this time with the existing $ ./manage.py dump_crl --ca Child --scope=ca > ChildScopeCA.crl.pem
$ ./manage.py dump_crl --ca Child --scope=user > ChildScopeUser.crl.pem
$ openssl crl -noout -text -in ChildScopeCA.crl.pem
...
Full Name:
URI:http://localhost:8000/django_ca/crl/ca/36C472B2/
Only CA Certificates
$ openssl crl -noout -text -in ChildScopeUser.crl.pem
...
Full Name:
URI:http://localhost:8000/django_ca/crl/36C472B2/
Only User Certificates Notice that the two CRLs now have (1) a different URL and (2) a different scope. The URL in $ openssl verify -CAfile Root.pem -CRLfile Root-no-idp.crl.pem \
> -untrusted Child.pem -CRLfile ChildScopeCA.crl.pem \
> -crl_check_all Grandchild.pem
Grandchild.pem: OK
$ openssl verify -CAfile Root.pem -CRLfile Root-no-idp.crl.pem \
> -untrusted Child.pem -CRLfile ChildScopeCA.crl.pem \
> -untrusted Grandchild.pem -CRLfile Grandchild.crl.pem \
> -crl_check_all grandchild-cert.pem
grandchild-cert.pem: OK So... this works as well. Final thoughtsI think we have to think in a bit more detail on how to do this exactly right, but at least we now know what works and what not. I clearly found a few bugs with intermediate CAs while testing this:
|
Probably related: openssl/openssl#2873 and openssl/openssl#8439. |
Great job at narrowing down the problem to the root CA only! If I understand your detailed comment correctly, the grandchild CA thing is a separate problem. About the initial issue: It is still not clear what the origin of the "Different CRL scope" error is. To me, the scope of the root CRL in the initial example is "all the certificates signed by the root CA". That includes the root CA itself, so that the error message when checking that certificate only does not make any sense. Therefore, it is hard to say if removing the IDP from the root CRL is the best fix. Maybe it would be worth at this point to get an opinion from an OpenSSL developer? There could be a bug in their library... |
@bruot after sleeping a few days, I think (but I have no time to try it out today), further insight: When I tried "CA with cRLDP" above, I still had the issue I found in "Intermediate CAs & enduser certificates - fixed DP" later. I even mention the different URLs:
So I will try this out again with a fixed DP for the CA to see if it's working. |
So I've just now verified this behaviour. Again with a patch (see bottom) to make sure that cRLDP in the Root CA and the IDP in the CRL now match. And this also works: $ ./manage.py init_ca --pathlen=2 FixedRoot /CN=FixedRoot
$ ./manage.py dump_crl -f PEM --scope=ca --ca FixedRoot FixedRoot.crl.pem
$ openssl x509 -noout -text -in FixedRoot.pem | grep django_ca
URI:http://localhost:8000/django_ca/crl/ca/54AB2253D301520F27901CD437C948339842A14/
$ openssl crl -in FixedRoot.crl.pem -noout -text | grep django_ca
URI:http://localhost:8000/django_ca/crl/ca/54AB2253D301520F27901CD437C948339842A14/
$ openssl verify -CAfile FixedRoot.pem -CRLfile FixedRoot.crl.pem -crl_check_all FixedRoot.pem
FixedRoot.pem: OK This is the diff I used: $ git diff
diff --git a/ca/django_ca/managers.py b/ca/django_ca/managers.py
index 553998ed..a9c078e6 100644
--- a/ca/django_ca/managers.py
+++ b/ca/django_ca/managers.py
@@ -207,7 +207,7 @@ class CertificateAuthorityManager(CertificateManagerMixin, models.Manager):
if not crl_url:
crl_path = reverse('django_ca:crl', kwargs={'serial': hex_serial})
crl_url = ['http://%s%s' % (default_hostname, crl_path)]
- if parent and not ca_crl_url: # CRL for CA only makes sense in intermediate CAs
+ if not ca_crl_url: # CRL for CA only makes sense in intermediate CAs
ca_crl_path = reverse('django_ca:ca-crl', kwargs={'serial': root_serial})
ca_crl_url = ['http://%s%s' % (default_hostname, ca_crl_path)]
diff --git a/ca/django_ca/models.py b/ca/django_ca/models.py
index 4a61650c..770c2e17 100644
--- a/ca/django_ca/models.py
+++ b/ca/django_ca/models.py
@@ -704,6 +704,11 @@ class CertificateAuthority(X509CertMixin):
if kwargs.get('full_name'):
full_name = kwargs['full_name']
full_name = [parse_general_name(n) for n in full_name]
+ elif scope == 'ca':
+ crl_path = reverse('django_ca:ca-crl', kwargs={'serial': self.serial})
+ full_name = [x509.UniformResourceIdentifier(
+ 'http://%s%s' % (settings.CA_DEFAULT_HOSTNAME + crl_path)
+ )]
elif self.crl_url:
crl_url = [url.strip() for url in self.crl_url.split()]
full_name = [x509.UniformResourceIdentifier(c) for c in crl_url] |
So bottom line, this is what is the resulting task list for this issue, some of this is already solved. Please let me know what you think of this:
I'm unsure if we should allow adding a cRLDP to root CAs. There apparently is a use case, but a rare one at the very least. No public root CA that I could find sets this extension. Currently, trying to add a cRLDP to a root CA is caught by django-ca and throws an error that they cannot be used. |
@mathiasertl I agree with your points 1, 3 and 4. For point 2, I don't understand "An IDP can always be added via command-line parameter already.". Is it in |
Yes, that's what I meant. Not that it's true, after checking 😆. I've just finished I'm quite happy with the state now. The test suite passes, the default creates valid CRLs. Unless it's a feature you required, I would delay manual CRL DP creation for the next release and release the current code as is as 1.16.1. If possible, please test the |
Thanks for the changes. It looks all fine with 9f34420, except one thing: When creating a root CA and then an intermediate CA, the
gives:
Line 723 in "django_ca/models.py" appears to be something you have just modified. If I add |
yes that certainly is an issue I'll fix! |
tested and fixed your issue. it should access (and check for) |
Fixed in 1.16.1. I'm leaving this one open for a while in case you still have problems. |
@mathiasertl It looks all good to me. Thanks for this and the other fixes! |
How can I remove the IDP from CertificateRevocationListView? The docs state I need to set full_name to None but this is already the default. I've now monkey patched add_idp to be False and the verfication succeeds. django-ca/ca/django_ca/models.py Line 1164 in e4bd073
Is it possible to get a global setting to disable IDP completely or as an alternative, as on option to CertificateRevocationListView like "expires"? Thank you very much! |
Hi @kevin-olbrich, Thank you for your comment and question! However, in the future it's probably better to just create a new ticket, rather then commenting on a two year old closed ticket. If you have further questions, please just open a new ticket. The docs seem to be a little out of date in that particular function: An IDP is always added, just the full name will not be set for root CAs with a ca scope or no scope.
YES absolutely! BUT for that please file a new issue and please make sure that you document the use case. Where/how are you using your CAs/CRLs so that the current code does not work for you? kr, Mat |
When creating a CA with a CRL URL, I find that OpenSSL fails to validate certificates signed by the CA. The reason seems to be that both certificates and the CRL include the CRL URL. If the CRL does not include that distribution point URL, validation succeeds.
Steps to reproduce:
manage.py init_ca --crl-url=http://pki.domain/crl/RootCA.crl RootCA /CN=RootCA
.manage.py dump_crl -f PEM --ca [ROOT_CA_SERIAL] RootCA_crl.pem
.Now, using all the files gathered before, let's try to validate the certificate:
openssl verify -no-CAfile -no-CApath -crl_check_all -CRLfile RootCA_crl.pem -CAfile rootca.pem Cert1.pem
Result: Step 5. fails with error:
I managed to make the validation work with the following:
manage.py dump_crl -f PEM --ca [ROOT_CA_SERIAL] RootCA_crl_wo_distribution_points.pem
.openssl verify -no-CAfile -no-CApath -crl_check_all -CRLfile RootCA_crl_wo_distribution_points.pem -CAfile rootca.pem Cert1.pem
This gives:
The certificates and CRL files can be examined with the following commands:
openssl x509 -noout -text -in Cert1.pem
openssl crl -text -in RootCA_crl.pem -noout
openssl crl -text -in RootCA_crl_wo_distribution_points.pem -noout
They show that the only difference between the two tests is that, in the first case, the CRL includes the "CRL distribution points" extension with a URL, while it does not in the second case.
I don't know the details regarding CRLs, but I guess that the error exists so that the snake does not eats its tail by preventing OpenSSL to check the revocation status of the CRL itself, using... that same CRL.When you look at CRLs from reputed CA, they don't include a CRL URL in their CRL files:http://crl.globalsign.com/gseccovsslca2018.crlhttp://crl3.digicert.com/DigiCertGlobalRootCA.crlhttp://crl.identrust.com/DSTROOTCAX3CRL.crlhttp://crl.sectigo.com/SectigoRSAOrganizationValidationSecureServerCA.crlThe text was updated successfully, but these errors were encountered: