From f88cc06435d7d94ac3333ff2a1b33135337bb00e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Pernas=20Maradei?= Date: Thu, 9 Aug 2018 21:37:33 +0200 Subject: [PATCH] Add TLS engine and keyform support to libmosquitto MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Clients can now offload crypto tasks to an external crypto device through the OpenSSL ENGINE API. - The keyfiles can now be treated as PEM or ENGINE keys. - Two new functions were added to libmosquitto to set up the previously mentioned features. - Both mosquitto_sub and mosquitto_pub include support to turn on the mentioned features through command line options. Signed-off-by: Nicolás Pernas Maradei --- client/client_shared.c | 32 +++++++++++++ client/client_shared.h | 2 + client/pub_client.c | 5 +- client/sub_client.c | 5 +- lib/cpp/mosquittopp.cpp | 10 ++++ lib/cpp/mosquittopp.h | 2 + lib/linker.version | 2 + lib/mosquitto.h | 38 ++++++++++++++++ lib/mosquitto_internal.h | 9 ++++ lib/net_mosq.c | 98 ++++++++++++++++++++++++++++++++++++---- lib/net_mosq.h | 2 + lib/options.c | 38 ++++++++++++++++ lib/tls_mosq.h | 1 + man/mosquitto_pub.1.xml | 24 ++++++++++ man/mosquitto_sub.1.xml | 24 ++++++++++ 15 files changed, 281 insertions(+), 11 deletions(-) diff --git a/client/client_shared.c b/client/client_shared.c index 75d1effe..f2b8ca60 100644 --- a/client/client_shared.c +++ b/client/client_shared.c @@ -151,6 +151,8 @@ void client_config_cleanup(struct mosq_config *cfg) free(cfg->keyfile); free(cfg->ciphers); free(cfg->tls_version); + free(cfg->tls_engine); + free(cfg->keyform); # ifdef WITH_TLS_PSK free(cfg->psk); free(cfg->psk_identity); @@ -308,6 +310,10 @@ int client_config_load(struct mosq_config *cfg, int pub_or_sub, int argc, char * fprintf(stderr, "Error: Both certfile and keyfile must be provided if one of them is.\n"); return 1; } + if((cfg->keyform && !cfg->keyfile)){ + fprintf(stderr, "Error: keyfile must be specified if keyform is.\n"); + return 1; + } #endif #ifdef WITH_TLS_PSK if((cfg->cafile || cfg->capath) && cfg->psk){ @@ -425,6 +431,14 @@ int client_config_line_proc(struct mosq_config *cfg, int pub_or_sub, int argc, c cfg->ciphers = strdup(argv[i+1]); } i++; + }else if(!strcmp(argv[i], "--tls-engine")){ + if(i==argc-1){ + fprintf(stderr, "Error: --tls-engine argument given but no engine_id specified.\n\n"); + return 1; + }else{ + cfg->tls_engine = strdup(argv[i+1]); + } + i++; #endif }else if(!strcmp(argv[i], "-C")){ if(pub_or_sub == CLIENT_PUB){ @@ -561,6 +575,14 @@ int client_config_line_proc(struct mosq_config *cfg, int pub_or_sub, int argc, c cfg->keyfile = strdup(argv[i+1]); } i++; + }else if(!strcmp(argv[i], "--keyform")){ + if(i==argc-1){ + fprintf(stderr, "Error: --keyform argument given but no keyform specified.\n\n"); + return 1; + }else{ + cfg->keyform = strdup(argv[i+1]); + } + i++; #endif }else if(!strcmp(argv[i], "-L") || !strcmp(argv[i], "--url")){ if(i==argc-1){ @@ -917,6 +939,16 @@ int client_opts_set(struct mosquitto *mosq, struct mosq_config *cfg) mosquitto_lib_cleanup(); return 1; } + if(cfg->tls_engine && mosquitto_tls_engine_set(mosq, cfg->tls_engine)){ + if(!cfg->quiet) fprintf(stderr, "Error: Problem setting TLS engine.\n"); + mosquitto_lib_cleanup(); + return 1; + } + if(cfg->keyform && mosquitto_tls_keyform_set(mosq, cfg->keyform)){ + if(!cfg->quiet) fprintf(stderr, "Error: Problem setting keyform.\n"); + mosquitto_lib_cleanup(); + return 1; + } # ifdef WITH_TLS_PSK if(cfg->psk && mosquitto_tls_psk_set(mosq, cfg->psk, cfg->psk_identity, NULL)){ if(!cfg->quiet) fprintf(stderr, "Error: Problem setting TLS-PSK options.\n"); diff --git a/client/client_shared.h b/client/client_shared.h index 06762293..3df157f8 100644 --- a/client/client_shared.h +++ b/client/client_shared.h @@ -66,6 +66,8 @@ struct mosq_config { char *ciphers; bool insecure; char *tls_version; + char *tls_engine; + char *keyform; # ifdef WITH_TLS_PSK char *psk; char *psk_identity; diff --git a/client/pub_client.c b/client/pub_client.c index 8c729b87..8c9ced3e 100644 --- a/client/pub_client.c +++ b/client/pub_client.c @@ -222,7 +222,8 @@ void print_usage(void) printf(" [--will-topic [--will-payload payload] [--will-qos qos] [--will-retain]]\n"); #ifdef WITH_TLS printf(" [{--cafile file | --capath dir} [--cert file] [--key file]\n"); - printf(" [--ciphers ciphers] [--insecure]]\n"); + printf(" [--ciphers ciphers] [--insecure] [--tls-engine engine]\n"); + printf(" [--keyform keyform]]\n"); #ifdef WITH_TLS_PSK printf(" [--psk hex-key --psk-identity identity [--ciphers ciphers]]\n"); #endif @@ -273,6 +274,7 @@ void print_usage(void) printf(" communication.\n"); printf(" --cert : client certificate for authentication, if required by server.\n"); printf(" --key : client private key for authentication, if required by server.\n"); + printf(" --keyform : keyfile type, can be one of pem or engine.\n"); printf(" --ciphers : openssl compatible list of TLS ciphers to support.\n"); printf(" --tls-version : TLS protocol version, can be one of tlsv1.2 tlsv1.1 or tlsv1.\n"); printf(" Defaults to tlsv1.2 if available.\n"); @@ -280,6 +282,7 @@ void print_usage(void) printf(" hostname. Using this option means that you cannot be sure that the\n"); printf(" remote host is the server you wish to connect to and so is insecure.\n"); printf(" Do not use this option in a production environment.\n"); + printf(" --tls-engine : toggles the usage of a SSL engine device.\n"); # ifdef WITH_TLS_PSK printf(" --psk : pre-shared-key in hexadecimal (no leading 0x) to enable TLS-PSK mode.\n"); printf(" --psk-identity : client identity string for TLS-PSK mode.\n"); diff --git a/client/sub_client.c b/client/sub_client.c index 95120cd0..cc56299a 100644 --- a/client/sub_client.c +++ b/client/sub_client.c @@ -157,7 +157,8 @@ void print_usage(void) printf(" [--will-topic [--will-payload payload] [--will-qos qos] [--will-retain]]\n"); #ifdef WITH_TLS printf(" [{--cafile file | --capath dir} [--cert file] [--key file]\n"); - printf(" [--ciphers ciphers] [--insecure]]\n"); + printf(" [--ciphers ciphers] [--insecure] [--tls-engine engine]\n"); + printf(" [--keyform keyform]]\n"); #ifdef WITH_TLS_PSK printf(" [--psk hex-key --psk-identity identity [--ciphers ciphers]]\n"); #endif @@ -215,6 +216,7 @@ void print_usage(void) printf(" communication.\n"); printf(" --cert : client certificate for authentication, if required by server.\n"); printf(" --key : client private key for authentication, if required by server.\n"); + printf(" --keyform : keyfile type, can be one of pem or engine.\n"); printf(" --ciphers : openssl compatible list of TLS ciphers to support.\n"); printf(" --tls-version : TLS protocol version, can be one of tlsv1.2 tlsv1.1 or tlsv1.\n"); printf(" Defaults to tlsv1.2 if available.\n"); @@ -222,6 +224,7 @@ void print_usage(void) printf(" hostname. Using this option means that you cannot be sure that the\n"); printf(" remote host is the server you wish to connect to and so is insecure.\n"); printf(" Do not use this option in a production environment.\n"); + printf(" --tls-engine : toggles the usage of a SSL engine device.\n"); #ifdef WITH_TLS_PSK printf(" --psk : pre-shared-key in hexadecimal (no leading 0x) to enable TLS-PSK mode.\n"); printf(" --psk-identity : client identity string for TLS-PSK mode.\n"); diff --git a/lib/cpp/mosquittopp.cpp b/lib/cpp/mosquittopp.cpp index 71b32068..2fcef663 100644 --- a/lib/cpp/mosquittopp.cpp +++ b/lib/cpp/mosquittopp.cpp @@ -366,4 +366,14 @@ 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_engine_set(const char *engine_id) +{ + return mosquitto_tls_engine_set(m_mosq, engine_id); +} + +int mosquittopp::tls_keyform_set(const char *keyform) +{ + return mosquitto_tls_keyform_set(m_mosq, keyform); +} + } diff --git a/lib/cpp/mosquittopp.h b/lib/cpp/mosquittopp.h index 38a5835f..961c3c92 100644 --- a/lib/cpp/mosquittopp.h +++ b/lib/cpp/mosquittopp.h @@ -110,6 +110,8 @@ 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_engine_set(const char *engine_id); + int tls_keyform_set(const char *keyform); 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 fb18c049..15fb4196 100644 --- a/lib/linker.version +++ b/lib/linker.version @@ -90,6 +90,8 @@ MOSQ_1.5 { mosquitto_sub_topic_check2; mosquitto_topic_matches_sub2; mosquitto_connect_with_flags_callback_set; + mosquitto_tls_engine_set; + mosquitto_tls_keyform_set; } MOSQ_1.4; MOSQ_1.6 { diff --git a/lib/mosquitto.h b/lib/mosquitto.h index 521cfa07..8a99ee90 100644 --- a/lib/mosquitto.h +++ b/lib/mosquitto.h @@ -1160,6 +1160,44 @@ 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_engine_set + * + * Configure the client for TLS engine support. Must be called + * before . + * + * Parameters: + * mosq - a valid mosquitto instance. + * engine_id - the engine ID that wants to be used. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * + * See Also: + * + */ +libmosq_EXPORT int mosquitto_tls_engine_set(struct mosquitto *mosq, const char *engine_id); + +/* + * Function: mosquitto_tls_keyform_set + * + * Configure the client to treat the keyfile differently depending on its type. + * Must be called before . + * + * Parameters: + * mosq - a valid mosquitto instance. + * keyform - the key type. Currently only "pem" or "engine" are supported. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * + * See Also: + * + */ +libmosq_EXPORT int mosquitto_tls_keyform_set(struct mosquitto *mosq, const char *keyform); + /* * Function: mosquitto_connect_callback_set * diff --git a/lib/mosquitto_internal.h b/lib/mosquitto_internal.h index c6b3d6e5..cf1bd38c 100644 --- a/lib/mosquitto_internal.h +++ b/lib/mosquitto_internal.h @@ -153,6 +153,13 @@ struct mosquitto_message_all{ struct mosquitto_message msg; }; +#ifdef WITH_TLS +enum _mosquitto_keyform { + mosq_k_pem = 0, + mosq_k_engine = 1, +}; +#endif + struct mosquitto { mosq_sock_t sock; #ifndef WITH_BROKER @@ -191,6 +198,8 @@ struct mosquitto { int tls_cert_reqs; bool tls_insecure; bool ssl_ctx_defaults; + char *tls_engine; + enum _mosquitto_keyform tls_keyform; #endif bool want_write; bool want_connect; diff --git a/lib/net_mosq.c b/lib/net_mosq.c index 4efda3d2..b0ec50bb 100644 --- a/lib/net_mosq.c +++ b/lib/net_mosq.c @@ -71,6 +71,42 @@ Contributors: #ifdef WITH_TLS int tls_ex_index_mosq = -1; +UI_METHOD *_ui_method = NULL; + +/* Functions taken from OpenSSL s_server/s_client */ +static int ui_open(UI *ui) +{ + return UI_method_get_opener(UI_OpenSSL())(ui); +} + +static int ui_read(UI *ui, UI_STRING *uis) +{ + return UI_method_get_reader(UI_OpenSSL())(ui, uis); +} + +static int ui_write(UI *ui, UI_STRING *uis) +{ + return UI_method_get_writer(UI_OpenSSL())(ui, uis); +} + +static int ui_close(UI *ui) +{ + return UI_method_get_closer(UI_OpenSSL())(ui); +} + +static void setup_ui_method(void) +{ + _ui_method = UI_create_method("OpenSSL application user interface"); + UI_method_set_opener(_ui_method, ui_open); + UI_method_set_reader(_ui_method, ui_read); + UI_method_set_writer(_ui_method, ui_write); + UI_method_set_closer(_ui_method, ui_close); +} + +UI_METHOD *net__get_ui_method(void) +{ + return _ui_method; +} #endif int net__init(void) @@ -90,6 +126,9 @@ int net__init(void) SSL_load_error_strings(); SSL_library_init(); OpenSSL_add_all_algorithms(); + OPENSSL_config(NULL); + ENGINE_load_builtin_engines(); + setup_ui_method(); if(tls_ex_index_mosq == -1){ tls_ex_index_mosq = SSL_get_ex_new_index(0, "client context", NULL, NULL, NULL); } @@ -440,6 +479,7 @@ int net__socket_connect_tls(struct mosquitto *mosq) static int net__init_ssl_ctx(struct mosquitto *mosq) { int ret; + ENGINE *engine = NULL; if(mosq->ssl_ctx){ if(!mosq->ssl_ctx_defaults){ @@ -493,10 +533,28 @@ static int net__init_ssl_ctx(struct mosquitto *mosq) SSL_CTX_set_mode(mosq->ssl_ctx, SSL_MODE_RELEASE_BUFFERS); #endif + if(mosq->tls_engine){ + engine = ENGINE_by_id(mosq->tls_engine); + if(!engine){ + log__printf(mosq, MOSQ_LOG_ERR, "Error loading %s engine\n", mosq->tls_engine); + COMPAT_CLOSE(mosq->sock); + return MOSQ_ERR_TLS; + } + if(!ENGINE_init(engine)){ + log__printf(mosq, MOSQ_LOG_ERR, "Failed engine initialisation\n"); + ENGINE_free(engine); + COMPAT_CLOSE(mosq->sock); + return MOSQ_ERR_TLS; + } + ENGINE_set_default(engine, ENGINE_METHOD_ALL); + ENGINE_free(engine); /* release the structural reference from ENGINE_by_id() */ + } + if(mosq->tls_ciphers){ ret = SSL_CTX_set_cipher_list(mosq->ssl_ctx, mosq->tls_ciphers); if(ret == 0){ log__printf(mosq, MOSQ_LOG_ERR, "Error: Unable to set TLS ciphers. Check cipher list \"%s\".", mosq->tls_ciphers); + ENGINE_FINISH(engine); COMPAT_CLOSE(mosq->sock); mosq->sock = INVALID_SOCKET; net__print_ssl_error(mosq); @@ -523,6 +581,7 @@ static int net__init_ssl_ctx(struct mosquitto *mosq) log__printf(mosq, MOSQ_LOG_ERR, "Error: Unable to load CA certificates, check capath \"%s\".", mosq->tls_capath); } #endif + ENGINE_FINISH(engine); COMPAT_CLOSE(mosq->sock); mosq->sock = INVALID_SOCKET; net__print_ssl_error(mosq); @@ -547,6 +606,7 @@ static int net__init_ssl_ctx(struct mosquitto *mosq) #else log__printf(mosq, MOSQ_LOG_ERR, "Error: Unable to load client certificate \"%s\".", mosq->tls_certfile); #endif + ENGINE_FINISH(engine); COMPAT_CLOSE(mosq->sock); mosq->sock = INVALID_SOCKET; net__print_ssl_error(mosq); @@ -554,21 +614,41 @@ static int net__init_ssl_ctx(struct mosquitto *mosq) } } if(mosq->tls_keyfile){ - ret = SSL_CTX_use_PrivateKey_file(mosq->ssl_ctx, mosq->tls_keyfile, SSL_FILETYPE_PEM); - if(ret != 1){ + if(mosq->tls_keyform == mosq_k_engine){ + EVP_PKEY *pkey = ENGINE_load_private_key(engine, mosq->tls_keyfile, ui_method, NULL); + if(!pkey){ + log__printf(mosq, MOSQ_LOG_ERR, "Error: Unable to load engine private key file \"%s\".", mosq->tls_keyfile); + ENGINE_FINISH(engine); + COMPAT_CLOSE(mosq->sock); + net__print_ssl_error(mosq); + return MOSQ_ERR_TLS; + } + if(SSL_CTX_use_PrivateKey(mosq->ssl_ctx, pkey) <= 0){ + log__printf(mosq, MOSQ_LOG_ERR, "Error: Unable to use engine private key file \"%s\".", mosq->tls_keyfile); + ENGINE_FINISH(engine); + COMPAT_CLOSE(mosq->sock); + net__print_ssl_error(mosq); + return MOSQ_ERR_TLS; + } + }else{ + ret = SSL_CTX_use_PrivateKey_file(mosq->ssl_ctx, mosq->tls_keyfile, SSL_FILETYPE_PEM); + if(ret != 1){ #ifdef WITH_BROKER - log__printf(mosq, MOSQ_LOG_ERR, "Error: Unable to load client key file, check bridge_keyfile \"%s\".", mosq->tls_keyfile); + log__printf(mosq, MOSQ_LOG_ERR, "Error: Unable to load client key file, check bridge_keyfile \"%s\".", mosq->tls_keyfile); #else - log__printf(mosq, MOSQ_LOG_ERR, "Error: Unable to load client key file \"%s\".", mosq->tls_keyfile); -#endif - COMPAT_CLOSE(mosq->sock); - mosq->sock = INVALID_SOCKET; - net__print_ssl_error(mosq); - return MOSQ_ERR_TLS; + log__printf(mosq, MOSQ_LOG_ERR, "Error: Unable to load client key file \"%s\".", mosq->tls_keyfile); +#endif + ENGINE_FINISH(engine); + COMPAT_CLOSE(mosq->sock); + mosq->sock = INVALID_SOCKET; + net__print_ssl_error(mosq); + return MOSQ_ERR_TLS; + } } ret = SSL_CTX_check_private_key(mosq->ssl_ctx); if(ret != 1){ log__printf(mosq, MOSQ_LOG_ERR, "Error: Client certificate/key are inconsistent."); + ENGINE_FINISH(engine); COMPAT_CLOSE(mosq->sock); mosq->sock = INVALID_SOCKET; net__print_ssl_error(mosq); diff --git a/lib/net_mosq.h b/lib/net_mosq.h index ea729665..0fbc95bc 100644 --- a/lib/net_mosq.h +++ b/lib/net_mosq.h @@ -71,6 +71,8 @@ ssize_t net__write(struct mosquitto *mosq, void *buf, size_t count); #ifdef WITH_TLS int net__socket_apply_tls(struct mosquitto *mosq); int net__socket_connect_tls(struct mosquitto *mosq); +UI_METHOD *net__get_ui_method(void); +#define ENGINE_FINISH(e) if(e) ENGINE_finish(e) #endif #endif diff --git a/lib/options.c b/lib/options.c index dd9f7180..7ed2a95a 100644 --- a/lib/options.c +++ b/lib/options.c @@ -219,6 +219,44 @@ int mosquitto_tls_insecure_set(struct mosquitto *mosq, bool value) } +int mosquitto_tls_engine_set(struct mosquitto *mosq, const char *engine_id) +{ +#ifdef WITH_TLS + if(!mosq) return MOSQ_ERR_INVAL; + ENGINE *e = ENGINE_by_id(engine_id); + if (!e) + return MOSQ_ERR_INVAL; + ENGINE_free(e); /* release the structural reference from ENGINE_by_id() */ + mosq->tls_engine = mosquitto__strdup(engine_id); + return MOSQ_ERR_SUCCESS; +#else + return MOSQ_ERR_NOT_SUPPORTED; +#endif +} + + +int mosquitto_tls_keyform_set(struct mosquitto *mosq, const char *keyform) +{ +#ifdef WITH_TLS + if(!mosq) return MOSQ_ERR_INVAL; + + if (keyform){ + if(!strcasecmp(keyform, "pem")) + mosq->tls_keyform = mosq_k_pem; + else if (!strcasecmp(keyform, "engine")) + mosq->tls_keyform = mosq_k_engine; + else + return MOSQ_ERR_INVAL; + }else{ + mosq->tls_keyform = mosq_k_pem; + } + return MOSQ_ERR_SUCCESS; +#else + return MOSQ_ERR_NOT_SUPPORTED; +#endif +} + + int mosquitto_tls_psk_set(struct mosquitto *mosq, const char *psk, const char *identity, const char *ciphers) { #ifdef WITH_TLS_PSK diff --git a/lib/tls_mosq.h b/lib/tls_mosq.h index 66948180..f28d2eb1 100644 --- a/lib/tls_mosq.h +++ b/lib/tls_mosq.h @@ -26,6 +26,7 @@ Contributors: #ifdef WITH_TLS #include +#include int mosquitto__server_certificate_verify(int preverify_ok, X509_STORE_CTX *ctx); int mosquitto__verify_certificate_hostname(X509 *cert, const char *hostname); diff --git a/man/mosquitto_pub.1.xml b/man/mosquitto_pub.1.xml index ab221c7d..d2ed0771 100644 --- a/man/mosquitto_pub.1.xml +++ b/man/mosquitto_pub.1.xml @@ -62,6 +62,12 @@ file ciphers version + engine + + + pem + engine + @@ -180,6 +186,24 @@ for more information. + + + + A valid openssl engine id. These can be listed with + openssl engine command. + See also . + + + + + + Specifies the type of key in use. This could be pem or + engine. This parameter is useful for example when a TPM + module is being used and the key has been created with + it. If not specified, pem keys are assumed + See also . + + diff --git a/man/mosquitto_sub.1.xml b/man/mosquitto_sub.1.xml index b25b19a1..50d778da 100644 --- a/man/mosquitto_sub.1.xml +++ b/man/mosquitto_sub.1.xml @@ -69,6 +69,12 @@ file file version + engine + + + pem + engine + @@ -190,6 +196,24 @@ for more information. + + + + A valid openssl engine id. These can be listed with + openssl engine command. + See also . + + + + + + Specifies the type of key in use. This could be pem or + engine. This parameter is useful for example when a TPM + module is being used and the key has been created with + it. If not specified, pem keys are assumed + See also . + +