Skip to content

Commit 8e8ff55

Browse files
committed
CDRIVER-3408 OCSP stapling support for OpenSSL
1 parent cbcb2c2 commit 8e8ff55

File tree

4 files changed

+246
-11
lines changed

4 files changed

+246
-11
lines changed

src/libmongoc/src/mongoc/mongoc-openssl-private.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,13 @@
2626

2727
#include "mongoc-ssl.h"
2828

29+
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && !defined(OPENSSL_NO_OCSP) && \
30+
!defined(LIBRESSL_VERSION_NUMBER)
31+
#define MONGOC_ENABLE_OCSP
32+
#endif
2933

30-
BSON_BEGIN_DECLS
3134

35+
BSON_BEGIN_DECLS
3236

3337
bool
3438
_mongoc_openssl_check_cert (SSL *ssl,
@@ -43,6 +47,11 @@ _mongoc_openssl_init (void);
4347
void
4448
_mongoc_openssl_cleanup (void);
4549

50+
#ifdef MONGOC_ENABLE_OCSP
51+
int
52+
_mongoc_ocsp_tlsext_status_cb (SSL *ssl, void *arg);
53+
#endif
54+
4655

4756
BSON_END_DECLS
4857

src/libmongoc/src/mongoc/mongoc-openssl.c

Lines changed: 189 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,19 @@
2323
#include <openssl/bio.h>
2424
#include <openssl/ssl.h>
2525
#include <openssl/err.h>
26+
#include <openssl/ocsp.h>
2627
#include <openssl/x509v3.h>
2728
#include <openssl/crypto.h>
2829

2930
#include <string.h>
3031

3132
#include "mongoc-init.h"
33+
#include "mongoc-openssl-private.h"
3234
#include "mongoc-socket.h"
3335
#include "mongoc-ssl.h"
34-
#include "mongoc-openssl-private.h"
35-
#include "mongoc-trace-private.h"
36+
#include "mongoc-stream-tls-openssl-private.h"
3637
#include "mongoc-thread-private.h"
38+
#include "mongoc-trace-private.h"
3739
#include "mongoc-util-private.h"
3840

3941
#ifdef _WIN32
@@ -451,6 +453,189 @@ _mongoc_openssl_setup_pem_file (SSL_CTX *ctx,
451453
return 1;
452454
}
453455

456+
#ifdef MONGOC_ENABLE_OCSP
457+
458+
static X509 *
459+
_get_issuer (X509 *cert, STACK_OF (X509) * chain)
460+
{
461+
X509 *issuer = NULL, *candidate = NULL;
462+
X509_NAME *issuer_name = NULL, *candidate_name = NULL;
463+
int i;
464+
465+
issuer_name = X509_get_issuer_name (cert);
466+
for (i = 0; i < sk_X509_num (chain) && issuer == NULL; i++) {
467+
candidate = sk_X509_value (chain, i);
468+
candidate_name = X509_get_subject_name (candidate);
469+
if (0 == X509_NAME_cmp (candidate_name, issuer_name)) {
470+
issuer = candidate;
471+
}
472+
}
473+
return issuer;
474+
}
475+
476+
#define ERR_STR (ERR_error_string (ERR_get_error (), NULL))
477+
478+
int
479+
_mongoc_ocsp_tlsext_status_cb (SSL *ssl, void *arg)
480+
{
481+
const int ERROR = -1, FAILURE = 0, SUCCESS = 1;
482+
OCSP_RESPONSE *resp = NULL;
483+
OCSP_BASICRESP *basic = NULL;
484+
X509_STORE *store = NULL;
485+
X509 *peer = NULL, *issuer = NULL;
486+
STACK_OF (X509) *cert_chain = NULL;
487+
const unsigned char *r = NULL;
488+
int cert_status, reason, len, status, ret;
489+
OCSP_CERTID *id = NULL;
490+
mongoc_openssl_ocsp_opt_t *opts = (mongoc_openssl_ocsp_opt_t *) arg;
491+
ASN1_GENERALIZEDTIME *produced_at = NULL, *this_update = NULL,
492+
*next_update = NULL;
493+
494+
if (opts->weak_cert_validation) {
495+
return SUCCESS;
496+
}
497+
498+
if (!(peer = SSL_get_peer_certificate (ssl))) {
499+
MONGOC_ERROR ("No certificate was presented by the peer: %s", ERR_STR);
500+
ret = ERROR;
501+
goto done;
502+
}
503+
504+
/* Get the stapled OCSP response returned by the server */
505+
len = SSL_get_tlsext_status_ocsp_resp (ssl, &r);
506+
if (!r) {
507+
bool must_staple = X509_get_ext_d2i (peer, NID_tlsfeature, 0, 0) != NULL;
508+
if (must_staple) {
509+
MONGOC_ERROR ("Server must contain a stapled response: %s", ERR_STR);
510+
ret = FAILURE;
511+
goto done;
512+
}
513+
ret = SUCCESS; // TODO: contact OCSP responder
514+
goto done;
515+
}
516+
517+
/* obtain an OCSP_RESPONSE object from the OCSP response */
518+
if (!d2i_OCSP_RESPONSE (&resp, &r, len)) {
519+
MONGOC_ERROR ("Failed to parse OCSP response: %s", ERR_STR);
520+
ret = ERROR;
521+
goto done;
522+
}
523+
524+
/* Validate the OCSP response status of the OCSP_RESPONSE object */
525+
status = OCSP_response_status (resp);
526+
if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
527+
MONGOC_ERROR ("OCSP response error %d %s",
528+
status,
529+
OCSP_response_status_str (status));
530+
ret = ERROR;
531+
goto done;
532+
}
533+
534+
/* Get the OCSP_BASICRESP structure contained in OCSP_RESPONSE object for the
535+
* peer cert */
536+
basic = OCSP_response_get1_basic (resp);
537+
if (!basic) {
538+
MONGOC_ERROR ("Could not find BasicOCSPResponse: %s", ERR_STR);
539+
ret = ERROR;
540+
goto done;
541+
}
542+
543+
store = SSL_CTX_get_cert_store (SSL_get_SSL_CTX (ssl));
544+
545+
/* Get a STACK_OF(X509) certs forming the cert chain of the peer, including
546+
* the peer's cert */
547+
if (!(cert_chain = SSL_get0_verified_chain (ssl))) {
548+
MONGOC_ERROR ("No certificate was presented by the peer: %s", ERR_STR);
549+
ret = ERROR;
550+
goto done;
551+
}
552+
553+
/*
554+
* checks that the basic response message is correctly signed and that the
555+
* signer certificate can be validated.
556+
* 1. The function first verifies the signer cert of the response is in the
557+
* given cert chain.
558+
* 2. Next, the function verifies the signature of the basic response.
559+
* 3. Finally, the function validates the signer cert, constructing the
560+
* validation path via the untrusted cert chain.
561+
*/
562+
if (SUCCESS != OCSP_basic_verify (basic, cert_chain, store, 0)) {
563+
MONGOC_ERROR ("OCSP response failed verification: %s", ERR_STR);
564+
ret = ERROR;
565+
goto done;
566+
}
567+
568+
if (!(issuer = _get_issuer (peer, cert_chain))) {
569+
MONGOC_ERROR ("Could not get issuer from peer cert");
570+
ret = ERROR;
571+
goto done;
572+
}
573+
574+
if (!(id = OCSP_cert_to_id (NULL /* SHA1 */, peer, issuer))) {
575+
MONGOC_ERROR ("Could not obtain a valid OCSP_CERTID for peer: %s",
576+
ERR_STR);
577+
ret = ERROR;
578+
goto done;
579+
}
580+
581+
/* searches the basic response for an OCSP response for the given cert ID */
582+
if (!OCSP_resp_find_status (basic,
583+
id,
584+
&cert_status,
585+
&reason,
586+
&produced_at,
587+
&this_update,
588+
&next_update)) {
589+
MONGOC_ERROR ("No OCSP response found for the peer certificate: %s",
590+
ERR_STR);
591+
ret = ERROR;
592+
goto done;
593+
}
594+
595+
/* checks the validity of this_update and next_update values */
596+
if (!OCSP_check_validity (this_update, next_update, 0L, -1L)) {
597+
MONGOC_ERROR ("OCSP response has expired: %s", ERR_STR);
598+
ret = ERROR;
599+
goto done;
600+
}
601+
602+
switch (cert_status) {
603+
case V_OCSP_CERTSTATUS_GOOD:
604+
/* TODO: cache response */
605+
break;
606+
607+
case V_OCSP_CERTSTATUS_REVOKED:
608+
MONGOC_ERROR ("OCSP Certificate Status: Revoked. Reason %d", reason);
609+
ret = FAILURE;
610+
goto done;
611+
612+
default: /* V_OCSP_CERTSTATUS_UNKNOWN */
613+
break;
614+
}
615+
616+
/* Validate hostname matches cert */
617+
if (!opts->allow_invalid_hostname &&
618+
X509_check_host (peer, opts->host, 0, 0, NULL) != SUCCESS &&
619+
X509_check_ip_asc (peer, opts->host, 0) != SUCCESS) {
620+
ret = FAILURE;
621+
goto done;
622+
}
623+
624+
ret = SUCCESS;
625+
done:
626+
if (basic)
627+
OCSP_BASICRESP_free (basic);
628+
if (resp)
629+
OCSP_RESPONSE_free (resp);
630+
if (id)
631+
OCSP_CERTID_free (id);
632+
if (peer)
633+
X509_free (peer);
634+
if (issuer)
635+
X509_free (issuer);
636+
return ret;
637+
}
638+
#endif
454639

