-
Notifications
You must be signed in to change notification settings - Fork 110
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
Add lint to verify CRL TBSCertList.revokedCertificates field is absent when there are no revoked certificates #832
Conversation
I like that this PR does not depend on x509.RevocationList parsing to work, effectively making it so it can catch issues with the go stdlib as well. +1 |
var tbs cryptobyte.String | ||
// From crypto/x509/parser.go: do the same trick again as above to extract | ||
// the raw bytes for Certificate.RawTBSCertificate | ||
if !input.ReadASN1Element(&tbs, cryptobyte_asn1.SEQUENCE) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit, just for consistency sake here
The ReadASN1Element and ReadASN1 happens twice in a row, once for the initial input, and then once more for the second input. Could this be a separate function call, similar to the skipTime
function below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first ReadASN1Element and ReadASN1 pair on lines 70-75 operate on an outer cryptobyte.String
whereas the second ReadASN1Element and ReadASN1 pair on lines 80-85 read from that outer cryptobyte.String
and then operate on an inner cryptobyte.String
. I think this is fine as-is and should be left alone.
} | ||
|
||
// Skip optional version | ||
tbs.SkipOptionalASN1(cryptobyte_asn1.INTEGER) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a chance that the CRL is so malformed that these fields don't exist? What happens in that case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there should be a separate lint that handles that case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For what it's worth, Boulder contains a lint that looks for a missing issuer
field.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good, but tests need a negative test case, this would need to be a malformed CRL generated outside of this code and thrown into the testdata directory.
In the past I've done this by temporarily removing the checks from the x509 code to allow me to generate malformed entries, and then storing them as PEMs here.
I have some doubts on the above comments. This alternative implementation by Phil is certainly OK, however I tested my own implementation with a few hundreds CRLs of all types, taken randomly from the list of CDPs obtainable from CCADB, and I found a completely correct behaviour in all cases. |
Honestly, I don't see why two implementations can't exist for the same lint. Just consolidate it into a single file and Argubly multiple implementations create stronger guarantees of behavior validation. |
The negative test was created using a throwaway openssl CA which issued a certificate and then revoked that certificate. I then used this tool in a Windows VM to manually delete asn1 chunks inside the |
I'm sure that your implementation is wholly correct, and I have no objections to landing both side-by-side. My concern isn't with regards to the asn1 package's current behavior -- my concern is that it does not make a documented API guarantee to preserve that behavior in the future, and an asn1-based implementation could unexpectedly become broken. |
Whoops, sorry @aaomidi wrong button. |
If I understand correctly, Phil, the "negative test" was created by deleting asn1 chunks from an existing CRL. The result is a CRL that contains the revokedCertificates element with zero length (which is the wanted flaw), however this CRL at this point has a broken signature, as well as an empty issuer field. Therefore it is "triply broken", so to speak. Without any controversy intended, it does not seem to me to be a very representative example of what is actually found in the wild. |
No controversy at all, this is the first time that I've ever put up an alternate solution to an existing PR. I hope you don't feel like I'm stepping on your toes, that was not my intention. I don't believe the test CRL needs to be representative, it just needs bytes that can be parsed far enough to fail in the specific way that the lint covers. Would a more realistic CRL be nice to use, of course. Other lints should catch the missing
Internally we have been discussing upstreaming the Boulder CRL lints to zlint, one of which does catch the missing In my code, I'm purposefully skipping fields to get to the field I do want to test. I mentioned the same thing to Amir here. Using mississued CRLs [1] and [2] found in the wild such as from this Bugzilla bug lint correctly with my code. This is not meant to pick on Entrust. Another recent example of this same problem can be found here, but the actual problematic CRLs were not uploaded, just linked. Using the negative test you submitted in PR 831 crl_empty_revoked_certificates_ko.pem also lints correctly with my code. |
I think that this is likely true for many of our test certificates. I don't think that this necessarily needs to representative in terms of triggering only one lint, if we believe it does indeed test what's expected in this specific lint. |
I think @defacto64 raises a valid concern regarding the empty Issuer DN field. RFC 5280 requires that this field be non-empty, so the current test case may fail if the parser enforces this rule from 5280 in the future. |
Here's a slightly modified version of @pgporada's test case with a non-empty Issuer DN:
|
@CBonnell May I use that to add to the negative test please? @defacto64 May I use your negative test CRL too? |
Definitely, glad it's of use. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zakird I think this is good to go? Maybe once this is merged we can get a release pushed out too?
RFC 5280 Section 5.1.2.6 states:
The golang x509 library does parse CRLs and by virtue of zero values, will correctly omit the
revokedCertificates
field from the DER-encoded bytes of an ASN.1 data structure. The important bits thatcrypto/x509
uses to determine whetherrevokedCertificates
exists are here: [1] [2] [3] and [4].This code is a validation that golang is doing the correct thing with respect to omitting this field. I chose this method over
asn1.Unmarshal
and building out a struct representation of a CRL to reduce complexity and avoid potential future issues in golang's handling of asn1 encoding/decoding.Related to my previous work letsencrypt/boulder#7417
Related to, but a different implementation of, #831