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

CDRIVER-3668 support OCSP back to OpenSSL 1.0.1 #623

Merged
merged 3 commits into from Jun 5, 2020

Conversation

kevinAlbs
Copy link
Collaborator

@kevinAlbs kevinAlbs commented May 29, 2020

Adds polyfills and makes a few tweaks to support OpenSSL back to 1.0.1.

Summary of changes:

  • change SSL_CTX_set_tlsext_status_type to SSL_set_tlsext_status_type.
  • polyfill SSL_get0_verified_chain, NID_tlsfeature, and hostname check.
  • check for status_request from the tlsfeature extension when inspecting peer certificate.
  • skip time check for older OpenSSL when updating cache entries.
  • perform the OCSP check after the handshake, since sometimes the peer certificate is not available in the callback in OpenSSL <= 1.0.2.
  • check tlsDisableOCSPEndpointCheck before reaching out to a responder.
  • make tlsDisableOCSPEndpointCheck and tlsDisableCertificateRevocationCheck URI options implicitly enable TLS.
  • enable OCSP tests on OpenSSL and macOS that were skipped.
  • add OCSP tests for OpenSSL 1.0.1.
  • update OCSP OpenSSL documentation.
  • change OCSP verification logs from MONGOC_DEBUG to TRACE in successful cases.

The first commit has the changes, the second has the config.yml regeneration.

Full evergreen patch build: https://evergreen.mongodb.com/version/5ed03422a4cf471c38658ada

- change SSL_CTX_set_tlsext_status_type to SSL_set_tlsext_status_type.
- polyfill SSL_get0_verified_chain, NID_tlsfeature, and hostname check.
- check for status_request from the tlsfeature extension when inspecting peer certificate.
- skip time check for older OpenSSL when updating cache entries.
- perform the OCSP check after the handshake, since sometimes the peer certificate is not available in the callback in OpenSSL <= 1.0.2.
- check tlsDisableOCSPEndpointCheck before reaching out to a responder.
- make tlsDisableOCSPEndpointCheck and tlsDisableCertificateRevocationCheck URI options implicitly enable TLS.
- enable OCSP tests on OpenSSL and macOS that were skipped.
- add OCSP tests for OpenSSL 1.0.1.
- update OCSP OpenSSL documentation.
- change OCSP verification logs from MONGOC_DEBUG to TRACE in successful cases.
super(CompileTask, self).__init__(task_name=task_name,
depends_on=depends_on,
tags=tags,
**kwargs)

self.extra_commands = extra_commands or []
self.suffix_commands = suffix_commands or []
self.prefix_commands = prefix_commands or []
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I added "prefix_commands" here, so I could create a debug-compile task that used OpenSSL 1.0.1 specifically. That needs to call the "install ssl" evergreen function first.

* If a driver would accept a stapled OCSP response and that response has a
* later nextUpdate than the response already in the cache, drivers SHOULD
* replace the older entry in the cache with the fresher response. */
return -1;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'll admit, this is not ideal. After searching a bit, and checking with Shreyas to see how the server polyfilled the time comparison, it seems like we'd need to parse the string representation of the time to something we can easily compare:
https://github.com/mongodb/mongo/blob/f627ff829ba96487fccb6945d0c2807b1bb51d52/src/mongo/util/net/ssl_manager_openssl.cpp#L375-L412

It doesn't look like it's required by the spec to check nextUpdate when updating the cache. By always comparing "a" to be before "b", we won't overwrite cache entries. They'll still get removed when they're detected as being expired by OCSP_check_validity.

If this seems ok with you, I'll create a ticket to do this as post-4.4 follow-up.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

#else
/* TLS versions before 1.1.0 did not define the TLS Feature extension. */
tlsfeature_nid =
OBJ_create ("1.3.6.1.5.5.7.1.24", "tlsfeature", "TLS Feature");
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The server does something similar to define the OID when it is not predefined in OpenSSL:
https://github.com/mongodb/mongo/blob/f627ff829ba96487fccb6945d0c2807b1bb51d52/src/mongo/util/net/ssl_manager_openssl.cpp#L357-L361


