]> granicus.if.org Git - apache/commitdiff
mod_ssl: Add support for OCSP validation of client certificates:
authorJoe Orton <jorton@apache.org>
Thu, 29 Nov 2007 11:18:40 +0000 (11:18 +0000)
committerJoe Orton <jorton@apache.org>
Thu, 29 Nov 2007 11:18:40 +0000 (11:18 +0000)
* modules/ssl/ssl_engine_config.c (modssl_ctx_init,
  modssl_ctx_cfg_merge): Initialize and merge OCSP config options.
  (ssl_cmd_SSLOCSPOverrideResponder, ssl_cmd_SSLOCSPDefaultResponder,
  ssl_cmd_SSLOCSPEnable): Add functions.

* modules/ssl/mod_ssl.c (ssl_config_cmds): Add config options.

* modules/ssl/ssl_private.h: Add prototypes, config options to
  modssl_ctx_t.

* modules/ssl/ssl_util_ocsp.c: New file, utility interface for
  dispatching OCSP requests.

* modules/ssl/ssl_engine_ocsp.c: New file, interface for performing
  OCSP validation.

* modules/ssl/ssl_engine_kernel.c (ssl_callback_SSLVerify): Perform
  OCSP validation if configured, and the cert is so-far verified to be
  trusted.  Fail if OCSP validation is configured an the optional-no-ca
  check tripped.

* modules/ssl/config.m4: Check for OCSP support, build new files.

* modules/ssl/mod_ssl.dsp: Build new files.

* modules/ssl/ssl_toolkit_compat.h: Include headers for OCSP
  interfaces.

PR: 41123
Submitted by: Marc Stern <marc.stern approach.be>, Joe Orton
Reviewed by: Steve Henson <steve openssl.org>

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@599385 13f79535-47bb-0310-9956-ffa450edef68

CHANGES
modules/ssl/config.m4
modules/ssl/mod_ssl.c
modules/ssl/mod_ssl.dsp
modules/ssl/ssl_engine_config.c
modules/ssl/ssl_engine_kernel.c
modules/ssl/ssl_engine_ocsp.c [new file with mode: 0644]
modules/ssl/ssl_private.h
modules/ssl/ssl_toolkit_compat.h
modules/ssl/ssl_util_ocsp.c [new file with mode: 0644]

diff --git a/CHANGES b/CHANGES
index 7fff2d6c307f2eb8c716fc9b0224a2998479f641..72c55401e4318ea364f021c14e3368bc5e857b16 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -2,6 +2,9 @@
 Changes with Apache 2.3.0
 [ When backported to 2.2.x, remove entry from this file ]
 
+  *) mod_ssl: Add support for OCSP validation of client certificates.
+     PR 41123.  [Marc Stern <marc.stern approach.be>, Joe Orton]
+
   *) mod_filter: Don't segfault on (unsupported) chained FilterProvider usage.
      PR 43956 [Nick Kew]
 
index 336d03587c4f02a860e8751ef9b374f6c336b61b..f95e1d4b27f38b3d82dc25a922718699c8fad62f 100644 (file)
@@ -135,6 +135,11 @@ AC_DEFUN([CHECK_SSL_MEMCACHE], [
   fi
 ])
 
+AC_DEFUN([CHECK_OCSP], [
+AC_CHECK_HEADERS(openssl/ocsp.h, 
+  [AC_DEFINE([HAVE_OCSP], 1, [Define if OCSP is supported by OpenSSL])]
+)
+])
 
 dnl #  start of module specific part
 APACHE_MODPATH_INIT(ssl)
@@ -163,6 +168,8 @@ ssl_scache_dc.lo dnl
 ssl_scache_memcache.lo dnl
 ssl_util.lo dnl
 ssl_util_ssl.lo dnl
