]> granicus.if.org Git - curl/commitdiff
openssl: add support for the Certificate Status Request TLS extension
authorAlessandro Ghedini <alessandro@ghedini.me>
Mon, 16 Jun 2014 13:05:17 +0000 (15:05 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Thu, 22 Jan 2015 22:25:23 +0000 (23:25 +0100)
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
lib/vtls/openssl.c
lib/vtls/openssl.h

index 2b07b2817d21eac7c7ae8f5f90f3bfa204be631e..d7f011a48d485442c3906b80dba29ab12784fffc 100644 (file)
@@ -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.
index 2d6ad2dd9877b39b198972da5059028345595498..c066d43ea0965f8623f7b09639e732b0bd9e869b 100644 (file)
@@ -64,6 +64,7 @@
 #include <openssl/md5.h>
 #include <openssl/conf.h>
 #include <openssl/bn.h>
+#include <openssl/ocsp.h>
 #else
 #include <rand.h>
 #include <x509v3.h>
@@ -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 */
index 9e9ba1e1ea87e9cd2a3e02bb0bcfbde30a299e85..39103295c790af6a51f49802fd7b6dbde57b1598 100644 (file)
@@ -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"