STACK_OF (X509) * _get_verified_chain (SSL *ssl)
{
X509_STORE *store = NULL;
Copy link
Collaborator Author

@kevinAlbs kevinAlbs May 29, 2020

Choose a reason for hiding this comment

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


#define TLSFEATURE_STATUS_REQUEST 5

/* Parse just enough of a DER encoded data to check if a SEQUENCE of INTEGER
Copy link
Collaborator Author

@kevinAlbs kevinAlbs May 29, 2020

Choose a reason for hiding this comment

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

The server has a full DER parser. We only need to parse this one thing, a SEQUENCE of INTEGER.
I used https://docs.microsoft.com/en-us/windows/win32/seccertenroll/about-sequence as a reference for the expected sequence, and this website: http://lapo.it/asn1js/ to interactively view a parsed cert (here is an example of the mustStaple cert on that website).

@@ -492,7 +664,7 @@ _contact_ocsp_responder (OCSP_CERTID *id, X509 *peer)
url_stack = X509_get1_ocsp (peer);
for (i = 0; i < sk_OPENSSL_STRING_num (url_stack) && !resp; i++) {
url = sk_OPENSSL_STRING_value (url_stack, i);
MONGOC_DEBUG ("Contacting OCSP responder '%s'", url);
TRACE ("Contacting OCSP responder '%s'", url);
Copy link
Collaborator Author

@kevinAlbs kevinAlbs May 29, 2020

Choose a reason for hiding this comment

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

I noticed that running example-client would print messages to stdout even on success. Debug logs go to stdout by default if no log handler is set. Though that seems like an odd default, I also don't see other cases of debug logs used for successful log messages, just non-critical errors. So I changed these to TRACE logs, which require the driver to be configured with -DENABLE_TRACE.

if (must_staple) {
MONGOC_ERROR ("Server must contain a stapled response");
ret = OCSP_CB_REVOKED;
GOTO (done);
}

if (!(resp = _contact_ocsp_responder (id, peer))) {
if (opts->disable_endpoint_check ||
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Reach out to a responder unless the user set tlsDisableOCSPEndpointCheck in the URI.

#ifdef MONGOC_ENABLE_OCSP_OPENSSL
/* Validate OCSP */
if (openssl->ocsp_opts &&
1 != _mongoc_ocsp_tlsext_status (ssl, openssl->ocsp_opts)) {
Copy link
Collaborator Author

@kevinAlbs kevinAlbs May 29, 2020

Choose a reason for hiding this comment

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

This is no longer done in the callback. I observed on OpenSSL 1.0.2/1.0.1 that against some servers, the callback would be unable to obtain the peer cert (i.e. SSL_get_peer_certificate returned NULL).

So instead, like the hostname check, this now does the OCSP check after the handshake.

Aside: One advantage of the callback is that when the callback returns a failure, that alerts the server the reason for the failure. Doing the OCSP check outside of the handshake means a failure would just close the connection client side. That does not seem like a big loss.

@@ -244,7 +248,7 @@ extern void
test_aws_install (TestSuite *suite);
extern void
test_streamable_ismaster_install (TestSuite *suite);
#ifdef MONGOC_ENABLE_OCSP_OPENSSL
#if defined(MONGOC_ENABLE_OCSP_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10101000L
extern void
test_ocsp_cache_install (TestSuite *suite);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Because of the behavior change with the time, I left the cache tests limited to 1.1.1. I'd like to add these tests back as future work (along with the time polyfill).

MONGOC_ERROR ("Failed to parse OCSP response");
ret = OCSP_CB_ERROR;
GOTO (done);
}
} else {
MONGOC_DEBUG ("Server does not contain a stapled response");
must_staple = X509_get_ext_d2i (peer, NID_tlsfeature, 0, 0) != NULL;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This previously checked for the existence of the tlsfeature extension. But after looking at the server code I realized this should be looking for the status_request extension within the tlsfeature extension:
https://github.com/mongodb/mongo/blob/4dcdc501520558d602897aed9aa61781265328d7/src/mongo/util/net/ssl_manager_openssl.cpp#L1605-L1616

pymongo does the same:
https://github.com/mongodb/mongo-python-driver/blob/29960237dcdd9d90402d6c76c4296aec3d01d3fc/pymongo/ocsp_support.py#L292-L298


for (i = 2; i < length; i += 3) {
/* Expect an integer, representable in one byte. */
if (length < i + 3 || data[i] != 0x02 || data[i + 1] >= 127) {
Copy link
Contributor

Choose a reason for hiding this comment

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

You need to validate the length of the INTEGER is 1 byte otherwise your i+3 logic will start reading the wrong data.

Imagine if the first INTEGER was 2 bytes instead of 1. Your iterator logic would be completely broken.

The JS viewer is pretty cool. Here is my ASN.1 reference - https://www.itu.int/rec/T-REC-X.690-201508-I

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah, good catch! I was checking for a multi-byte length, but not a multi-byte integer. And thanks for the reference.

This was also ripe for unit testing, so I added tests.

@kevinAlbs kevinAlbs merged commit 733322e into mongodb:master Jun 5, 2020
kevinAlbs added a commit that referenced this pull request Jun 5, 2020
- change SSL_CTX_set_tlsext_status_type to SSL_set_tlsext_status_type.
- polyfill SSL_get0_verified_chain, NID_tlsfeature, and hostname check.
- check for status_request from the tlsfeature extension when inspecting peer certificate.
- skip time check for older OpenSSL when updating cache entries.
- perform the OCSP check after the handshake, since sometimes the peer certificate is not available in the callback in OpenSSL <= 1.0.2.
- check tlsDisableOCSPEndpointCheck before reaching out to a responder.
- make tlsDisableOCSPEndpointCheck and tlsDisableCertificateRevocationCheck URI options implicitly enable TLS.
- enable OCSP tests on OpenSSL and macOS that were skipped.
- add OCSP tests for OpenSSL 1.0.1.
- update OCSP OpenSSL documentation.
- change OCSP verification logs from MONGOC_DEBUG to TRACE in successful cases.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants