Repository: trafficserver
Updated Branches:
  refs/heads/master 562179c50 -> 2621e676c


TS-2367: Add OCSP (Online Certificate Status Protocol) Stapling Support


Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo
Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/2621e676
Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/2621e676
Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/2621e676

Branch: refs/heads/master
Commit: 2621e676c2b3880c5f3d822bf6e0e5cf30c021fc
Parents: 562179c
Author: Feifei Cai <ff...@yahoo-inc.com>
Authored: Fri Aug 1 11:02:43 2014 -0700
Committer: Bryan Call <bc...@apache.org>
Committed: Fri Aug 1 11:02:43 2014 -0700

----------------------------------------------------------------------
 iocore/net/OCSPStapling.cc  | 412 +++++++++++++++++++++++++++++++++++++++
 iocore/net/P_OCSPStapling.h |  35 ++++
 2 files changed, 447 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/trafficserver/blob/2621e676/iocore/net/OCSPStapling.cc
----------------------------------------------------------------------
diff --git a/iocore/net/OCSPStapling.cc b/iocore/net/OCSPStapling.cc
new file mode 100644
index 0000000..ca27b00
--- /dev/null
+++ b/iocore/net/OCSPStapling.cc
@@ -0,0 +1,412 @@
+/** @file
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#include <openssl/ocsp.h>
+#include "P_OCSPStapling.h"
+#include "P_Net.h"
+#include "P_SSLConfig.h"
+
+#ifdef HAVE_OPENSSL_OCSP_STAPLING
+
+// Maxiumum OCSP stapling response size.
+// This should be the response for a single certificate and will typically 
include the responder certificate chain,
+// so 10K should be more than enough.
+#define MAX_STAPLING_DER 10240
+
+// Cached info stored in SSL_CTX ex_info
+struct certinfo
+{
+  unsigned char idx[20];  // Index in session cache SHA1 hash of certificate
+  OCSP_CERTID *cid; // Certificate ID for OCSP requests or NULL if ID cannot 
be determined
+  char *uri;  // Responder details
+  ink_mutex stapling_mutex;
+  unsigned char resp_der[MAX_STAPLING_DER];
+  unsigned int resp_derlen;
+  bool is_expire;
+  time_t expire_time;
+};
+
+void certinfo_free(void * /*parent*/, void *ptr, CRYPTO_EX_DATA * /*ad*/,
+    int /*idx*/, long /*argl*/, void * /*argp*/)
+{
+  certinfo *cinf = (certinfo *)ptr;
+
+  if (!cinf)
+    return;
+  if (cinf->uri)
+    OPENSSL_free(cinf->uri);
+  ink_mutex_destroy(&cinf->stapling_mutex);
+  OPENSSL_free(cinf);
+}
+
+static int ssl_stapling_index = -1;
+
+void ssl_stapling_ex_init(void)
+{
+  if (ssl_stapling_index != -1)
+    return;
+  ssl_stapling_index = SSL_CTX_get_ex_new_index(0, 0, 0, 0, certinfo_free);
+}
+
+static X509 *
+stapling_get_issuer(SSL_CTX *ssl_ctx, X509 *x)
+{
+  X509 *issuer = NULL;
+  int i;
+  X509_STORE *st = SSL_CTX_get_cert_store(ssl_ctx);
+  X509_STORE_CTX inctx;
+  STACK_OF(X509) *extra_certs = NULL;
+
+#ifdef SSL_CTX_get_extra_chain_certs
+  SSL_CTX_get_extra_chain_certs(ssl_ctx, &extra_certs);
+#else
+  extra_certs = ssl_ctx->extra_certs;
+#endif
+
+  if (sk_X509_num(extra_certs) == 0)
+    return NULL;
+
+  for (i = 0; i < sk_X509_num(extra_certs); i++) {
+    issuer = sk_X509_value(extra_certs, i);
+    if (X509_check_issued(issuer, x) == X509_V_OK) {
+      CRYPTO_add(&issuer->references, 1, CRYPTO_LOCK_X509);
+      return issuer;
+    }
+  }
+
+  if (!X509_STORE_CTX_init(&inctx, st, NULL, NULL))
+    return NULL;
+  if (X509_STORE_CTX_get1_issuer(&issuer, &inctx, x) <= 0)
+    issuer = NULL;
+  X509_STORE_CTX_cleanup(&inctx);
+
+  return issuer;
+}
+
+bool
+ssl_stapling_init_cert(SSL_CTX *ctx, const char *certfile)
+{
+  certinfo *cinf;
+  X509 *cert = NULL;
+  X509 *issuer = NULL;
+  STACK_OF(OPENSSL_STRING) *aia = NULL;
+  BIO *bio = BIO_new_file(certfile, "r");
+
+  cert = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL);
+  if (!cert) {
+    Debug("ssl", "can not read cert from certfile %s!", certfile);
+    return false;
+  }
+
+  cinf  = (certinfo *)SSL_CTX_get_ex_data(ctx, ssl_stapling_index);
+  if (cinf) {
+    Debug("ssl", "certificate already initialized!");
+    return false;
+  }
+
+  cinf = (certinfo *)OPENSSL_malloc(sizeof(certinfo));
+  if (!cinf) {
+    Debug("ssl", "error allocating memory!");
+    return false;
+  }
+
+  // Initialize certinfo
+  cinf->cid = NULL;
+  cinf->uri = NULL;
+  cinf->resp_derlen = 0;
+  ink_mutex_init(&cinf->stapling_mutex, "stapling_mutex");
+  cinf->is_expire = true;
+  cinf->expire_time = 0;
+
+  SSL_CTX_set_ex_data(ctx, ssl_stapling_index, cinf);
+
+  issuer = stapling_get_issuer(ctx, cert);
+  if (issuer == NULL) {
+        Debug("ssl", "can not get issuer certificate!");
+    return false;
+  }
+
+  cinf->cid = OCSP_cert_to_id(NULL, cert, issuer);
+  X509_free(issuer);
+  if (!cinf->cid)
+    return false;
+  X509_digest(cert, EVP_sha1(), cinf->idx, NULL);
+
+  aia = X509_get1_ocsp(cert);
+  if (aia)
+    cinf->uri = sk_OPENSSL_STRING_pop(aia);
+  if (!cinf->uri) {
+        Debug("ssl", "no responder URI");
+  }
+  if (aia)
+    X509_email_free(aia);
+
+  Debug("ssl", "success to init certinfo into SSL_CTX: %p", ctx);
+  return true;
+}
+
+static certinfo *
+stapling_get_cert_info(SSL_CTX *ctx)
+{
+  certinfo *cinf;
+
+  cinf = (certinfo *)SSL_CTX_get_ex_data(ctx, ssl_stapling_index);
+  if (cinf && cinf->cid)
+    return cinf;
+
+  return NULL;
+}
+
+static bool
+stapling_cache_response(OCSP_RESPONSE *rsp, certinfo *cinf)
+{
+  unsigned char resp_der[MAX_STAPLING_DER];
+  unsigned char *p;
+  unsigned int resp_derlen;
+
+  p = resp_der;
+  resp_derlen = i2d_OCSP_RESPONSE(rsp, &p);
+
+  if (resp_derlen == 0) {
+    Error("stapling_cache_response: can not encode OCSP stapling response");
+    return false;
+  }
+
+  if (resp_derlen > MAX_STAPLING_DER) {
+    Error("stapling_cache_response: OCSP stapling response too big (%u 
bytes)", resp_derlen);
+    return false;
+  }
+
+  ink_mutex_acquire(&cinf->stapling_mutex);
+  memcpy(cinf->resp_der, resp_der, resp_derlen);
+  cinf->resp_derlen = resp_derlen;
+  cinf->is_expire = false;
+  cinf->expire_time = time(NULL) + SSLConfigParams::ssl_ocsp_cache_timeout;
+  ink_mutex_release(&cinf->stapling_mutex);
+
+  Debug("ssl", "stapling_cache_response: success to cache response");
+  return true;
+}
+
+static int
+stapling_check_response(certinfo *cinf, OCSP_RESPONSE *rsp)
+{
+  int status, reason;
+  OCSP_BASICRESP *bs = NULL;
+  ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
+  int response_status = OCSP_response_status(rsp);
+
+  // Check to see if response is an error.
+  // If so we automatically accept it because it would have expired from the 
cache if it was time to retry.
+  if (response_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
+    return SSL_TLSEXT_ERR_NOACK;
+  }
+
+  bs = OCSP_response_get1_basic(rsp);
+  if (bs == NULL) {
+    // If we can't parse response just pass it back to client
+    Error("stapling_check_response: can not parsing response");
+    return SSL_TLSEXT_ERR_OK;
+  }
+  if (!OCSP_resp_find_status(bs, cinf->cid, &status, &reason, &rev,
+        &thisupd, &nextupd)) {
+    // If ID not present just pass it back to client
+    Error("stapling_check_response: certificate ID not present in response");
+  } else {
+    OCSP_check_validity(thisupd, nextupd, 300, -1);
+  }
+  OCSP_BASICRESP_free(bs);
+
+  return SSL_TLSEXT_ERR_OK;
+}
+
+static OCSP_RESPONSE *
+query_responder(BIO *b, char *path, OCSP_REQUEST *req, int req_timeout)
+{
+  ink_hrtime start, end;
+  OCSP_RESPONSE *resp = NULL;
+  OCSP_REQ_CTX *ctx;
+  int rv;
+
+  start = ink_get_hrtime();
+  end = ink_hrtime_add(start, ink_hrtime_from_sec(req_timeout));
+
+  ctx = OCSP_sendreq_new(b, path, req, -1);
+  do {
+    rv = OCSP_sendreq_nbio(&resp, ctx);
+    ink_hrtime_sleep(HRTIME_MSECONDS(1));
+  } while ((rv == -1) && BIO_should_retry(b) && (ink_get_hrtime() < end));
+
+  OCSP_REQ_CTX_free(ctx);
+
+  if (rv)
+    return resp;
+
+  return NULL;
+}
+
+static OCSP_RESPONSE *
+process_responder(OCSP_REQUEST *req,
+    char *host, char *path, char *port,
+    int req_timeout)
+{
+  BIO *cbio = NULL;
+  OCSP_RESPONSE *resp = NULL;
+  cbio = BIO_new_connect(host);
+  if (!cbio) {
+    goto end;
+  }
+  if (port) BIO_set_conn_port(cbio, port);
+
+  BIO_set_nbio(cbio, 1);
+  if (BIO_do_connect(cbio) <= 0 && !BIO_should_retry(cbio)) {
+    Debug("ssl", "process_responder: fail to connect to OCSP respond server");
+    goto end;
+  }
+  resp = query_responder(cbio, path, req, req_timeout);
+
+end:
+  if (cbio)
+    BIO_free_all(cbio);
+  return resp;
+}
+
+static bool
+stapling_refresh_response(certinfo *cinf, OCSP_RESPONSE **prsp)
+{
+  bool rv = true;
+  OCSP_REQUEST *req = NULL;
+  OCSP_CERTID *id = NULL;
+  char *host, *path, *port;
+  int ssl_flag = 0;
+  int req_timeout = -1;
+
+  Debug("ssl", "stapling_refresh_response: querying responder");
+  *prsp = NULL;
+
+  if (!OCSP_parse_url(cinf->uri, &host, &port, &path, &ssl_flag)) {
+    goto err;
+  }
+
+  req = OCSP_REQUEST_new();
+  if (!req)
+    goto err;
+  id = OCSP_CERTID_dup(cinf->cid);
+  if (!id)
+    goto err;
+  if (!OCSP_request_add0_id(req, id))
+    goto err;
+
+  req_timeout = SSLConfigParams::ssl_ocsp_request_timeout;
+  *prsp = process_responder(req, host, path, port, req_timeout);
+
+  if (*prsp == NULL) {
+    goto done;
+  }
+
+  if (OCSP_response_status(*prsp) == OCSP_RESPONSE_STATUS_SUCCESSFUL) {
+    Debug("ssl", "stapling_refresh_response: query response received");
+    stapling_check_response(cinf, *prsp);
+  } else {
+    Error("stapling_refresh_response: responder error");
+  }
+
+  if (!stapling_cache_response(*prsp, cinf)) {
+    Error("stapling_refresh_response: can not cache response");
+  } else {
+    Debug("ssl", "stapling_refresh_response: success to refresh response");
+  }
+
+done:
+  if (req)
+    OCSP_REQUEST_free(req);
+  if (*prsp)
+    OCSP_RESPONSE_free(*prsp);
+  return rv;
+
+err:
+  rv = false;
+  Debug("ssl", "stapling_refresh_response: fail to refresh response");
+  goto done;
+}
+
+void
+ocsp_update()
+{
+  SSL_CTX *ctx;
+  certinfo *cinf = NULL;
+  OCSP_RESPONSE *resp = NULL;
+  time_t current_time;
+
+  SSLCertificateConfig::scoped_config certLookup;
+  const unsigned ctxCount = certLookup->count();
+
+  for (unsigned i = 0; i < ctxCount; i++) {
+    ctx = certLookup->get(i);
+    cinf = stapling_get_cert_info(ctx);
+    if (cinf) {
+      ink_mutex_acquire(&cinf->stapling_mutex);
+      current_time = time(NULL);
+      if (cinf->resp_derlen == 0 || cinf->is_expire || cinf->expire_time < 
current_time) {
+        ink_mutex_release(&cinf->stapling_mutex);
+        if (stapling_refresh_response(cinf, &resp)) {
+          Note("Success to refresh OCSP response for 1 certificate.");
+        } else {
+          Note("Fail to refresh OCSP response for 1 certificate.");
+        }
+      } else {
+        ink_mutex_release(&cinf->stapling_mutex);
+      }
+    }
+  }
+}
+
+// RFC 6066 Section-8: Certificate Status Request
+int
+ssl_callback_ocsp_stapling(SSL *ssl)
+{
+  certinfo *cinf = NULL;
+  time_t current_time;
+
+  cinf = stapling_get_cert_info(ssl->ctx);
+  if (cinf == NULL) {
+    Debug("ssl", "ssl_callback_ocsp_stapling: fail to get certificate 
information");
+    return SSL_TLSEXT_ERR_NOACK;
+  }
+
+  ink_mutex_acquire(&cinf->stapling_mutex);
+  current_time = time(NULL);
+  if (cinf->resp_derlen == 0 || cinf->is_expire || cinf->expire_time < 
current_time) {
+    ink_mutex_release(&cinf->stapling_mutex);
+    Debug("ssl", "ssl_callback_ocsp_stapling: fail to get certificate status");
+    return SSL_TLSEXT_ERR_NOACK;
+  } else {
+    unsigned char *p = (unsigned char *)OPENSSL_malloc(cinf->resp_derlen);
+    unsigned int len = cinf->resp_derlen;
+    memcpy(p, cinf->resp_der, cinf->resp_derlen);
+    ink_mutex_release(&cinf->stapling_mutex);
+    SSL_set_tlsext_status_ocsp_resp(ssl, p, len);
+    Debug("ssl", "ssl_callback_ocsp_stapling: success to get certificate 
status");
+    return SSL_TLSEXT_ERR_OK;
+  }
+}
+
+#endif /* HAVE_OPENSSL_OCSP_STAPLING */

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/2621e676/iocore/net/P_OCSPStapling.h
----------------------------------------------------------------------
diff --git a/iocore/net/P_OCSPStapling.h b/iocore/net/P_OCSPStapling.h
new file mode 100644
index 0000000..d036651
--- /dev/null
+++ b/iocore/net/P_OCSPStapling.h
@@ -0,0 +1,35 @@
+/** @file
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#ifndef __P_OCSPSTAPLING_H__
+#define __P_OCSPSTAPLING_H__
+
+#include <openssl/ssl.h>
+
+#ifdef SSL_CTX_set_tlsext_status_cb
+#define HAVE_OPENSSL_OCSP_STAPLING 1
+  void ssl_stapling_ex_init();
+  bool ssl_stapling_init_cert(SSL_CTX *ctx, const char *certfile);
+  void ocsp_update();
+  int ssl_callback_ocsp_stapling(SSL *);
+#endif /* SSL_CTX_set_tlsext_status_cb */
+
+#endif /* __P_OCSPSTAPLING_H__ */

Reply via email to