From 74adb43cc1a7b415b01795879595e7f1281308dc Mon Sep 17 00:00:00 2001 From: "Dr. Lars Voelker" Date: Fri, 9 Jun 2017 15:52:50 +0200 Subject: [PATCH] Adding OCSP Stapling support to mosquitto Adding OCSP Stapling support to mosquitto, so that the TLS client side requests the certificate status and checks it. This code uses the OpenSSL-based OCSP implementation and is somewhat based on the libcurl code for OCSP stapling. Signed-off-by: Dr. Lars Voelker --- lib/CMakeLists.txt | 2 +- lib/Makefile | 4 + lib/cpp/mosquittopp.cpp | 4 + lib/cpp/mosquittopp.h | 1 + lib/linker.version | 1 + lib/mosquitto.c | 19 +++++ lib/mosquitto.h | 23 +++++- lib/mosquitto_internal.h | 1 + lib/net_mosq.c | 17 +++++ lib/net_mosq.h | 1 + lib/net_mosq_ocsp.c | 159 +++++++++++++++++++++++++++++++++++++++ man/mosquitto.conf.5.xml | 7 ++ src/CMakeLists.txt | 2 +- src/Makefile | 5 +- src/bridge.c | 1 + src/conf.c | 11 +++ src/mosquitto_broker.h | 1 + 17 files changed, 255 insertions(+), 4 deletions(-) create mode 100644 lib/net_mosq_ocsp.c diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 07be5d93..83f64e6c 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -32,7 +32,7 @@ add_library(libmosquitto SHARED mosquitto.c mosquitto.h mosquitto_internal.h mqtt3_protocol.h - net_mosq.c net_mosq.h + net_mosq_ocsp.c net_mosq.c net_mosq.h read_handle.c read_handle.h read_handle_client.c read_handle_shared.c diff --git a/lib/Makefile b/lib/Makefile index 825fcead..370a3c03 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -6,6 +6,7 @@ MOSQ_OBJS=mosquitto.o \ logging_mosq.o \ memory_mosq.o \ messages_mosq.o \ + net_mosq_ocsp.o \ net_mosq.o \ read_handle.o \ read_handle_client.o \ @@ -59,6 +60,9 @@ messages_mosq.o : messages_mosq.c messages_mosq.h memory_mosq.o : memory_mosq.c memory_mosq.h ${CROSS_COMPILE}$(CC) $(LIB_CFLAGS) -c $< -o $@ +net_mosq_ocsp.o : net_mosq_ocsp.c net_mosq.h + ${CROSS_COMPILE}$(CC) $(LIB_CFLAGS) -c $< -o $@ + net_mosq.o : net_mosq.c net_mosq.h ${CROSS_COMPILE}$(CC) $(LIB_CFLAGS) -c $< -o $@ diff --git a/lib/cpp/mosquittopp.cpp b/lib/cpp/mosquittopp.cpp index 0a22b80f..0b45749a 100644 --- a/lib/cpp/mosquittopp.cpp +++ b/lib/cpp/mosquittopp.cpp @@ -305,4 +305,8 @@ int mosquittopp::tls_psk_set(const char *psk, const char *identity, const char * return mosquitto_tls_psk_set(m_mosq, psk, identity, ciphers); } +int mosquittopp::tls_ocsp_set(int ocsp_reqs) +{ + return mosquitto_tls_ocsp_set(m_mosq, ocsp_reqs); +} } diff --git a/lib/cpp/mosquittopp.h b/lib/cpp/mosquittopp.h index 9b0cb694..dcaa7d19 100644 --- a/lib/cpp/mosquittopp.h +++ b/lib/cpp/mosquittopp.h @@ -78,6 +78,7 @@ class mosqpp_EXPORT mosquittopp { int tls_opts_set(int cert_reqs, const char *tls_version=NULL, const char *ciphers=NULL); int tls_insecure_set(bool value); int tls_psk_set(const char *psk, const char *identity, const char *ciphers=NULL); + int tls_ocsp_set(int ocsp_reqs); int opts_set(enum mosq_opt_t option, void *value); int loop(int timeout=-1, int max_packets=1); diff --git a/lib/linker.version b/lib/linker.version index 59eed794..5abcccc9 100644 --- a/lib/linker.version +++ b/lib/linker.version @@ -77,4 +77,5 @@ MOSQ_1.4 { mosquitto_pub_topic_check; mosquitto_sub_topic_check; mosquitto_socks5_set; + mosquitto_tls_ocsp_set; } MOSQ_1.3; diff --git a/lib/mosquitto.c b/lib/mosquitto.c index be8e62e5..c5c2c3c5 100644 --- a/lib/mosquitto.c +++ b/lib/mosquitto.c @@ -203,6 +203,7 @@ int mosquitto_reinitialise(struct mosquitto *mosq, const char *id, bool clean_se mosq->tls_cert_reqs = SSL_VERIFY_PEER; mosq->tls_insecure = false; mosq->want_write = false; + mosq->tls_ocsp_required = false; #endif #ifdef WITH_THREADING pthread_mutex_init(&mosq->callback_mutex, NULL); @@ -833,6 +834,24 @@ int mosquitto_tls_psk_set(struct mosquitto *mosq, const char *psk, const char *i } +int mosquitto_tls_ocsp_set(struct mosquitto *mosq, int ocsp_reqs) +{ +#ifdef WITH_TLS + if (ocsp_reqs==0) { + mosq->tls_ocsp_required = false; + return MOSQ_ERR_SUCCESS; + } + + if (ocsp_reqs==1) { + mosq->tls_ocsp_required = true; + return MOSQ_ERR_SUCCESS; + } +#endif + + return MOSQ_ERR_INVAL; +} + + int mosquitto_loop(struct mosquitto *mosq, int timeout, int max_packets) { #ifdef HAVE_PSELECT diff --git a/lib/mosquitto.h b/lib/mosquitto.h index 02e4ff67..ac4f9484 100644 --- a/lib/mosquitto.h +++ b/lib/mosquitto.h @@ -80,7 +80,8 @@ enum mosq_err_t { MOSQ_ERR_UNKNOWN = 13, MOSQ_ERR_ERRNO = 14, MOSQ_ERR_EAI = 15, - MOSQ_ERR_PROXY = 16 + MOSQ_ERR_PROXY = 16, + MOSQ_ERR_OCSP = 17 }; /* Error values */ @@ -1086,6 +1087,26 @@ libmosq_EXPORT int mosquitto_tls_opts_set(struct mosquitto *mosq, int cert_reqs, */ libmosq_EXPORT int mosquitto_tls_psk_set(struct mosquitto *mosq, const char *psk, const char *identity, const char *ciphers); +/* + * Function: mosquitto_tls_ocsp_set + * + * Set advanced SSL/TLS options. Must be called before . + * + * Parameters: + * mosq - a valid mosquitto instance. + * ocsp_reqs - whether OCSP checking is required: + * 0 - no checking required + * 1 - checking required + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * + * See Also: + * + */ +libmosq_EXPORT int mosquitto_tls_ocsp_set(struct mosquitto *mosq, int ocsp_reqs); + /* * Function: mosquitto_connect_callback_set * diff --git a/lib/mosquitto_internal.h b/lib/mosquitto_internal.h index 8d3014dd..4857fe3b 100644 --- a/lib/mosquitto_internal.h +++ b/lib/mosquitto_internal.h @@ -187,6 +187,7 @@ struct mosquitto { char *tls_psk_identity; int tls_cert_reqs; bool tls_insecure; + bool tls_ocsp_required; #endif bool want_write; bool want_connect; diff --git a/lib/net_mosq.c b/lib/net_mosq.c index 063c4a22..b520d155 100644 --- a/lib/net_mosq.c +++ b/lib/net_mosq.c @@ -465,6 +465,23 @@ int mosquitto__socket_connect_tls(struct mosquitto *mosq) { int ret, err; ERR_clear_error(); + long res; + if (mosq->tls_ocsp_required) { + // Note: OCSP is available in all currently supported OpenSSL versions. + if ((res=SSL_set_tlsext_status_type(mosq->ssl, TLSEXT_STATUSTYPE_ocsp)) != 1) { + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "Could not activate OCSP (error: %ld)", res); + return MOSQ_ERR_OCSP; + } + if ((res=SSL_CTX_set_tlsext_status_cb(mosq->ssl_ctx, _mosquitto_verify_ocsp_status_cb)) != 1) { + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "Could not activate OCSP (error: %ld)", res); + return MOSQ_ERR_OCSP; + } + if ((res=SSL_CTX_set_tlsext_status_arg(mosq->ssl_ctx, mosq)) != 1) { + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "Could not activate OCSP (error: %ld)", res); + return MOSQ_ERR_OCSP; + } + } + ret = SSL_connect(mosq->ssl); if(ret != 1) { err = SSL_get_error(mosq->ssl, ret); diff --git a/lib/net_mosq.h b/lib/net_mosq.h index b504ebc0..227ea187 100644 --- a/lib/net_mosq.h +++ b/lib/net_mosq.h @@ -90,6 +90,7 @@ int _mosquitto_packet_read(struct mosquitto *mosq); #ifdef WITH_TLS int _mosquitto_socket_apply_tls(struct mosquitto *mosq); int mosquitto__socket_connect_tls(struct mosquitto *mosq); +int _mosquitto_verify_ocsp_status_cb(SSL * ssl, void *arg); #endif #endif diff --git a/lib/net_mosq_ocsp.c b/lib/net_mosq_ocsp.c new file mode 100644 index 00000000..0044e6bb --- /dev/null +++ b/lib/net_mosq_ocsp.c @@ -0,0 +1,159 @@ +/* +Copyright (c) 2009-2014 Roger Light +Copyright (c) 2017 Bayerische Motoren Werke Aktiengesellschaft (BMW AG), Dr. Lars Voelker + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Dr. Lars Voelker, BMW AG +*/ + +/* +COPYRIGHT AND PERMISSION NOTICE of curl on which the ocsp code is based: + +Copyright (c) 1996 - 2016, Daniel Stenberg, , and many +contributors, see the THANKS file. + +All rights reserved. + +Permission to use, copy, modify, and distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright +notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall not +be used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization of the copyright holder. +*/ + +#ifdef WITH_TLS +#include +#include +#include +#include + +#include +#include +#include + +int _mosquitto_verify_ocsp_status_cb(SSL * ssl, void *arg) +{ + struct mosquitto *mosq = (struct mosquitto *)arg; + int ocsp_status, result2, i; + unsigned char *p; + const unsigned char *cp; + OCSP_RESPONSE *rsp = NULL; + OCSP_BASICRESP *br = NULL; + X509_STORE *st = NULL; + STACK_OF(X509) *ch = NULL; + + long len = SSL_get_tlsext_status_ocsp_resp(mosq->ssl, &p); + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "OCSP: SSL_get_tlsext_status_ocsp_resp returned %ld bytes", len); + + // the following functions expect a const pointer + cp = (const unsigned char *)p; + + if (!cp || len <= 0) { + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "OCSP: no response"); + goto end; + } + + + rsp = d2i_OCSP_RESPONSE(NULL, &cp, len); + if (rsp==NULL) { + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "OCSP: invalid response"); + goto end; + } + + ocsp_status = OCSP_response_status(rsp); + if(ocsp_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "OCSP: invalid status: %s (%d)", + OCSP_response_status_str(ocsp_status), ocsp_status); + goto end; + } + + br = OCSP_response_get1_basic(rsp); + if (!br) { + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "OCSP: invalid response"); + goto end; + } + + ch = SSL_get_peer_cert_chain(mosq->ssl); + if (sk_X509_num(ch) <= 0) { + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "OCSP: we did not receive certificates of the server (num: %d)", sk_X509_num(ch)); + goto end; + } + + st = SSL_CTX_get_cert_store(mosq->ssl_ctx); + + // Note: + // Other checkers often fix problems in OpenSSL before 1.0.2a (e.g. libcurl). + // For all currently supported versions of the OpenSSL project, this is not needed anymore. + + if ((result2=OCSP_basic_verify(br, ch, st, 0)) <= 0) { + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "OCSP: response verification failed (error: %d)", result2); + goto end; + } + + for(i = 0; i < OCSP_resp_count(br); i++) { + int cert_status, crl_reason; + OCSP_SINGLERESP *single = NULL; + + ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd; + + single = OCSP_resp_get0(br, i); + if(!single) + continue; + + cert_status = OCSP_single_get0_status(single, &crl_reason, &rev, &thisupd, &nextupd); + + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "OCSP: SSL certificate status: %s (%d)", + OCSP_cert_status_str(cert_status), cert_status); + + switch(cert_status) { + case V_OCSP_CERTSTATUS_GOOD: + // Note: A OCSP stapling result will be accepted up to 5 minutes after it expired! + if(!OCSP_check_validity(thisupd, nextupd, 300L, -1L)) { + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "OCSP: OCSP response has expired"); + goto end; + } + break; + + case V_OCSP_CERTSTATUS_REVOKED: + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "OCSP: SSL certificate revocation reason: %s (%d)", + OCSP_crl_reason_str(crl_reason), crl_reason); + goto end; + + case V_OCSP_CERTSTATUS_UNKNOWN: + goto end; + + default: + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "OCSP: SSL certificate revocation status unknown"); + goto end; + } + } + + if (br!=NULL) OCSP_BASICRESP_free(br); + if (rsp!=NULL) OCSP_RESPONSE_free(rsp); + return 1; // OK + +end: + if (br!=NULL) OCSP_BASICRESP_free(br); + if (rsp!=NULL) OCSP_RESPONSE_free(rsp); + return 0; // Not OK +} +#endif diff --git a/man/mosquitto.conf.5.xml b/man/mosquitto.conf.5.xml index 37614dd4..e9a279d1 100644 --- a/man/mosquitto.conf.5.xml +++ b/man/mosquitto.conf.5.xml @@ -1410,6 +1410,13 @@ topic clients/total in 0 test/mosquitto/org $SYS/broker/ connection to succeed. + + [ true | false ] + + When set to true, the bridge requires OCSP on the TLS + connection it opens as client. + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6918ac26..5efa3afe 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,7 +13,7 @@ set (MOSQ_SRCS mosquitto.c mosquitto_broker.h net.c - ../lib/net_mosq.c ../lib/net_mosq.h + ../lib/net_mosq_ocsp.c ../lib/net_mosq.c ../lib/net_mosq.h persist.c persist.h read_handle.c read_handle_client.c read_handle_server.c ../lib/read_handle_shared.c ../lib/read_handle.h diff --git a/src/Makefile b/src/Makefile index 52dc02c5..ed6061cb 100644 --- a/src/Makefile +++ b/src/Makefile @@ -8,7 +8,7 @@ else all : mosquitto endif -mosquitto : mosquitto.o bridge.o conf.o context.o database.o logging.o loop.o memory_mosq.o persist.o net.o net_mosq.o read_handle.o read_handle_client.o read_handle_server.o read_handle_shared.o security.o security_default.o send_client_mosq.o send_mosq.o send_server.o service.o subs.o sys_tree.o time_mosq.o tls_mosq.o util_mosq.o websockets.o will_mosq.o +mosquitto : mosquitto.o bridge.o conf.o context.o database.o logging.o loop.o memory_mosq.o persist.o net.o net_mosq_ocsp.o net_mosq.o read_handle.o read_handle_client.o read_handle_server.o read_handle_shared.o security.o security_default.o send_client_mosq.o send_mosq.o send_server.o service.o subs.o sys_tree.o time_mosq.o tls_mosq.o util_mosq.o websockets.o will_mosq.o ${CROSS_COMPILE}${CC} $^ -o $@ ${LDFLAGS} $(BROKER_LIBS) mosquitto.o : mosquitto.c mosquitto_broker.h @@ -38,6 +38,9 @@ memory_mosq.o : ../lib/memory_mosq.c ../lib/memory_mosq.h net.o : net.c mosquitto_broker.h ${CROSS_COMPILE}${CC} $(BROKER_CFLAGS) -c $< -o $@ +net_mosq_ocsp.o : ../lib/net_mosq_ocsp.c ../lib/net_mosq.h + ${CROSS_COMPILE}${CC} $(BROKER_CFLAGS) -c $< -o $@ + net_mosq.o : ../lib/net_mosq.c ../lib/net_mosq.h ${CROSS_COMPILE}${CC} $(BROKER_CFLAGS) -c $< -o $@ diff --git a/src/bridge.c b/src/bridge.c index dc5f9c44..219c5db8 100644 --- a/src/bridge.c +++ b/src/bridge.c @@ -111,6 +111,7 @@ int mqtt3_bridge_new(struct mosquitto_db *db, struct _mqtt3_bridge *bridge) new_context->tls_certfile = new_context->bridge->tls_certfile; new_context->tls_keyfile = new_context->bridge->tls_keyfile; new_context->tls_cert_reqs = SSL_VERIFY_PEER; + new_context->tls_ocsp_required = new_context->bridge->tls_ocsp_required; new_context->tls_version = new_context->bridge->tls_version; new_context->tls_insecure = new_context->bridge->tls_insecure; #ifdef REAL_WITH_TLS_PSK diff --git a/src/conf.c b/src/conf.c index a3e233de..5ca8557b 100644 --- a/src/conf.c +++ b/src/conf.c @@ -781,6 +781,17 @@ int _config_read_file_core(struct mqtt3_config *config, bool reload, const char } #else _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge and/or TLS-PSK support not available."); +#endif + }else if(!strcmp(token, "bridge_require_ocsp")){ +#if defined(WITH_BRIDGE) && defined(WITH_TLS) + if(reload) continue; // Listeners not valid for reloading. + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } + if(_conf_parse_bool(&token, "bridge_require_ocsp", &cur_bridge->tls_ocsp_required, saveptr)) return MOSQ_ERR_INVAL; +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: TLS support not available."); #endif }else if(!strcmp(token, "bridge_keyfile")){ #if defined(WITH_BRIDGE) && defined(WITH_TLS) diff --git a/src/mosquitto_broker.h b/src/mosquitto_broker.h index f33007c8..1a990e3d 100644 --- a/src/mosquitto_broker.h +++ b/src/mosquitto_broker.h @@ -335,6 +335,7 @@ struct _mqtt3_bridge{ char *tls_certfile; char *tls_keyfile; bool tls_insecure; + bool tls_ocsp_required; char *tls_version; # ifdef REAL_WITH_TLS_PSK char *tls_psk_identity;