Skip to content
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

OpenSSL backend code for CRLs #2315

Merged
merged 20 commits into from Oct 22, 2015
Merged

Conversation

etrauschke
Copy link
Contributor

This is the backend code for the CRL objects. Looks like a bunch of additional automatic tests have been added so let me make sure they clear first.

@etrauschke
Copy link
Contributor Author

Looks like I have to change the test certs so that the 0.9.8 tests will not see a cert issuer extension marked as critical. Hopefully I can reuse some other cert for this, otherwise I might have to make a change to the vectors again.

@etrauschke
Copy link
Contributor Author

It looks like I'll have to change the test cert crl_all_reasons.pem so that the cert_issuer extension is not critical. Otherwise I would have to skip a bunch of tests in the 0.9.8 environments.

@codecov-io
Copy link

Current coverage is 99.98%

Merging #2315 into master will not affect coverage as of 4e0e10c

@@            master   #2315   diff @@
======================================
  Files          120     120       
  Stmts        12247   12495   +248
  Branches      1350    1366    +16
  Methods          0       0       
======================================
+ Hit          12245   12493   +248
  Partial          2       2       
  Missed           0       0       

Review entire Coverage Diff as of 4e0e10c

Powered by Codecov. Updated on successful CI builds.

@etrauschke
Copy link
Contributor Author

So I'm not sure what the coverage thing is complaining about, I guess it's because the for loop never runs completely through (intentionally).
I guess if that is really important I can change the line to something like this:

for r in crl.revoked_certificates.append(dummy_rev):

@etrauschke
Copy link
Contributor Author

Ended up completely changing the logic of the reason flag test. Coverage seems happy now.

@alex
Copy link
Member

alex commented Sep 8, 2015

Jenkins, retest this please.

@reaperhulk
Copy link
Member

jenkins, retest this please

@etrauschke
Copy link
Contributor Author

Anybody could have a look at this?


.. function:: load_pem_x509_crl(data, backend)

.. versionadded:: 1.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be 1.1 now

@reaperhulk
Copy link
Member

It doesn't look like your merge captured all the commits (it's definitely missing the PR you opened earlier).

@etrauschke
Copy link
Contributor Author

Oh, please ignore this last push I merged my master and just did a push without specifying the branch so it updated this one unintentionally as well. I'm still testing my actual changes and will push once again once I('m done.

@etrauschke
Copy link
Contributor Author

Now the PR is ready for review. If this gets a bit cluttered I can also create a new PR.

return self.__last_update

