diff options
Diffstat (limited to '')
-rw-r--r-- | modules/ssl/ssl_util_ocsp.c | 421 |
1 files changed, 421 insertions, 0 deletions
diff --git a/modules/ssl/ssl_util_ocsp.c b/modules/ssl/ssl_util_ocsp.c new file mode 100644 index 0000000..b66e151 --- /dev/null +++ b/modules/ssl/ssl_util_ocsp.c @@ -0,0 +1,421 @@ +/* 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" + +#ifndef OPENSSL_NO_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, + const apr_uri_t *proxy_uri) +{ + BIO *bio; + int len; + + len = i2d_OCSP_REQUEST(req, NULL); + + bio = BIO_new(BIO_s_mem()); + + BIO_printf(bio, "POST "); + /* Use full URL instead of URI in case of a request through a proxy */ + if (proxy_uri) { + BIO_printf(bio, "http://%s:%d", + uri->hostname, uri->port); + } + BIO_printf(bio, "%s%s%s HTTP/1.0\r\n" + "Host: %s:%d\r\n" + "Content-Type: application/ocsp-request\r\n" + "Content-Length: %d\r\n" + "\r\n", + uri->path ? uri->path : "/", + uri->query ? "?" : "", 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, + apr_interval_time_t timeout, + conn_rec *c, apr_pool_t *p, + const apr_uri_t *proxy_uri) +{ + apr_status_t rv; + apr_sockaddr_t *sa; + apr_socket_t *sd; + char buf[HUGE_STRING_LEN]; + int len; + const apr_uri_t *next_hop_uri; + + if (proxy_uri) { + next_hop_uri = proxy_uri; + } + else { + next_hop_uri = uri; + } + + rv = apr_sockaddr_info_get(&sa, next_hop_uri->hostname, APR_UNSPEC, + next_hop_uri->port, 0, p); + if (rv) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, APLOGNO(01972) + "could not resolve address of %s %s", + proxy_uri ? "proxy" : "OCSP responder", + next_hop_uri->hostinfo); + return NULL; + } + + /* establish a connection to the OCSP responder */ + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01973) + "connecting to %s '%s'", + proxy_uri ? "proxy" : "OCSP responder", + 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) { + apr_socket_timeout_set(sd, 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, APLOGNO(01974) + "could not connect to %s '%s'", + proxy_uri ? "proxy" : "OCSP responder", + next_hop_uri->hostinfo); + return NULL; + } + + /* send the request and get a response */ + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01975) + "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, APLOGNO(01976) + "failed to send request to OCSP responder '%s'", + uri->hostinfo); + return NULL; + } + } + + return sd; +} + +/* Return a pool-allocated 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, APLOGNO(01977) + "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, APLOGNO(01978) + "failed reading line from OCSP server"); + return NULL; + } + + if (len == 0) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, APLOGNO(02321) + "empty response from OCSP server"); + return NULL; + } + + if (line[len-1] != APR_ASCII_LF) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, APLOGNO(01979) + "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, APLOGNO(01980) + "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, APLOGNO(01981) + "OCSP response header: %s", line); + } + + if (count == MAX_HEADERS) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(01982) + "could not read response headers from OCSP server, " + "exceeded maximum count (%u)", MAX_HEADERS); + return NULL; + } + else if (!line) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(01983) + "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) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01984) + "OCSP response: got EOF"); + break; + } + if (rv != APR_SUCCESS) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, APLOGNO(01985) + "error reading response from OCSP server"); + return NULL; + } + if (len == 0) { + /* Ignore zero-length buckets (possible side-effect of + * line splitting). */ + apr_bucket_delete(e); + continue; + } + count += len; + if (count > MAX_CONTENT) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, APLOGNO(01986) + "OCSP response size exceeds %u byte limit", + MAX_CONTENT); + return NULL; + } + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01987) + "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) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(01988) + "failed to decode OCSP response data"); + ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, mySrvFromConn(c)); + } + + return response; +} + +OCSP_RESPONSE *modssl_dispatch_ocsp_request(const apr_uri_t *uri, + apr_interval_time_t timeout, + OCSP_REQUEST *request, + conn_rec *c, apr_pool_t *p) +{ + OCSP_RESPONSE *response = NULL; + apr_socket_t *sd; + BIO *bio; + const apr_uri_t *proxy_uri; + + proxy_uri = (mySrvConfigFromConn(c))->server->proxy_uri; + bio = serialize_request(request, uri, proxy_uri); + if (bio == NULL) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(01989) + "could not serialize OCSP request"); + ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, mySrvFromConn(c)); + return NULL; + } + + sd = send_request(bio, uri, timeout, c, p, proxy_uri); + 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; +} + +/* _________________________________________________________________ +** +** OCSP other certificate support +** _________________________________________________________________ +*/ + +/* + * Read a file that contains certificates in PEM format and + * return as a STACK. + */ + +static STACK_OF(X509) *modssl_read_ocsp_certificates(const char *file) +{ + BIO *bio; + X509 *x509; + unsigned long err; + STACK_OF(X509) *other_certs = NULL; + + if ((bio = BIO_new(BIO_s_file())) == NULL) + return NULL; + if (BIO_read_filename(bio, file) <= 0) { + BIO_free(bio); + return NULL; + } + + /* create new extra chain by loading the certs */ + ERR_clear_error(); + while ((x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL)) != NULL) { + if (!other_certs) { + other_certs = sk_X509_new_null(); + if (!other_certs) + return NULL; + } + + if (!sk_X509_push(other_certs, x509)) { + X509_free(x509); + sk_X509_pop_free(other_certs, X509_free); + BIO_free(bio); + return NULL; + } + } + /* Make sure that only the error is just an EOF */ + if ((err = ERR_peek_error()) > 0) { + if (!( ERR_GET_LIB(err) == ERR_LIB_PEM + && ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) { + BIO_free(bio); + sk_X509_pop_free(other_certs, X509_free); + return NULL; + } + while (ERR_get_error() > 0) ; + } + BIO_free(bio); + return other_certs; +} + +void ssl_init_ocsp_certificates(server_rec *s, modssl_ctx_t *mctx) +{ + /* + * Configure Trusted OCSP certificates. + */ + + if (!mctx->ocsp_certs_file) { + return; + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "Configuring Trusted OCSP certificates"); + + mctx->ocsp_certs = modssl_read_ocsp_certificates(mctx->ocsp_certs_file); + + if (!mctx->ocsp_certs) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, + "Unable to configure OCSP Trusted Certificates"); + ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, s); + ssl_die(s); + } + mctx->ocsp_verify_flags |= OCSP_TRUSTOTHER; +} + +#endif /* HAVE_OCSP */ |