+ssl_engine_ocsp.lo dnl
+ssl_util_ocsp.lo dnl
 "
 dnl #  hook module into the Autoconf mechanism (--enable-ssl option)
 APACHE_MODULE(ssl, [SSL/TLS support (mod_ssl)], $ssl_objs, , no, [
@@ -170,6 +177,7 @@ APACHE_MODULE(ssl, [SSL/TLS support (mod_ssl)], $ssl_objs, , no, [
     APR_SETVAR(MOD_SSL_LDADD, [\$(SSL_LIBS)])
     CHECK_DISTCACHE
     CHECK_SSL_MEMCACHE
+    CHECK_OCSP
     if test "x$enable_ssl" = "xshared"; then
        # The only symbol which needs to be exported is the module
        # structure, so ask libtool to hide everything else:
index dbaaca3951c37526359d8d0efedfbd275176c3c4..da2c252d4aaad383dbdd65bf6d6df2f723bffc2c 100644 (file)
@@ -178,6 +178,13 @@ static const command_rec ssl_config_cmds[] = {
                "Require a boolean expression to evaluate to true for granting access"
                "(arbitrary complex boolean expression - see manual)")
 
+    SSL_CMD_SRV(OCSPEnable, FLAG,
+               "Enable use of OCSP to verify certificate revocation (`on', `off')")
+    SSL_CMD_SRV(OCSPDefaultResponder, TAKE1,
+               "URL of the default OCSP Responder")
+    SSL_CMD_SRV(OCSPOverrideResponder, FLAG,
+               "Force use of the default responder URL (`on', `off')")
+
     /* Deprecated directives. */
     AP_INIT_RAW_ARGS("SSLLog", ap_set_deprecated, NULL, OR_ALL,
       "SSLLog directive is no longer supported - use ErrorLog."),
index 95d41991acf1d15c6a239fbbf5b0311e62479dd4..aba8f92f13a6e2b54c180a541ef6cccb83893010 100644 (file)
@@ -198,6 +198,14 @@ SOURCE=.\ssl_expr_scan.c
 # End Source File
 # Begin Source File
 
+SOURCE=.\ssl_engine_ocsp.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\ssl_util_ocsp.c
+# End Source File
+# Begin Source File
+
 SOURCE=.\ssl_scache.c
 # End Source File
 # Begin Source File
index 206149ae848e5b1b4f55df1e3ec1c36c89d9893e..7cda7667f43e7ea4e272f7fde9808e3d7b2363c8 100644 (file)
@@ -128,6 +128,10 @@ static void modssl_ctx_init(modssl_ctx_t *mctx)
     mctx->auth.cipher_suite   = NULL;
     mctx->auth.verify_depth   = UNSET;
     mctx->auth.verify_mode    = SSL_CVERIFY_UNSET;
+
+    mctx->ocsp_enabled        = FALSE;
+    mctx->ocsp_force_default  = FALSE;
+    mctx->ocsp_responder      = NULL;
 }
 
 static void modssl_ctx_init_proxy(SSLSrvConfigRec *sc,
@@ -217,6 +221,10 @@ static void modssl_ctx_cfg_merge(modssl_ctx_t *base,
     cfgMergeString(auth.cipher_suite);
     cfgMergeInt(auth.verify_depth);
     cfgMerge(auth.verify_mode, SSL_CVERIFY_UNSET);
+
+    cfgMergeBool(ocsp_enabled);
+    cfgMergeBool(ocsp_force_default);
+    cfgMerge(ocsp_responder, NULL);
 }
 
 static void modssl_ctx_cfg_merge_proxy(modssl_ctx_t *base,
@@ -1410,6 +1418,40 @@ const char *ssl_cmd_SSLUserName(cmd_parms *cmd, void *dcfg,
     return NULL;
 }
 
+const char *ssl_cmd_SSLOCSPEnable(cmd_parms *cmd, void *dcfg, int flag)
+{   
+    SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+
+    sc->server->ocsp_enabled = flag ? TRUE : FALSE;
+
+#ifndef HAVE_OCSP
+    if (flag) {
+        return "OCSP support not detected in SSL library; cannot enable "
+            "OCSP validation";
+    }
+#endif    
+
+    return NULL;
+}
+               
+const char *ssl_cmd_SSLOCSPOverrideResponder(cmd_parms *cmd, void *dcfg, int flag)
+{
+    SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+
+    sc->server->ocsp_force_default = flag ? TRUE : FALSE;
+
+    return NULL;
+}
+
+const char *ssl_cmd_SSLOCSPDefaultResponder(cmd_parms *cmd, void *dcfg, const char *arg)
+{   
+    SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+
+    sc->server->ocsp_responder = arg;
+
+    return NULL;
+}
+
 void ssl_hook_ConfigTest(apr_pool_t *pconf, server_rec *s)
 {
     if (!ap_exists_config_define("DUMP_CERTS")) {
index b3fcbe9df80f5cf702d0d5a4b51c1c177ba7e114..c0e9be74d0d73459ae379f4e9e96357106e87f63 100644 (file)
@@ -1293,12 +1293,35 @@ int ssl_callback_SSLVerify(int ok, X509_STORE_CTX *ctx)
     }
 
     /*
-     * Additionally perform CRL-based revocation checks
+     * Perform OCSP/CRL-based revocation checks
      */
     if (ok) {
         if (!(ok = ssl_callback_SSLVerify_CRL(ok, ctx, conn))) {
             errnum = X509_STORE_CTX_get_error(ctx);
         }
+        
+#ifdef HAVE_OCSP
+        /* If there was an optional verification error, it's not
+         * possible to perform OCSP validation since the issuer may be
+         * missing/untrusted.  Fail in that case. */
+        if (ok && ssl_verify_error_is_optional(errnum)
+            && sc->server->ocsp_enabled) {
+            X509_STORE_CTX_set_error(ctx, X509_V_ERR_APPLICATION_VERIFICATION);
+            errnum = X509_V_ERR_APPLICATION_VERIFICATION;
+            ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, conn,
+                          "cannot perform OCSP validation for cert "
+                          "if issuer has not been verified "
+                          "(optional_no_ca configured)");
+            ok = FALSE;
+        }
+
+        if (ok && sc->server->ocsp_enabled) {
+            ok = modssl_verify_ocsp(ctx, sc, s, conn, conn->pool);
+            if (!ok) {
+                errnum = X509_STORE_CTX_get_error(ctx);
+            }
+        }
+#endif
     }
 
     /*
diff --git a/modules/ssl/ssl_engine_ocsp.c b/modules/ssl/ssl_engine_ocsp.c
new file mode 100644 (file)
index 0000000..718137d
--- /dev/null
@@ -0,0 +1,253 @@
+/* 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 "ssl_private.h"
+
+#ifdef HAVE_OCSP
+#include "apr_base64.h"
+
+/* Return the responder URI specified in the given certificate, or
+ * NULL if none specified. */
+static const char *extract_responder_uri(X509 *cert, apr_pool_t *pool)
+{
+    STACK_OF(ACCESS_DESCRIPTION) *values;
+    char *result = NULL;
+    int j;
+
+    values = X509_get_ext_d2i(cert, NID_info_access, NULL, NULL);
+    if (!values) {
+        return NULL;
+    }
+
+    for (j = 0; j < sk_ACCESS_DESCRIPTION_num(values) && !result; j++) {
+        ACCESS_DESCRIPTION *value = sk_ACCESS_DESCRIPTION_value(values, j);
+        
+        /* Name found in extension, and is a URI: */
+        if (OBJ_obj2nid(value->method) == NID_ad_OCSP
+            && value->location->type == GEN_URI) {
+            result = apr_pstrdup(pool,
+                                 (char *)value->location->d.uniformResourceIdentifier->data);
+        }
+    }
+    
+    AUTHORITY_INFO_ACCESS_free(values);
+
+    return result;
+}
+
+/* Return the responder URI object which should be used in the given
+ * configuration for the given certificate, or NULL if none can be
+ * determined. */
+static apr_uri_t *determine_responder_uri(SSLSrvConfigRec *sc, X509 *cert, 
+                                          conn_rec *c, apr_pool_t *p)
+{
+    apr_uri_t *u = apr_palloc(p, sizeof *u);
+    const char *s;
+    apr_status_t rv;
+
+    /* Use default responder URL if forced by configuration, else use
+     * certificate-specified responder, falling back to default if
+     * necessary and possible. */
+    if (sc->server->ocsp_force_default) {
+        s = sc->server->ocsp_responder;
+    }
+    else {
+        s = extract_responder_uri(cert, p); 
+
+        if (s == NULL && sc->server->ocsp_responder) {
+            s = sc->server->ocsp_responder;
+        }
+    }
+
+    if (s == NULL) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+                      "no OCSP responder specified in certificate and "
+                      "no default configured");
+        return NULL;
+    }
+
+    rv = apr_uri_parse(p, s, u);
+    if (rv || !u->hostname || !u->path) {    
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, 
+                      "failed to parse OCSP responder URI '%s'", s);
+        return NULL;
+    }
+
+    if (strcasecmp(u->scheme, "http") != 0) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, 
+                      "cannot handle OCSP responder URI '%s'", s);
+        return NULL;
+    }
+
+    return u;
+}
+
+/* Create an OCSP request for the given certificate; returning the
+ * certificate ID in *certid and *issuer on success.  Returns the
+ * request object on success, or NULL on error. */
+static OCSP_REQUEST *create_request(X509_STORE_CTX *ctx, X509 *cert, 
+                                    OCSP_CERTID **certid, 
+                                    server_rec *s, apr_pool_t *p)
+{
+    OCSP_REQUEST *req = OCSP_REQUEST_new();
+
+    *certid = OCSP_cert_to_id(NULL, cert, ctx->current_issuer);
+    if (!*certid || !OCSP_request_add0_id(req, *certid)) {
+        ssl_log_ssl_error(APLOG_MARK, APLOG_ERR, s);
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
+                     "could not retrieve certificate id");
+        return NULL;
+    }
+    
+    OCSP_request_add1_nonce(req, 0, -1);
+    
+    return req;
+}
+        
+/* Verify the OCSP status of given certificate.  Returns
+ * V_OCSP_CERTSTATUS_* result code. */
+static int verify_ocsp_status(X509 *cert, X509_STORE_CTX *ctx, conn_rec *c, 
+                              SSLSrvConfigRec *sc, server_rec *s,
+                              apr_pool_t *pool) 
+{
+    int rc = V_OCSP_CERTSTATUS_GOOD;
+    OCSP_RESPONSE *response = NULL;
+    OCSP_BASICRESP *basicResponse = NULL;
+    OCSP_REQUEST *request = NULL;
+    OCSP_CERTID *certID = NULL;
+    apr_uri_t *ruri;
+   
+    ruri = determine_responder_uri(sc, cert, c, pool);
+    if (!ruri) {
+        return V_OCSP_CERTSTATUS_UNKNOWN;
+    }
+
+    request = create_request(ctx, cert, &certID, s, pool);
+    if (request) {
+        response = modssl_dispatch_ocsp_request(ruri, request, c, pool);
+    }
+
+    if (!request || !response) {
+        rc = V_OCSP_CERTSTATUS_UNKNOWN;
+    }
+    
+    if (rc == V_OCSP_CERTSTATUS_GOOD) {
+        int r = OCSP_response_status(response);
+
+        if (r != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
+            ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
+                        "OCSP response not successful: %d", rc);
+            rc = V_OCSP_CERTSTATUS_UNKNOWN;
+        }
+    }
+    
+    if (rc == V_OCSP_CERTSTATUS_GOOD) {
+        basicResponse = OCSP_response_get1_basic(response);
+        if (!basicResponse) {
+            ssl_log_ssl_error(APLOG_MARK, APLOG_ERR, s);
+            ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c,
+                          "could not retrieve OCSP basic response");
+            rc = V_OCSP_CERTSTATUS_UNKNOWN;
+        }
+    }
+    
+    if (rc == V_OCSP_CERTSTATUS_GOOD) {
+        if (OCSP_check_nonce(request, basicResponse) != 1) {
+            ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
+                        "Bad OCSP responder answer (bad nonce)");
+            rc = V_OCSP_CERTSTATUS_UNKNOWN;
+        }
+    }
+    
+    if (rc == V_OCSP_CERTSTATUS_GOOD) {
+        /* TODO: allow flags configuration. */
+        if (OCSP_basic_verify(basicResponse, NULL, ctx->ctx, 0) != 1) {
+            ssl_log_ssl_error(APLOG_MARK, APLOG_ERR, s);
+            ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
+                        "failed to verify the OCSP response");
+            rc = V_OCSP_CERTSTATUS_UNKNOWN;
+        }
+    }
+    
+    if (rc == V_OCSP_CERTSTATUS_GOOD) {
+        int reason = -1, status;
+
+        rc = OCSP_resp_find_status(basicResponse, certID, &status,
+                                   &reason, NULL, NULL, NULL);
+        if (rc != 1) {
+            ssl_log_ssl_error(APLOG_MARK, APLOG_ERR, s);
+            ssl_log_cxerror(APLOG_MARK, APLOG_ERR, 0, c, cert,
+                            "failed to retrieve OCSP response status");
+            rc = V_OCSP_CERTSTATUS_UNKNOWN;
+        }
+        else {
+            int level = 
+                (status == V_OCSP_CERTSTATUS_GOOD) ? APLOG_INFO : APLOG_ERR;
+            const char *result = 
+                status == V_OCSP_CERTSTATUS_GOOD ? "good" : 
+                (status == V_OCSP_CERTSTATUS_REVOKED ? "revoked" : "unknown");
+
+            ssl_log_cxerror(APLOG_MARK, level, 0, c, cert,
+                            "OCSP validation completed, "
+                            "certificate status: %s (%d, %d)",
+                            result, status, reason);
+            rc = status;
+        }
+    }
+    
+    if (request) OCSP_REQUEST_free(request);
+    if (response) OCSP_RESPONSE_free(response);
+    if (basicResponse) OCSP_BASICRESP_free(basicResponse);
+    /* certID is freed when the request is freed */
+
+    return rc;
+}
+
+int modssl_verify_ocsp(X509_STORE_CTX *ctx, SSLSrvConfigRec *sc, 
+                       server_rec *s, conn_rec *c, apr_pool_t *pool) 
+{
+    X509 *cert = X509_STORE_CTX_get_current_cert(ctx);
+    apr_pool_t *vpool;
+    int rv;
+    
+    /* Since the passed-inpool is likely to be the connection pool,
+     * create a temporary pool to constrain memory use. */
+    apr_pool_create(&vpool, pool);
+
+    rv = verify_ocsp_status(cert, ctx, c, sc, s, vpool);
+    
+    apr_pool_destroy(vpool);
+
+    /* Propagate the verification status back to the passed-in
+     * context. */
+    switch (rv) {
+    case V_OCSP_CERTSTATUS_GOOD:
+        X509_STORE_CTX_set_error(ctx, X509_V_OK);
+        break;
+        
+    case V_OCSP_CERTSTATUS_REVOKED:
+        X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REVOKED);
+        break;
+        
+    case V_OCSP_CERTSTATUS_UNKNOWN:
+        /* correct error code for application errors? */
+        X509_STORE_CTX_set_error(ctx, X509_V_ERR_APPLICATION_VERIFICATION);
+        break;
+    }
+
+    return rv == V_OCSP_CERTSTATUS_GOOD;
+} 
+#endif /* HAVE_OCSP */
index e1c0515e570c36554ef9d3ba0cf2b2337166ccf6..97613947581c16c73fea5f8afa3c7d5f123e2adc 100644 (file)
@@ -452,6 +452,12 @@ typedef struct {
     X509_STORE  *crl;
 
     modssl_auth_ctx_t auth;
+
+    BOOL ocsp_enabled; /* true if OCSP verification enabled */
+    BOOL ocsp_force_default; /* true if the default responder URL is
+                              * used regardless of per-cert URL */
+    const char *ocsp_responder; /* default responder URL */
+
 } modssl_ctx_t;
 
 struct SSLSrvConfigRec {
@@ -541,6 +547,10 @@ const char  *ssl_cmd_SSLProxyCARevocationFile(cmd_parms *, void *, const char *)
 const char  *ssl_cmd_SSLProxyMachineCertificatePath(cmd_parms *, void *, const char *);
 const char  *ssl_cmd_SSLProxyMachineCertificateFile(cmd_parms *, void *, const char *);
 
+const char *ssl_cmd_SSLOCSPOverrideResponder(cmd_parms *cmd, void *dcfg, int flag);
+const char *ssl_cmd_SSLOCSPDefaultResponder(cmd_parms *cmd, void *dcfg, const char *arg);
+const char *ssl_cmd_SSLOCSPEnable(cmd_parms *cmd, void *dcfg, int flag);
+
 /**  module initialization  */
 int          ssl_init_Module(apr_pool_t *, apr_pool_t *, apr_pool_t *, server_rec *);
 void         ssl_init_Engine(server_rec *, apr_pool_t *);
@@ -700,6 +710,23 @@ void         ssl_var_log_config_register(apr_pool_t *p);
 
 #define APR_SHM_MAXSIZE (64 * 1024 * 1024)
 
+#ifdef HAVE_OCSP
+/* Perform OCSP verification using the given context and
+ * configuration.  Returns non-zero on success or zero on failure.  On
+ * failure, the context error code is set. */
+int modssl_verify_ocsp(X509_STORE_CTX *ctx, 
+                       SSLSrvConfigRec *sc, server_rec *s, conn_rec *c, 
+                       apr_pool_t *pool);
+
+/* OCSP helper interface; dispatches the given OCSP request to the
+ * responder at the given URI.  Returns the decoded OCSP response
+ * object, or NULL on error (in which case, errors will have been
+ * logged).  Pool 'p' is used for temporary allocations. */
+OCSP_RESPONSE *modssl_dispatch_ocsp_request(const apr_uri_t *uri, 
+                                            OCSP_REQUEST *request,
+                                            conn_rec *c, apr_pool_t *p);
+#endif
+
 #endif /* SSL_PRIVATE_H */
 /** @} */
 
index 430127c1ec504509128077b94cfa8546eea355e5..e3850e2e0aba9251edd4cde68c2783d65ebe850e 100644 (file)
 #include <openssl/evp.h>
 #include <openssl/rand.h>
 #include <openssl/x509v3.h>
+
+#ifdef HAVE_OCSP
+#include <openssl/x509_vfy.h>
+#include <openssl/ocsp.h>
+#endif
+
 /** Avoid tripping over an engine build installed globally and detected
  * when the user points at an explicit non-engine flavor of OpenSSL
  */
diff --git a/modules/ssl/ssl_util_ocsp.c b/modules/ssl/ssl_util_ocsp.c
new file mode 100644 (file)
index 0000000..9a7d0a9
--- /dev/null
@@ -0,0 +1,299 @@
+/* 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.
+ */
+
+/* This file implements an OCSP client including a toy HTTP/1.0
+ * client.  Once httpd depends on a real HTTP client library, most of
+ * this can be thrown away. */
+
+#include "ssl_private.h"
+
+#ifdef HAVE_OCSP
+
+#include "apr_buckets.h"
+#include "apr_uri.h"
+
+/* Serialize an OCSP request which will be sent to the responder at
+ * given URI to a memory BIO object, which is returned. */
+static BIO *serialize_request(OCSP_REQUEST *req, const apr_uri_t *uri)
+{
+    BIO *bio;
+    int len;
+
+    len = i2d_OCSP_REQUEST(req, NULL);
+
+    bio = BIO_new(BIO_s_mem());
+
+    BIO_printf(bio, "POST %s%s HTTP/1.0\r\n"
+               "Host: %s:%d\r\n"
+               "Content-Length: %d\r\n"
+               "\r\n", 
+               uri->path, uri->query ? uri->query : "",
+               uri->hostname, uri->port, len);
+
+    if (i2d_OCSP_REQUEST_bio(bio, req) != 1) {
+        BIO_free(bio);
+        return NULL;
+    }
+
+    return bio;
+}
+
+/* Send the OCSP request serialized into BIO 'request' to the
+ * responder at given server given by URI.  Returns socket object or
+ * NULL on error. */
+static apr_socket_t *send_request(BIO *request, const apr_uri_t *uri, 
+                                  conn_rec *c, apr_pool_t *p)
+{
+    apr_status_t rv;
+    apr_sockaddr_t *sa;
+    apr_socket_t *sd;
+    char buf[HUGE_STRING_LEN];
+    int len;
+
+    rv = apr_sockaddr_info_get(&sa, uri->hostname, APR_UNSPEC, uri->port, 0, p);
+    if (rv) {
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c,
+                      "could not resolve address of OCSP responder %s", 
+                      uri->hostinfo);
+        return NULL;
+    }
+    
+    /* establish a connection to the OCSP responder */ 
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, 
+                  "connecting to OCSP responder '%s'", uri->hostinfo);
+
+    /* Cycle through address until a connect() succeeds. */
+    for (; sa; sa = sa->next) {
+        rv = apr_socket_create(&sd, sa->family, SOCK_STREAM, APR_PROTO_TCP, p);
+        if (rv == APR_SUCCESS) {
+            /* Inherit the default I/O timeout. */
+            apr_socket_timeout_set(sd, c->base_server->timeout);
+
+            rv = apr_socket_connect(sd, sa);
+            if (rv == APR_SUCCESS) {
+                break;
+            }
+            apr_socket_close(sd);
+        }
+    }
+
+    if (sa == NULL) {
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c,
+                      "could not connect to OCSP responder '%s'",
+                      uri->hostinfo);
+        apr_socket_close(sd);
+        return NULL;
+    }
+
+    /* send the request and get a response */ 
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, 
+                 "sending request to OCSP responder");
+
+    while ((len = BIO_read(request, buf, sizeof buf)) > 0) {
+        char *wbuf = buf;
+        apr_size_t remain = len;
+        
+        do {
+            apr_size_t wlen = remain;
+
+            rv = apr_socket_send(sd, wbuf, &wlen);
+            wbuf += remain;
+            remain -= wlen;
+        } while (rv == APR_SUCCESS && remain > 0);
+
+        if (rv) {
+            apr_socket_close(sd);
+            ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c,
+                          "failed to send request to OCSP responder '%s'",
+                          uri->hostinfo);
+            return NULL;
+        }
+    }
+
+    return sd;
+}
+
+/* Return a pool-alocated NUL-terminated line, with CRLF stripped,
+ * read from brigade 'bbin' using 'bbout' as temporary storage. */
+static char *get_line(apr_bucket_brigade *bbout, apr_bucket_brigade *bbin,
+                      conn_rec *c, apr_pool_t *p)
+{
+    apr_status_t rv;
+    apr_size_t len;
+    char *line;
+
+    apr_brigade_cleanup(bbout);
+
+    rv = apr_brigade_split_line(bbout, bbin, APR_BLOCK_READ, 8192);
+    if (rv) {
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c,
+                      "failed reading line from OCSP server");
+        return NULL;
+    }
+    
+    rv = apr_brigade_pflatten(bbout, &line, &len, p);
+    if (rv) {
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c,
+                      "failed reading line from OCSP server");
+        return NULL;
+    }
+
+    if (len && line[len-1] != APR_ASCII_LF) {
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c,
+                      "response header line too long from OCSP server");
+        return NULL;
+    }
+
+    line[len-1] = '\0';
+    if (len > 1 && line[len-2] == APR_ASCII_CR) {
+        line[len-2] = '\0';
+    }
+
+    return line;
+}
+
+/* Maximum values to prevent eating RAM forever. */
+#define MAX_HEADERS (256)
+#define MAX_CONTENT (2048 * 1024)
+
+/* Read the OCSP response from the socket 'sd', using temporary memory
+ * BIO 'bio', and return the decoded OCSP response object, or NULL on
+ * error. */
+static OCSP_RESPONSE *read_response(apr_socket_t *sd, BIO *bio, conn_rec *c,
+                                    apr_pool_t *p)
+{
+    apr_bucket_brigade *bb, *tmpbb;
+    OCSP_RESPONSE *response;
+    char *line;
+    apr_size_t count;
+    apr_int64_t code;
+
+    /* Using brigades for response parsing is much simpler than using
+     * apr_socket_* directly. */
+    bb = apr_brigade_create(p, c->bucket_alloc);
+    tmpbb = apr_brigade_create(p, c->bucket_alloc);
+    APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_socket_create(sd, c->bucket_alloc));
+
+    line = get_line(tmpbb, bb, c, p);
+    if (!line || strncmp(line, "HTTP/", 5)
+        || (line = ap_strchr(line, ' ')) == NULL
+        || (code = apr_atoi64(++line)) < 200 || code > 299) {
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c,
+                      "bad response from OCSP server: %s",
+                      line ? line : "(none)");
+        return NULL;
+    }
+
+    /* Read till end of headers; don't have to even bother parsing the
+     * Content-Length since the server is obliged to close the
+     * connection after the response anyway for HTTP/1.0. */
+    count = 0;
+    while ((line = get_line(tmpbb, bb, c, p)) != NULL && line[0]
+           && ++count < MAX_HEADERS) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+                      "OCSP response header: %s", line);
+    }
+
+    if (!line) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+                      "could not read response header from OCSP server");
+        return NULL;
+    }
+
+    /* Read the response body into the memory BIO. */
+    count = 0;
+    while (!APR_BRIGADE_EMPTY(bb)) {
+        const char *data;
+        apr_size_t len;
+        apr_status_t rv;
+        apr_bucket *e = APR_BRIGADE_FIRST(bb);
+
+        rv = apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
+        if (rv == APR_EOF || (rv == APR_SUCCESS && len == 0)) {
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+                          "OCSP response: got EOF");
+            break;
+        }
+        if (rv != APR_SUCCESS) {
+            ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c,
+                          "error reading response from OCSP server");
+            return NULL;
+        }
+        count += len;
+        if (count > MAX_CONTENT) {
+            ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c,
+                          "OCSP response size exceeds %u byte limit",
+                          MAX_CONTENT);
+            return NULL;
+        }
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+                      "OCSP response: got %" APR_SIZE_T_FMT 
+                      " bytes, %" APR_SIZE_T_FMT " total", len, count);
+
+        BIO_write(bio, data, (int)len);
+        apr_bucket_delete(e);
+    }
+
+    apr_brigade_destroy(bb);
+    apr_brigade_destroy(tmpbb);
+
+    /* Finally decode the OCSP response from what's stored in the
+     * bio. */
+    response = d2i_OCSP_RESPONSE_bio(bio, NULL);
+    if (response == NULL) {
+        ssl_log_ssl_error(APLOG_MARK, APLOG_ERR, c->base_server);
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c,
+                      "failed to decode OCSP response data");
+    }
+
+    return response;
+}
+
+OCSP_RESPONSE *modssl_dispatch_ocsp_request(const apr_uri_t *uri, 
+                                            OCSP_REQUEST *request,
+                                            conn_rec *c, apr_pool_t *p) 
+{
+    OCSP_RESPONSE *response = NULL;
+    apr_socket_t *sd;
+    BIO *bio;
+
+    bio = serialize_request(request, uri);
+    if (bio == NULL) {
+        ssl_log_ssl_error(APLOG_MARK, APLOG_ERR, c->base_server);
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c,
+                      "could not serialize OCSP request");
+        return NULL;
+    }
+    
+    sd = send_request(bio, uri, c, p);
+    if (sd == NULL) {
+        /* Errors already logged. */
+        BIO_free(bio);
+        return NULL;
+    }
+
+    /* Clear the BIO contents, ready for the response. */
+    (void)BIO_reset(bio);
+
+    response = read_response(sd, bio, c, p);
+
+    apr_socket_close(sd);
+    BIO_free(bio);
+
+    return response;
+}
+
+#endif /* HAVE_OCSP */