diff options
Diffstat (limited to 'modules/ssl/ssl_engine_ocsp.c')
-rw-r--r-- | modules/ssl/ssl_engine_ocsp.c | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/modules/ssl/ssl_engine_ocsp.c b/modules/ssl/ssl_engine_ocsp.c new file mode 100644 index 0000000..5e04512 --- /dev/null +++ b/modules/ssl/ssl_engine_ocsp.c @@ -0,0 +1,312 @@ +/* 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" + +#ifndef OPENSSL_NO_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 == TRUE) { + 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, APLOGNO(01918) + "no OCSP responder specified in certificate and " + "no default configured"); + return NULL; + } + + rv = apr_uri_parse(p, s, u); + if (rv || !u->hostname) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(01919) + "failed to parse OCSP responder URI '%s'", s); + return NULL; + } + + if (ap_cstr_casecmp(u->scheme, "http") != 0) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, c, APLOGNO(01920) + "cannot handle OCSP responder URI '%s'", s); + return NULL; + } + + if (!u->port) { + u->port = apr_uri_port_of_scheme(u->scheme); + } + + 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, + SSLSrvConfigRec *sc) +{ + OCSP_REQUEST *req = OCSP_REQUEST_new(); + + *certid = OCSP_cert_to_id(NULL, cert, X509_STORE_CTX_get0_current_issuer(ctx)); + if (!*certid || !OCSP_request_add0_id(req, *certid)) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01921) + "could not retrieve certificate id"); + ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, s); + return NULL; + } + + if (sc->server->ocsp_use_request_nonce != FALSE) { + 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) { + if (sc->server->ocsp_mask & SSL_OCSPCHECK_NO_OCSP_FOR_CERT_OK) { + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, + "Skipping OCSP check for certificate cos no OCSP URL" + " found and no_ocsp_for_cert_ok is set"); + return V_OCSP_CERTSTATUS_GOOD; + } else { + return V_OCSP_CERTSTATUS_UNKNOWN; + } + } + + request = create_request(ctx, cert, &certID, s, pool, sc); + if (request) { + apr_interval_time_t to = sc->server->ocsp_responder_timeout == UNSET ? + apr_time_from_sec(DEFAULT_OCSP_TIMEOUT) : + sc->server->ocsp_responder_timeout; + response = modssl_dispatch_ocsp_request(ruri, to, 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, APLOGNO(01922) + "OCSP response not successful: %d", r); + rc = V_OCSP_CERTSTATUS_UNKNOWN; + } + } + + if (rc == V_OCSP_CERTSTATUS_GOOD) { + basicResponse = OCSP_response_get1_basic(response); + if (!basicResponse) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(01923) + "could not retrieve OCSP basic response"); + ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, s); + rc = V_OCSP_CERTSTATUS_UNKNOWN; + } + } + + if (rc == V_OCSP_CERTSTATUS_GOOD && + sc->server->ocsp_use_request_nonce != FALSE && + OCSP_check_nonce(request, basicResponse) != 1) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01924) + "Bad OCSP responder answer (bad nonce)"); + rc = V_OCSP_CERTSTATUS_UNKNOWN; + } + + if (rc == V_OCSP_CERTSTATUS_GOOD) { + /* Check if OCSP certificate verification required */ + if (sc->server->ocsp_noverify != TRUE) { + /* Modify OCSP response verification to include OCSP Responder cert */ + if (OCSP_basic_verify(basicResponse, sc->server->ocsp_certs, X509_STORE_CTX_get0_store(ctx), + sc->server->ocsp_verify_flags) != 1) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(01925) + "failed to verify the OCSP response"); + ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, s); + rc = V_OCSP_CERTSTATUS_UNKNOWN; + } + } + } + + if (rc == V_OCSP_CERTSTATUS_GOOD) { + int reason = -1, status; + ASN1_GENERALIZEDTIME *thisup = NULL, *nextup = NULL; + + rc = OCSP_resp_find_status(basicResponse, certID, &status, + &reason, NULL, &thisup, &nextup); + if (rc != 1) { + ssl_log_cxerror(SSLLOG_MARK, APLOG_ERR, 0, c, cert, APLOGNO(02272) + "failed to retrieve OCSP response status"); + ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, s); + rc = V_OCSP_CERTSTATUS_UNKNOWN; + } + else { + rc = status; + } + + /* Check whether the response is inside the defined validity + * period; otherwise fail. */ + if (rc != V_OCSP_CERTSTATUS_UNKNOWN) { + long resptime_skew = sc->server->ocsp_resptime_skew == UNSET ? + DEFAULT_OCSP_MAX_SKEW : sc->server->ocsp_resptime_skew; + /* oscp_resp_maxage can be passed verbatim - UNSET (-1) means + * that responses can be of any age as long as nextup is in the + * future. */ + int vrc = OCSP_check_validity(thisup, nextup, resptime_skew, + sc->server->ocsp_resp_maxage); + if (vrc != 1) { + ssl_log_cxerror(SSLLOG_MARK, APLOG_ERR, 0, c, cert, APLOGNO(02273) + "OCSP response outside validity period"); + ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, s); + rc = V_OCSP_CERTSTATUS_UNKNOWN; + } + } + + { + 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(SSLLOG_MARK, level, 0, c, cert, APLOGNO(03239) + "OCSP validation completed, " + "certificate status: %s (%d, %d)", + result, status, reason); + } + } + + 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; + + if (!cert) { + /* starting with OpenSSL 1.0, X509_STORE_CTX_get_current_cert() + * may yield NULL. Return early, but leave the ctx error as is. */ + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, + "No cert available to check with OCSP"); + return 1; + } + else if (X509_check_issued(cert,cert) == X509_V_OK) { + /* don't do OCSP checking for valid self-issued certs */ + ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, + "Skipping OCSP check for valid self-issued cert"); + X509_STORE_CTX_set_error(ctx, X509_V_OK); + return 1; + } + + /* Create a temporary pool to constrain memory use (the passed-in + * pool may be e.g. a connection pool). */ + apr_pool_create(&vpool, pool); + apr_pool_tag(vpool, "modssl_verify_ocsp"); + + 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 */ |