From d1cf5d570663dac157740cb5e49d24614f185da7 Mon Sep 17 00:00:00 2001 From: Alessandro Ghedini Date: Mon, 16 Jun 2014 15:05:17 +0200 Subject: [PATCH] openssl: add support for the Certificate Status Request TLS extension Also known as "status_request" or OCSP stapling, defined in RFC6066 section 8. Thanks-to: Joe Mason - for the work-around for the OpenSSL bug. --- docs/libcurl/opts/CURLOPT_SSL_VERIFYSTATUS.3 | 4 +- lib/vtls/openssl.c | 143 +++++++++++++++++++ lib/vtls/openssl.h | 3 + 3 files changed, 148 insertions(+), 2 deletions(-) diff --git a/docs/libcurl/opts/CURLOPT_SSL_VERIFYSTATUS.3 b/docs/libcurl/opts/CURLOPT_SSL_VERIFYSTATUS.3 index 2b07b2817d21ea..d7f011a48d4854 100644 --- a/docs/libcurl/opts/CURLOPT_SSL_VERIFYSTATUS.3 +++ b/docs/libcurl/opts/CURLOPT_SSL_VERIFYSTATUS.3 @@ -42,8 +42,8 @@ All TLS based protocols: HTTPS, FTPS, IMAPS, POP3, SMTPS etc. .SH EXAMPLE TODO .SH AVAILABILITY -Added in 7.41.0. This option is currently only supported by the GnuTLS and NSS -TLS backends. +Added in 7.41.0. This option is currently only supported by the OpenSSL, GnuTLS +and NSS TLS backends. .SH RETURN VALUE Returns CURLE_OK if OCSP stapling is supported by the SSL backend, otherwise returns CURLE_NOT_BUILT_IN. diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index 2d6ad2dd9877b3..c066d43ea0965f 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -64,6 +64,7 @@ #include #include #include +#include #else #include #include @@ -1319,6 +1320,130 @@ static CURLcode verifyhost(struct connectdata *conn, X509 *server_cert) return result; } + +static CURLcode verifystatus(struct connectdata *conn, + struct ssl_connect_data *connssl) +{ + int i, ocsp_status; + const unsigned char *p; + CURLcode result = CURLE_OK; + struct SessionHandle *data = conn->data; + + OCSP_RESPONSE *rsp = NULL; + OCSP_BASICRESP *br = NULL; + X509_STORE *st = NULL; + STACK_OF(X509) *ch = NULL; + + long len = SSL_get_tlsext_status_ocsp_resp(connssl->handle, &p); + + if(!p) { + failf(data, "No OCSP response received"); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; + } + + rsp = d2i_OCSP_RESPONSE(NULL, &p, len); + if(!rsp) { + failf(data, "Invalid OCSP response"); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; + } + + ocsp_status = OCSP_response_status(rsp); + if(ocsp_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + failf(data, "Invalid OCSP response status: %s (%d)", + OCSP_response_status_str(ocsp_status), ocsp_status); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; + } + + br = OCSP_response_get1_basic(rsp); + if(!br) { + failf(data, "Invalid OCSP response"); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; + } + + ch = SSL_get_peer_cert_chain(connssl->handle); + st = SSL_CTX_get_cert_store(connssl->ctx); + + /* The authorized responder cert in the OCSP response MUST be signed by the + peer cert's issuer (see RFC6960 section 4.2.2.2). If that's a root cert, + no problem, but if it's an intermediate cert OpenSSL has a bug where it + expects this issuer to be present in the chain embedded in the OCSP + response. So we add it if necessary. */ + + /* First make sure the peer cert chain includes both a peer and an issuer, + and the OCSP response contains a responder cert. */ + if(sk_X509_num(ch) >= 2 && sk_X509_num(br->certs) >= 1) { + X509 *responder = sk_X509_value(br->certs, sk_X509_num(br->certs) - 1); + + /* Find issuer of responder cert and add it to the OCSP response chain */ + for(i = 0; i < sk_X509_num(ch); i++) { + X509 *issuer = sk_X509_value(ch, i); + if(X509_check_issued(issuer, responder) == X509_V_OK) { + if(!OCSP_basic_add1_cert(br, issuer)) { + failf(data, "Could not add issuer cert to OCSP response"); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; + } + } + } + } + + if(OCSP_basic_verify(br, ch, st, 0) <= 0) { + failf(data, "OCSP response verification failed"); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; + } + + for(i = 0; i < sk_OCSP_SINGLERESP_num(br->tbsResponseData->responses); i++) { + int cert_status, crl_reason; + OCSP_SINGLERESP *single = NULL; + + ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd; + + if(!sk_OCSP_SINGLERESP_value(br->tbsResponseData->responses, i)) + continue; + + single = sk_OCSP_SINGLERESP_value(br->tbsResponseData->responses, i); + + cert_status = OCSP_single_get0_status(single, &crl_reason, &rev, + &thisupd, &nextupd); + + if(!OCSP_check_validity(thisupd, nextupd, 300L, -1L)) { + failf(data, "OCSP response has expired"); + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; + } + + infof(data, "SSL certificate status: %s (%d)\n", + OCSP_cert_status_str(cert_status), cert_status); + + switch(cert_status) { + case V_OCSP_CERTSTATUS_GOOD: + break; + + case V_OCSP_CERTSTATUS_REVOKED: + result = CURLE_SSL_INVALIDCERTSTATUS; + + failf(data, "SSL certificate revocation reason: %s (%d)", + OCSP_crl_reason_str(crl_reason), crl_reason); + goto end; + + case V_OCSP_CERTSTATUS_UNKNOWN: + result = CURLE_SSL_INVALIDCERTSTATUS; + goto end; + } + } + +end: + if(br) OCSP_BASICRESP_free(br); + OCSP_RESPONSE_free(rsp); + + return result; +} + #endif /* USE_SSLEAY */ /* The SSL_CTRL_SET_MSG_CALLBACK doesn't exist in ancient OpenSSL versions @@ -1930,6 +2055,10 @@ static CURLcode ossl_connect_step1(struct connectdata *conn, int sockindex) failf(data, "SSL: couldn't create a context (handle)!"); return CURLE_OUT_OF_MEMORY; } + + if(data->set.ssl.verifystatus) + SSL_set_tlsext_status_type(connssl->handle, TLSEXT_STATUSTYPE_ocsp); + SSL_set_connect_state(connssl->handle); connssl->server_cert = 0x0; @@ -2613,6 +2742,15 @@ static CURLcode servercert(struct connectdata *conn, infof(data, "\t SSL certificate verify ok.\n"); } + if(data->set.ssl.verifystatus) { + result = verifystatus(conn, connssl); + if(result) { + X509_free(connssl->server_cert); + connssl->server_cert = NULL; + return result; + } + } + if(!strict) /* when not strict, we don't bother about the verify cert problems */ result = CURLE_OK; @@ -3053,4 +3191,9 @@ void Curl_ossl_md5sum(unsigned char *tmp, /* input */ MD5_Update(&MD5pw, tmp, tmplen); MD5_Final(md5sum, &MD5pw); } + +bool Curl_ossl_cert_status_request(void) +{ + return TRUE; +} #endif /* USE_SSLEAY */ diff --git a/lib/vtls/openssl.h b/lib/vtls/openssl.h index 9e9ba1e1ea87e9..39103295c790af 100644 --- a/lib/vtls/openssl.h +++ b/lib/vtls/openssl.h @@ -73,6 +73,8 @@ void Curl_ossl_md5sum(unsigned char *tmp, /* input */ unsigned char *md5sum /* output */, size_t unused); +bool Curl_ossl_cert_status_request(void); + /* Set the API backend definition to OpenSSL */ #define CURL_SSL_BACKEND CURLSSLBACKEND_OPENSSL @@ -102,6 +104,7 @@ void Curl_ossl_md5sum(unsigned char *tmp, /* input */ #define curlssl_data_pending(x,y) Curl_ossl_data_pending(x,y) #define curlssl_random(x,y,z) Curl_ossl_random(x,y,z) #define curlssl_md5sum(a,b,c,d) Curl_ossl_md5sum(a,b,c,d) +#define curlssl_cert_status_request() Curl_ossl_cert_status_request() #define DEFAULT_CIPHER_SELECTION "ALL!EXPORT!EXPORT40!EXPORT56!aNULL!LOW!RC4"