diff --git a/client/client_shared.c b/client/client_shared.c index f14ebb1ecc..c45280aef2 100644 --- a/client/client_shared.c +++ b/client/client_shared.c @@ -151,6 +151,9 @@ void client_config_cleanup(struct mosq_config *cfg) free(cfg->keyfile); free(cfg->ciphers); free(cfg->tls_version); + free(cfg->tls_engine); + free(cfg->tls_engine_kpass_sha); + free(cfg->keyform); # ifdef WITH_TLS_PSK free(cfg->psk); free(cfg->psk_identity); @@ -308,6 +311,14 @@ 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; + } + if((cfg->tls_engine_kpass_sha && (!cfg->keyform || !cfg->tls_engine))){ + fprintf(stderr, "Error: when using tls-engine-kpass-sha, both tls-engine and keyform must also be provided.\n"); + return 1; + } #endif #ifdef WITH_TLS_PSK if((cfg->cafile || cfg->capath) && cfg->psk){ @@ -425,6 +436,22 @@ 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++; + }else if(!strcmp(argv[i], "--tls-engine-kpass-sha")){ + if(i==argc-1){ + fprintf(stderr, "Error: --tls-engine-kpass-sha argument given but no kpass sha specified.\n\n"); + return 1; + }else{ + cfg->tls_engine_kpass_sha = strdup(argv[i+1]); + } + i++; #endif }else if(!strcmp(argv[i], "-C")){ if(pub_or_sub == CLIENT_PUB){ @@ -556,6 +583,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){ @@ -912,6 +947,21 @@ 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; + } + if(cfg->tls_engine_kpass_sha && mosquitto_tls_engine_kpass_sha_set(mosq, cfg->tls_engine_kpass_sha)){ + if(!cfg->quiet) fprintf(stderr, "Error: Problem setting TLS engine key pass sha.\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 f1ce6f3132..9c6b13887d 100644 --- a/client/client_shared.h +++ b/client/client_shared.h @@ -66,6 +66,9 @@ struct mosq_config { char *ciphers; bool insecure; char *tls_version; + char *tls_engine; + char *tls_engine_kpass_sha; + char *keyform; # ifdef WITH_TLS_PSK char *psk; char *psk_identity; diff --git a/client/pub_client.c b/client/pub_client.c index 85fd73c8f0..6997675e66 100644 --- a/client/pub_client.c +++ b/client/pub_client.c @@ -223,7 +223,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] [--tls-engine-kpass-sha]]\n"); #ifdef WITH_TLS_PSK printf(" [--psk hex-key --psk-identity identity [--ciphers ciphers]]\n"); #endif @@ -274,6 +275,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"); @@ -281,6 +283,8 @@ 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"); + printf(" --tls-engine-kpass-sha : SHA1 of the key password to be used with the selected SSL engine.\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 1d83684610..e1854d7f5a 100644 --- a/client/sub_client.c +++ b/client/sub_client.c @@ -154,7 +154,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] [--tls-engine-kpass-sha]]\n"); #ifdef WITH_TLS_PSK printf(" [--psk hex-key --psk-identity identity [--ciphers ciphers]]\n"); #endif @@ -211,6 +212,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"); @@ -218,6 +220,8 @@ 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"); + printf(" --tls-engine-kpass-sha : SHA1 of the key password to be used with the selected SSL engine.\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 71b32068d0..345b9d97a1 100644 --- a/lib/cpp/mosquittopp.cpp +++ b/lib/cpp/mosquittopp.cpp @@ -366,4 +366,19 @@ 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); +} + +int mosquittopp::tls_engine_kpass_sha_set(const char *kpass_sha) +{ + return mosquitto_tls_engine_kpass_sha_set(m_mosq, kpass_sha); +} + } diff --git a/lib/cpp/mosquittopp.h b/lib/cpp/mosquittopp.h index 38a5835fb1..b29ee94791 100644 --- a/lib/cpp/mosquittopp.h +++ b/lib/cpp/mosquittopp.h @@ -110,6 +110,9 @@ 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 tls_engine_kpass_sha_set(const char *kpass_sha); 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 c592d17eef..8178c7b482 100644 --- a/lib/linker.version +++ b/lib/linker.version @@ -90,4 +90,7 @@ 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; + mosquitto_tls_engine_kpass_sha_set; } MOSQ_1.4; diff --git a/lib/mosquitto.h b/lib/mosquitto.h index e67a2b3427..d540074071 100644 --- a/lib/mosquitto.h +++ b/lib/mosquitto.h @@ -1130,6 +1130,65 @@ 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_tls_engine_kpass_sha_set + * + * Some SSL engines may require the usage of a password in order to being + * accessed, like the TPM engine. This function allows a SHA1 hash of the + * password to be passed on to the engine directly. + * Must be called before . + * + * Parameters: + * mosq - a valid mosquitto instance. + * kpass_sha - SHA1 of the private key password. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * + * See Also: + * + */ +libmosq_EXPORT int mosquitto_tls_engine_kpass_sha_set(struct mosquitto *mosq, const char *kpass_sha); + /* * Function: mosquitto_connect_callback_set * diff --git a/lib/mosquitto_internal.h b/lib/mosquitto_internal.h index 05667a78b1..ada384f48e 100644 --- a/lib/mosquitto_internal.h +++ b/lib/mosquitto_internal.h @@ -151,6 +151,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 @@ -189,6 +196,9 @@ struct mosquitto { int tls_cert_reqs; bool tls_insecure; bool ssl_ctx_defaults; + char *tls_engine; + char *tls_engine_kpass_sha; + 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 daf8ce94ba..bc3b68dd0f 100644 --- a/lib/net_mosq.c +++ b/lib/net_mosq.c @@ -80,6 +80,42 @@ and the Eclipse Distribution License is available at #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) @@ -99,6 +135,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); } @@ -447,6 +486,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){ @@ -498,10 +538,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); net__print_ssl_error(mosq); return MOSQ_ERR_TLS; @@ -527,6 +585,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); net__print_ssl_error(mosq); return MOSQ_ERR_TLS; @@ -550,26 +609,65 @@ 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); net__print_ssl_error(mosq); return MOSQ_ERR_TLS; } } 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){ + UI_METHOD *ui_method = net__get_ui_method(); + if(mosq->tls_engine_kpass_sha){ + if(!ENGINE_ctrl_cmd(engine, ENGINE_SECRET_MODE, ENGINE_SECRET_MODE_SHA, NULL, NULL, 0)){ + log__printf(mosq, MOSQ_LOG_ERR, "Error: Unable to set engine secret mode sha"); + ENGINE_FINISH(engine); + COMPAT_CLOSE(mosq->sock); + net__print_ssl_error(mosq); + return MOSQ_ERR_TLS; + } + if(!ENGINE_ctrl_cmd(engine, ENGINE_PIN, 0, mosq->tls_engine_kpass_sha, NULL, 0)){ + log__printf(mosq, MOSQ_LOG_ERR, "Error: Unable to set engine pin"); + ENGINE_FINISH(engine); + COMPAT_CLOSE(mosq->sock); + net__print_ssl_error(mosq); + return MOSQ_ERR_TLS; + } + ui_method = NULL; + } + 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); + log__printf(mosq, MOSQ_LOG_ERR, "Error: Unable to load client key file \"%s\".", mosq->tls_keyfile); #endif - COMPAT_CLOSE(mosq->sock); - net__print_ssl_error(mosq); - return MOSQ_ERR_TLS; + ENGINE_FINISH(engine); + COMPAT_CLOSE(mosq->sock); + 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); net__print_ssl_error(mosq); return MOSQ_ERR_TLS; diff --git a/lib/net_mosq.h b/lib/net_mosq.h index b5d854948d..fe58749827 100644 --- a/lib/net_mosq.h +++ b/lib/net_mosq.h @@ -71,6 +71,11 @@ 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) +#define ENGINE_SECRET_MODE "SECRET_MODE" +#define ENGINE_SECRET_MODE_SHA 0x1000 +#define ENGINE_PIN "PIN" #endif #endif diff --git a/lib/options.c b/lib/options.c index dd9f718099..a5c9d796aa 100644 --- a/lib/options.c +++ b/lib/options.c @@ -219,6 +219,58 @@ 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_engine_kpass_sha_set(struct mosquitto *mosq, const char *kpass_sha) +{ +#ifdef WITH_TLS + if(!mosq) return MOSQ_ERR_INVAL; + char *kpass_sha_bin = NULL; + if(mosquitto__hex2bin_sha1(kpass_sha, (unsigned char**)&kpass_sha_bin) != MOSQ_ERR_SUCCESS) return MOSQ_ERR_INVAL; + mosq->tls_engine_kpass_sha = kpass_sha_bin; + 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 6694818056..f28d2eb16c 100644 --- a/lib/tls_mosq.h +++ b/lib/tls_mosq.h @@ -26,6 +26,7 @@ and the Eclipse Distribution License is available at #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/lib/util_mosq.c b/lib/util_mosq.c index b85b95cefc..3d0eec58c3 100644 --- a/lib/util_mosq.c +++ b/lib/util_mosq.c @@ -344,7 +344,22 @@ int mosquitto_topic_matches_sub2(const char *sub, size_t sublen, const char *top return MOSQ_ERR_SUCCESS; } -#ifdef WITH_TLS_PSK +#ifdef WITH_TLS +int mosquitto__hex2bin_sha1(const char *hex, unsigned char **bin) +{ + unsigned char *sha, tmp[SHA_DIGEST_LENGTH]; + + if(mosquitto__hex2bin(hex, tmp, SHA_DIGEST_LENGTH) != SHA_DIGEST_LENGTH) + return MOSQ_ERR_INVAL; + + sha = mosquitto__malloc(SHA_DIGEST_LENGTH); + memcpy(sha, tmp, SHA_DIGEST_LENGTH); + *bin = sha; + return MOSQ_ERR_SUCCESS; +} +#endif + +#if defined(WITH_TLS_PSK) || defined(WITH_TLS) int mosquitto__hex2bin(const char *hex, unsigned char *bin, int bin_max_len) { BIGNUM *bn = NULL; diff --git a/lib/util_mosq.h b/lib/util_mosq.h index 0e65dd9889..bec6a2ec91 100644 --- a/lib/util_mosq.h +++ b/lib/util_mosq.h @@ -33,7 +33,11 @@ void mosquitto__check_keepalive(struct mosquitto *mosq); uint16_t mosquitto__mid_generate(struct mosquitto *mosq); FILE *mosquitto__fopen(const char *path, const char *mode, bool restrict_read); -#ifdef WITH_TLS_PSK +#ifdef WITH_TLS +int mosquitto__hex2bin_sha1(const char *hex, unsigned char **bin); +#endif + +#if defined(WITH_TLS_PSK) || defined(WITH_TLS) int mosquitto__hex2bin(const char *hex, unsigned char *bin, int bin_max_len); #endif diff --git a/man/mosquitto.conf.5.xml b/man/mosquitto.conf.5.xml index 9347cfc5ea..d7cbe9c5a1 100644 --- a/man/mosquitto.conf.5.xml +++ b/man/mosquitto.conf.5.xml @@ -872,6 +872,33 @@ the "openssl ciphers" command. + + engine + + A valid openssl engine id. These can be listed with + openssl engine command. + + + + [ pem | engine ] + + 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 + + + + engine_kpass_sha + + SHA1 of private key password when using an SSL engine. + Some SSL engines may require the usage of a password + in order to being accessed, like the TPM engine. Instead + of being prompted for the password, this option allows + a SHA1 hash of the password to be passed on to the + engine directly, without user interaction. + + file path diff --git a/man/mosquitto_pub.1.xml b/man/mosquitto_pub.1.xml index ab221c7d7c..9504c26427 100644 --- a/man/mosquitto_pub.1.xml +++ b/man/mosquitto_pub.1.xml @@ -62,6 +62,13 @@ file ciphers version + engine + + + pem + engine + + kpass-sha @@ -180,6 +187,36 @@ 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 . + + + + + + SHA1 of private key password when using an SSL engine. + Some SSL engines may require the usage of a password + in order to being accessed, like the TPM engine. Instead + of being prompted for the password, this option allows + a SHA1 hash of the password to be passed on to the + engine directly, without user interaction. + See also . + + diff --git a/man/mosquitto_sub.1.xml b/man/mosquitto_sub.1.xml index 01f9c979e7..a5fd8b0e65 100644 --- a/man/mosquitto_sub.1.xml +++ b/man/mosquitto_sub.1.xml @@ -68,6 +68,13 @@ file file version + engine + + + pem + engine + + kpass-sha @@ -189,6 +196,36 @@ 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 . + + + + + + SHA1 of private key password when using an SSL engine. + Some SSL engines may require the usage of a password + in order to being accessed, like the TPM engine. Instead + of being prompted for the password, this option allows + a SHA1 hash of the password to be passed on to the + engine directly, without user interaction. + See also . + + diff --git a/src/conf.c b/src/conf.c index f3d812dbc4..c5420b996e 100644 --- a/src/conf.c +++ b/src/conf.c @@ -298,6 +298,8 @@ void config__cleanup(struct mosquitto__config *config) mosquitto__free(config->listeners[i].psk_hint); mosquitto__free(config->listeners[i].crlfile); mosquitto__free(config->listeners[i].tls_version); + mosquitto__free(config->listeners[i].tls_engine); + mosquitto__free(config->listeners[i].tls_engine_kpass_sha); #ifdef WITH_WEBSOCKETS if(!config->listeners[i].ws_context) /* libwebsockets frees its own SSL_CTX */ #endif @@ -433,6 +435,9 @@ int config__parse_args(struct mosquitto_db *db, struct mosquitto__config *config || config->default_listener.capath || config->default_listener.certfile || config->default_listener.keyfile + || config->default_listener.tls_engine + || config->default_listener.tls_keyform != mosq_k_pem + || config->default_listener.tls_engine_kpass_sha || config->default_listener.ciphers || config->default_listener.psk_hint || config->default_listener.require_certificate @@ -483,6 +488,9 @@ int config__parse_args(struct mosquitto_db *db, struct mosquitto__config *config config->listeners[config->listener_count-1].use_username_as_clientid = config->default_listener.use_username_as_clientid; #ifdef WITH_TLS config->listeners[config->listener_count-1].tls_version = config->default_listener.tls_version; + config->listeners[config->listener_count-1].tls_engine = config->default_listener.tls_engine; + config->listeners[config->listener_count-1].tls_keyform = config->default_listener.tls_keyform; + config->listeners[config->listener_count-1].tls_engine_kpass_sha = config->default_listener.tls_engine_kpass_sha; config->listeners[config->listener_count-1].cafile = config->default_listener.cafile; config->listeners[config->listener_count-1].capath = config->default_listener.capath; config->listeners[config->listener_count-1].certfile = config->default_listener.certfile; @@ -1076,6 +1084,38 @@ int config__read_file_core(struct mosquitto__config *config, bool reload, struct if(conf__parse_string(&token, "certfile", &cur_listener->certfile, saveptr)) return MOSQ_ERR_INVAL; #else log__printf(NULL, MOSQ_LOG_WARNING, "Warning: TLS support not available."); +#endif + }else if(!strcmp(token, "tls_engine")){ +#ifdef WITH_TLS + if(reload) continue; // Listeners not valid for reloading. + if(conf__parse_string(&token, "tls_engine", &cur_listener->tls_engine, saveptr)) return MOSQ_ERR_INVAL; +#else + log__printf(NULL, MOSQ_LOG_WARNING, "Warning: TLS support not available."); +#endif + }else if(!strcmp(token, "tls_keyform")){ +#ifdef WITH_TLS + if(reload) continue; // Listeners not valid for reloading. + char *keyform = NULL; + if(conf__parse_string(&token, "tls_keyform", &keyform, saveptr)) return MOSQ_ERR_INVAL; + cur_listener->tls_keyform = mosq_k_pem; + if(!strcmp(keyform, "engine")) cur_listener->tls_keyform = mosq_k_engine; + mosquitto__free(keyform); +#else + log__printf(NULL, MOSQ_LOG_WARNING, "Warning: TLS support not available."); +#endif + }else if(!strcmp(token, "tls_engine_kpass_sha")){ +#ifdef WITH_TLS + if(reload) continue; // Listeners not valid for reloading. + char *kpass_sha = NULL, *kpass_sha_bin = NULL; + if(conf__parse_string(&token, "tls_engine_kpass_sha", &kpass_sha, saveptr)) return MOSQ_ERR_INVAL; + if(mosquitto__hex2bin_sha1(kpass_sha, (unsigned char**)&kpass_sha_bin) != MOSQ_ERR_SUCCESS){ + mosquitto__free(kpass_sha); + return MOSQ_ERR_INVAL; + } + cur_listener->tls_engine_kpass_sha = kpass_sha_bin; + mosquitto__free(kpass_sha); +#else + log__printf(NULL, MOSQ_LOG_WARNING, "Warning: TLS support not available."); #endif }else if(!strcmp(token, "ciphers")){ #ifdef WITH_TLS diff --git a/src/mosquitto_broker_internal.h b/src/mosquitto_broker_internal.h index 85aff0536e..310c429f69 100644 --- a/src/mosquitto_broker_internal.h +++ b/src/mosquitto_broker_internal.h @@ -221,6 +221,9 @@ struct mosquitto__listener { char *capath; char *certfile; char *keyfile; + char *tls_engine; + enum _mosquitto_keyform tls_keyform; + char *tls_engine_kpass_sha; char *ciphers; char *psk_hint; SSL_CTX *ssl_ctx; diff --git a/src/net.c b/src/net.c index 7397de577d..f7466a46fe 100644 --- a/src/net.c +++ b/src/net.c @@ -347,6 +347,7 @@ int net__socket_listen(struct mosquitto__listener *listener) int rc; X509_STORE *store; X509_LOOKUP *lookup; + ENGINE *engine = NULL; #endif if(!listener) return MOSQ_ERR_INVAL; @@ -430,6 +431,22 @@ int net__socket_listen(struct mosquitto__listener *listener) COMPAT_CLOSE(sock); return 1; } + if(listener->tls_engine){ + engine = ENGINE_by_id(listener->tls_engine); + if(!engine){ + log__printf(NULL, MOSQ_LOG_ERR, "Error loading %s engine\n", listener->tls_engine); + COMPAT_CLOSE(sock); + return 1; + } + if(!ENGINE_init(engine)){ + log__printf(NULL, MOSQ_LOG_ERR, "Failed engine initialisation\n"); + ENGINE_free(engine); + COMPAT_CLOSE(sock); + return 1; + } + ENGINE_set_default(engine, ENGINE_METHOD_ALL); + ENGINE_free(engine); /* release the structural reference from ENGINE_by_id() */ + } /* FIXME user data? */ if(listener->require_certificate){ SSL_CTX_set_verify(listener->ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, client_certificate_verify); @@ -440,18 +457,53 @@ int net__socket_listen(struct mosquitto__listener *listener) if(rc != 1){ log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to load server certificate \"%s\". Check certfile.", listener->certfile); COMPAT_CLOSE(sock); + ENGINE_FINISH(engine); return 1; } - rc = SSL_CTX_use_PrivateKey_file(listener->ssl_ctx, listener->keyfile, SSL_FILETYPE_PEM); - if(rc != 1){ - log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to load server key file \"%s\". Check keyfile.", listener->keyfile); - COMPAT_CLOSE(sock); - return 1; + if(listener->tls_keyform == mosq_k_engine){ + UI_METHOD *ui_method = net__get_ui_method(); + if(listener->tls_engine_kpass_sha){ + if(!ENGINE_ctrl_cmd(engine, ENGINE_SECRET_MODE, ENGINE_SECRET_MODE_SHA, NULL, NULL, 0)){ + log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to set engine secret mode sha"); + COMPAT_CLOSE(sock); + ENGINE_FINISH(engine); + return 1; + } + if(!ENGINE_ctrl_cmd(engine, ENGINE_PIN, 0, listener->tls_engine_kpass_sha, NULL, 0)){ + log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to set engine pin"); + COMPAT_CLOSE(sock); + ENGINE_FINISH(engine); + return 1; + } + ui_method = NULL; + } + EVP_PKEY *pkey = ENGINE_load_private_key(engine, listener->keyfile, ui_method, NULL); + if(!pkey){ + log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to load engine private key file \"%s\".", listener->keyfile); + COMPAT_CLOSE(sock); + ENGINE_FINISH(engine); + return 1; + } + if(SSL_CTX_use_PrivateKey(listener->ssl_ctx, pkey) <= 0){ + log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to use engine private key file \"%s\".", listener->keyfile); + COMPAT_CLOSE(sock); + ENGINE_FINISH(engine); + return 1; + } + }else{ + rc = SSL_CTX_use_PrivateKey_file(listener->ssl_ctx, listener->keyfile, SSL_FILETYPE_PEM); + if(rc != 1){ + log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to load server key file \"%s\". Check keyfile.", listener->keyfile); + COMPAT_CLOSE(sock); + ENGINE_FINISH(engine); + return 1; + } } rc = SSL_CTX_check_private_key(listener->ssl_ctx); if(rc != 1){ log__printf(NULL, MOSQ_LOG_ERR, "Error: Server certificate/key are inconsistent."); COMPAT_CLOSE(sock); + ENGINE_FINISH(engine); return 1; } /* Load CRLs if they exist. */ @@ -460,6 +512,7 @@ int net__socket_listen(struct mosquitto__listener *listener) if(!store){ log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to obtain TLS store."); COMPAT_CLOSE(sock); + ENGINE_FINISH(engine); return 1; } lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); @@ -467,6 +520,7 @@ int net__socket_listen(struct mosquitto__listener *listener) if(rc != 1){ log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to load certificate revocation file \"%s\". Check crlfile.", listener->crlfile); COMPAT_CLOSE(sock); + ENGINE_FINISH(engine); return 1; } X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK); @@ -483,6 +537,7 @@ int net__socket_listen(struct mosquitto__listener *listener) if(mosquitto__tls_server_ctx(listener)){ COMPAT_CLOSE(sock); + ENGINE_FINISH(engine); return 1; } SSL_CTX_set_psk_server_callback(listener->ssl_ctx, psk_server_callback); @@ -491,6 +546,7 @@ int net__socket_listen(struct mosquitto__listener *listener) if(rc == 0){ log__printf(NULL, MOSQ_LOG_ERR, "Error: Unable to set TLS PSK hint."); COMPAT_CLOSE(sock); + ENGINE_FINISH(engine); return 1; } }