mirror of
https://github.com/apache/httpd.git
synced 2025-08-08 15:02:10 +03:00
mod_ssl: Add support for OCSP validation of client certificates:
* 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
This commit is contained in:
3
CHANGES
3
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]
|
||||
|
||||
|
@@ -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:
|
||||
|
@@ -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."),
|
||||
|
@@ -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
|
||||
|
@@ -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")) {
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
/*
|
||||
|
253
modules/ssl/ssl_engine_ocsp.c
Normal file
253
modules/ssl/ssl_engine_ocsp.c
Normal file
@@ -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 */
|
@@ -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 */
|
||||
/** @} */
|
||||
|
||||
|
@@ -38,6 +38,12 @@
|
||||
#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
|
||||
*/
|
||||
|
299
modules/ssl/ssl_util_ocsp.c
Normal file
299
modules/ssl/ssl_util_ocsp.c
Normal file
@@ -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 */
|
Reference in New Issue
Block a user