| /* 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) { | |
| const ASN1_STRING *uri = value->location->d.uniformResourceIdentifier; | |
| result = modssl_ASN1_STRING_convert(pool, uri, 0); | |
| } | |
| } | |
| 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 || !u->scheme) { | |
| 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 */ |