/* 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) { 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 (strcasecmp(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) { 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", rc); 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); 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 */