455640
/**
456641
* _mongoc_openssl_ctx_new:
@@ -493,7 +678,8 @@ _mongoc_openssl_ctx_new (mongoc_ssl_opt_t *opt)
493678
ssl_ctx_options |= SSL_OP_NO_COMPRESSION;
494679
#endif
495680

496-
/* man SSL_get_options says: "SSL_OP_NO_RENEGOTIATION options were added in OpenSSL 1.1.1". */
681+
/* man SSL_get_options says: "SSL_OP_NO_RENEGOTIATION options were added in
682+
* OpenSSL 1.1.1". */
497683
#ifdef SSL_OP_NO_RENEGOTIATION
498684
ssl_ctx_options |= SSL_OP_NO_RENEGOTIATION;
499685
#endif

src/libmongoc/src/mongoc/mongoc-stream-tls-openssl-private.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424

2525
BSON_BEGIN_DECLS
2626

27+
typedef struct {
28+
const char *host;
29+
bool allow_invalid_hostname;
30+
bool weak_cert_validation;
31+
} mongoc_openssl_ocsp_opt_t;
2732

2833
/**
2934
* mongoc_stream_tls_openssl_t:
@@ -34,6 +39,7 @@ typedef struct {
3439
BIO *bio;
3540
BIO_METHOD *meth;
3641
SSL_CTX *ctx;
42+
mongoc_openssl_ocsp_opt_t *ocsp_opts;
3743
} mongoc_stream_tls_openssl_t;
3844

3945

src/libmongoc/src/mongoc/mongoc-stream-tls-openssl.c

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@
4545

4646
#define MONGOC_STREAM_TLS_OPENSSL_BUFFER_SIZE 4096
4747

48-
#if OPENSSL_VERSION_NUMBER < 0x10100000L || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L)
48+
#if OPENSSL_VERSION_NUMBER < 0x10100000L || \
49+
(defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L)
4950
static void
5051
BIO_meth_free (BIO_METHOD *meth)
5152
{
@@ -93,6 +94,11 @@ _mongoc_stream_tls_openssl_destroy (mongoc_stream_t *stream)
9394
SSL_CTX_free (openssl->ctx);
9495
openssl->ctx = NULL;
9596

97+
if (openssl->ocsp_opts)
98+
bson_free ((char *) openssl->ocsp_opts->host);
99+
bson_free (openssl->ocsp_opts);
100+
openssl->ocsp_opts = NULL;
101+
96102
bson_free (openssl);
97103
bson_free (stream);
98104

@@ -540,11 +546,11 @@ _mongoc_stream_tls_openssl_check_closed (mongoc_stream_t *stream) /* IN */
540546
/**
541547
* mongoc_stream_tls_openssl_handshake:
542548
*/
543-
bool
544-
mongoc_stream_tls_openssl_handshake (mongoc_stream_t *stream,
545-
const char *host,
546-
int *events,
547-
bson_error_t *error)
549+
static bool
550+
_mongoc_stream_tls_openssl_handshake (mongoc_stream_t *stream,
551+
const char *host,
552+
int *events,
553+
bson_error_t *error)
548554
{
549555
mongoc_stream_tls_t *tls = (mongoc_stream_tls_t *) stream;
550556
mongoc_stream_tls_openssl_t *openssl =
@@ -558,10 +564,21 @@ mongoc_stream_tls_openssl_handshake (mongoc_stream_t *stream,
558564
BIO_get_ssl (openssl->bio, &ssl);
559565

560566
if (BIO_do_handshake (openssl->bio) == 1) {
567+
#if (OPENSSL_VERSION_NUMBER >= 0x10002000L)
568+
X509 *peer = NULL;
569+
peer = SSL_get_peer_certificate (ssl);
570+
571+
if (tls->ssl_opts.allow_invalid_hostname ||
572+
X509_check_host (peer, host, 0, 0, NULL) == 1 ||
573+
X509_check_ip_asc (peer, host, 0) == 1) {
574+
RETURN (true);
575+
}
576+
#else
561577
if (_mongoc_openssl_check_cert (
562578
ssl, host, tls->ssl_opts.allow_invalid_hostname)) {
563579
RETURN (true);
564580
}
581+
#endif
565582

566583
*events = 0;
567584
bson_set_error (error,
@@ -674,6 +691,7 @@ mongoc_stream_tls_openssl_new (mongoc_stream_t *base_stream,
674691
{
675692
mongoc_stream_tls_t *tls;
676693
mongoc_stream_tls_openssl_t *openssl;
694+
mongoc_openssl_ocsp_opt_t *ocsp_opts = NULL;
677695
SSL_CTX *ssl_ctx = NULL;
678696
BIO *bio_ssl = NULL;
679697
BIO *bio_mongoc_shim = NULL;
@@ -712,6 +730,21 @@ mongoc_stream_tls_openssl_new (mongoc_stream_t *base_stream,
712730
* Set a callback to get the SNI, if provided */
713731
SSL_CTX_set_tlsext_servername_callback (ssl_ctx,
714732
_mongoc_stream_tls_openssl_sni);
733+
#ifdef MONGOC_ENABLE_OCSP
734+
} else {
735+
if (!SSL_CTX_set_tlsext_status_type (ssl_ctx, TLSEXT_STATUSTYPE_ocsp)) {
736+
SSL_CTX_free (ssl_ctx);
737+
RETURN (NULL);
738+
}
739+
740+
ocsp_opts = bson_malloc(sizeof(mongoc_openssl_ocsp_opt_t));
741+
ocsp_opts->allow_invalid_hostname = opt->allow_invalid_hostname;
742+
ocsp_opts->weak_cert_validation = opt->weak_cert_validation;
743+
ocsp_opts->host = bson_strdup(host);
744+
745+
SSL_CTX_set_tlsext_status_arg (ssl_ctx, ocsp_opts);
746+
SSL_CTX_set_tlsext_status_cb (ssl_ctx, _mongoc_ocsp_tlsext_status_cb);
747+
#endif
715748
}
716749

717750
if (opt->weak_cert_validation) {
@@ -750,6 +783,7 @@ mongoc_stream_tls_openssl_new (mongoc_stream_t *base_stream,
750783
openssl->bio = bio_ssl;
751784
openssl->meth = meth;
752785
openssl->ctx = ssl_ctx;
786+
openssl->ocsp_opts = ocsp_opts;
753787

754788
tls = (mongoc_stream_tls_t *) bson_malloc0 (sizeof *tls);
755789
tls->parent.type = MONGOC_STREAM_TLS;
@@ -765,7 +799,7 @@ mongoc_stream_tls_openssl_new (mongoc_stream_t *base_stream,
765799
tls->parent.timed_out = _mongoc_stream_tls_openssl_timed_out;
766800
tls->parent.should_retry = _mongoc_stream_tls_openssl_should_retry;
767801
memcpy (&tls->ssl_opts, opt, sizeof tls->ssl_opts);
768-
tls->handshake = mongoc_stream_tls_openssl_handshake;
802+
tls->handshake = _mongoc_stream_tls_openssl_handshake;
769803
tls->ctx = (void *) openssl;
770804
tls->timeout_msec = -1;
771805
tls->base_stream = base_stream;

0 commit comments

Comments
 (0)