@property
def revoked_certificates(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is in our current interface, but I'm wondering if it might be more consistent for us to just make CertificateRevocationList an iterable? That would look more like the way Extensions, SubjectAlternativeName, and several other x509 classes behave.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you just want me to get rid of revoked_certificates() and replace it with an iter() and len() function? I can do that, I just tried to stay as close to the OpenSSL specification because I think this makes it easier for people to find out how to access the revoked certs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think that's consistent with our current approach for x509 objects.

@reaperhulk
Copy link
Member

Let's also discuss the caching in a separate PR since it'd be awesome to land this and caching is orthogonal to the vast majority of the work here. (Sorry to make you do some extra work!)

@reaperhulk
Copy link
Member

It looks like the certificateIssuer revoked certificate extension is also something we can handle in 0.9.8. It's true that calling X509V3_EXT_d2i on the ext doesn't work, but in this case we can do the following:

ext is an X509_EXTENSION * which has an ASN1_OCTET_STRING *value.
An ASN1_OCTET_STRING * is a typedef for the struct asn1_string_st which has int length and unsigned char *data.
We can bind GENERAL_NAMES *d2i_GENERAL_NAMES(GENERAL_NAMES **, const unsigned char **, long); which should allow us to use the ASN1_OCTET_STRING data/length to obtain the GENERAL_NAMES * that we need to pass to the _decode_general_names function.

@etrauschke
Copy link
Contributor Author

@reaperhulk With the CRLExtensionOID I probably should have paid more attention when this change was made. This is unfortunate because it is basically wrong in the current form. They are not CRL extensions. Not sure if you would break anyones code yet since the backend support hasn't been in so the number of people using it is probably 0. However, I understand how complicated it is to change committed interfaces. Just one thing to keep in mind, if the backend code goes back, people might start using these extensions and then it gets even harder to change things around.

@etrauschke
Copy link
Contributor Author

The plan with the certificateIssuer sounds pretty nifty but how would we integrate this with the X509ExtensionParser which does run X509V3_EXT_d2i for us. We would have to shoehorn some extra code in there which seems ugly. Maybe we can look at this again if we find additional extensions which require newer OpenSSL versions to be properly supported.

@etrauschke
Copy link
Contributor Author

Before I forget it, I also changed the exception message for unsupported critical extensions so that it actually tells you that it is critical. Otherwise the message is confusing because the rule is that unknown exceptions are to be ignored unless they are marked critical.

@reaperhulk
Copy link
Member

Right now there are no extensions that we parse conditionally on underyling OpenSSL version. I'd very much prefer to keep it that way since it's extremely difficult to explain to users how the "same" version of cryptography might exhibit significantly different behavior. At the very least we need to raise a warning.

It is a shame that the naming is wrong (and that's on me!), but we can transition away from that name with our standard deprecation strategy without too much pain. We can put in a PR to change the name after this PR, update the docs to all point at the new name, but then keep the old name for compatibility for the next 2 releases. It would then be completely dropped in the 1.3 release.

@etrauschke
Copy link
Contributor Author

Ok, let me take a stab at the certIssuer custom parsing. I'll also remove the caching stuff for now and undo the naming change for CRLExtensionOID.

undo name change of CRLExtensionOID
use custom parsing mechanism for certIssuer entry extension
add new crl to vectors for testing invalid certIssuer entry ext
@etrauschke
Copy link
Contributor Author

Found a way to make this work, let me know what you think.
I had to add a new CRL pem file to vectors which includes an empty/invalid certIssuer entry extension. This is necessary for code coverage since I can't use the generic bailout for an invalid extension anymore. Let me know if you want me to push that in a separate PR. Also, I had to add a new binding for *d2i_GENERAL_NAMES which you might also want in a separate PR. So just let me know and I'll do that.

@reaperhulk
Copy link
Member

Yeah if you wouldn't mind dropping those in separate PRs we can get them landed immediately :)

backend
)

with pytest.raises(ValueError):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you walk me through the logic here? How was this extension encoded badly such that it should error? What makes the empty extension trigger the extension is invalid path?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I did is I created a new custom extension which has the same OID as the certificate issuer extension but has a custom value. The way I did this is like that (in C using OpenSSL):

X509_REVOKED *r = X509_REVOKED_new();
int nid = OBJ_create("2.5.29.29", "Test", "Test Extension");
X509_REVOKED_add1_ext_i2d(r, nid, NULL, 0, 0);

Then when we get to _decode_cert_issuer(backend, ext) we get a NULL pointer as data_ptr_ptr (or something else which isn't a GENERAL_NAMES type) which goes into d2i_GENERAL_NAMES() and returns a NULL pointer for which I explicitly test.

If that is not good enough, I could also create this CRL so that it sets an integer instead of a NULL value. Everything which is not GENERAL_NAMES should trigger this. However, I think at that point we are fairly far out in terms of error cases, I expect this code to never be triggered in the real world. I just needed something to make coverage happy.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm okay with this -- just wanted to make sure I understood the approach. Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I think about this, I wonder if we should just silently ignore it. I mean for the other extensions we don't check if the values actually make sense and we can pass a NULL value to _decode_general_names(), which just leads to an empty GeneralNames object.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about just creating a bad extension (use an OID that doesn't make sense for the context, like EKU or KU)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I owe you a review of this updated code again. Will get to it tonight.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, thanks. I just wanted to see if we need to change the behavior again, because before we have decided on what should happen you might just waste your time reviewing code which needs to be changed anyway.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, looking at this more I'd prefer it to ignore it like a normal NULL. We should already be triggering this code path with another test so we shouldn't need a special test case at all now that you're using the ExtensionParser class, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, for everything which goes through the regular processing with X509V3_EXT_d2i we already handle the NULL case and I haven't change any of that code. It would be just about the certIssuer extension parsing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's leave this as is with just an additional comment next to that value error that states it's present because we're parsing the extension ourselves and can't rely on the check in the extensionparser.

@reaperhulk
Copy link
Member

I've restarted the CI job in #2417. Once that merges you can rebase this and add the two comments and I'll do a final review. Once again, thanks for sticking with this!

@reaperhulk
Copy link
Member

The vector has now merged. If you add those two comments I'll do a final paranoia-based review to make sure I haven't missed anything and we can get this merged!

Deserialize a certificate revocation list (CRL) from PEM encoded data. PEM
requests are base64 decoded and have delimiters that look like
``-----BEGIN X509 CRL-----``. This format is also known as
PKCS#10.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PKCS10 defines CSRs -- is there anything about CRLs in there?

@reaperhulk
Copy link
Member

I have a few small things I'd like to clean up but I'm satisfied that it's ready for merge now. Huge thanks @etrauschke. Be sure to send a PR adding yourself to AUTHORS.rst when you get a chance!

reaperhulk added a commit that referenced this pull request Oct 22, 2015
@reaperhulk reaperhulk merged commit 6a2e08b into pyca:master Oct 22, 2015
@coveralls
Copy link

Coverage Status

Changes Unknown when pulling cee79f8 on etrauschke:crl_ossl_backend into ** on pyca:master**.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 16, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Development

Successfully merging this pull request may close these issues.

None yet

6 participants