From 9c8afe1b8fe76336919fbfb59d25fc85bec58265 Mon Sep 17 00:00:00 2001 From: DBL2017 <1578770584@qq.com> Date: Thu, 25 Apr 2024 22:53:33 +0800 Subject: [PATCH 1/4] Add public key pinning Signed-off-by: DBL2017 <1578770584@qq.com> --- src/MQTTAsync.c | 2 ++ src/MQTTAsync.h | 5 ++- src/MQTTClient.h | 5 ++- src/SSLSocket.c | 94 +++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 103 insertions(+), 3 deletions(-) diff --git a/src/MQTTAsync.c b/src/MQTTAsync.c index c6a11f6d3..7cdc99026 100644 --- a/src/MQTTAsync.c +++ b/src/MQTTAsync.c @@ -753,6 +753,8 @@ int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options) free((void*)m->c->sslopts->privateKeyPassword); if (m->c->sslopts->enabledCipherSuites) free((void*)m->c->sslopts->enabledCipherSuites); + if (m->c->sslopts->publicKey) + free((void*)m->c->sslopts->publicKey); if (m->c->sslopts->struct_version >= 2) { if (m->c->sslopts->CApath) diff --git a/src/MQTTAsync.h b/src/MQTTAsync.h index e11af0482..64a6c0a44 100644 --- a/src/MQTTAsync.h +++ b/src/MQTTAsync.h @@ -1095,6 +1095,9 @@ typedef struct /** The password to load the client's privateKey if encrypted. */ const char* privateKeyPassword; + /** This setting points to the file in PEM format containing the server's public key, can be used public key pinning*/ + const char* publicKey; + /** * The list of cipher suites that the client will present to the server during the SSL handshake. For a * full explanation of the cipher list format, please see the OpenSSL on-line documentation: @@ -1176,7 +1179,7 @@ typedef struct unsigned int protos_len; } MQTTAsync_SSLOptions; -#define MQTTAsync_SSLOptions_initializer { {'M', 'Q', 'T', 'S'}, 5, NULL, NULL, NULL, NULL, NULL, 1, MQTT_SSL_VERSION_DEFAULT, 0, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0 } +#define MQTTAsync_SSLOptions_initializer { {'M', 'Q', 'T', 'S'}, 5, NULL, NULL, NULL, NULL, NULL, NULL, 1, MQTT_SSL_VERSION_DEFAULT, 0, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0 } /** Utility structure where name/value pairs are needed */ typedef struct diff --git a/src/MQTTClient.h b/src/MQTTClient.h index a5dc7f267..1af11fda3 100644 --- a/src/MQTTClient.h +++ b/src/MQTTClient.h @@ -698,6 +698,9 @@ typedef struct /** The password to load the client's privateKey if encrypted. */ const char* privateKeyPassword; + /** This setting points to the file in PEM format containing the server's public key, can be used public key pinning*/ + const char* publicKey; + /** * The list of cipher suites that the client will present to the server during the SSL handshake. For a * full explanation of the cipher list format, please see the OpenSSL on-line documentation: @@ -779,7 +782,7 @@ typedef struct unsigned int protos_len; } MQTTClient_SSLOptions; -#define MQTTClient_SSLOptions_initializer { {'M', 'Q', 'T', 'S'}, 5, NULL, NULL, NULL, NULL, NULL, 1, MQTT_SSL_VERSION_DEFAULT, 0, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0 } +#define MQTTClient_SSLOptions_initializer { {'M', 'Q', 'T', 'S'}, 5, NULL, NULL, NULL, NULL, NULL, NULL, 1, MQTT_SSL_VERSION_DEFAULT, 0, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0 } /** * MQTTClient_libraryInfo is used to store details relating to the currently used diff --git a/src/SSLSocket.c b/src/SSLSocket.c index a4941b60c..5af12e826 100644 --- a/src/SSLSocket.c +++ b/src/SSLSocket.c @@ -47,6 +47,7 @@ extern Sockets mod_s; static int SSLSocket_error(char* aString, SSL* ssl, SOCKET sock, int rc, int (*cb)(const char *str, size_t len, void *u), void* u); +static int SSLSocket_certificate_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx); char* SSL_get_verify_result_string(int rc); void SSL_CTX_info_callback(const SSL* ssl, int where, int ret); char* SSLSocket_get_version_string(int version); @@ -78,6 +79,8 @@ static ssl_mutex_type sslCoreMutex; /* Used to store MQTTClient_SSLOptions for TLS-PSK callback */ static int tls_ex_index_ssl_opts; +/* Used to store MQTTClient_SSLOptions for TLS Certificate verify callback */ +static int tls_ex_index_ssl_opts_for_verify_cb; #if defined(_WIN32) || defined(_WIN64) #define iov_len len @@ -122,6 +125,92 @@ static int SSLSocket_error(char* aString, SSL* ssl, SOCKET sock, int rc, int (*c return error; } +static int SSLSocket_certificate_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) +{ + int error = X509_STORE_CTX_get_error(x509_ctx); + FUNC_ENTRY; + + int iVerifyOK = 0; + + /* depth==0 server certificate */ + int depth = X509_STORE_CTX_get_error_depth(x509_ctx); + Log(TRACE_MIN, -1, "preverify_ok=%d depth=%d", preverify_ok, depth); + if (depth == 0) + { + /* 1. Extracting the public key from a certificate. */ + X509* cert = X509_STORE_CTX_get_current_cert(x509_ctx); + if (cert == NULL) + goto exit; + + EVP_PKEY* pubkey_from_cert = X509_get_pubkey(cert); + if (pubkey_from_cert == NULL) + { + Log(TRACE_MIN, -1, "Error extracting public key from certificate"); + goto exit; + } + + /* 2. The public key from the configuration. */ + SSL* ssl = X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + if (ssl == NULL) + { + Log(TRACE_MIN, -1, "Error SSL get_ex_data"); + goto exit; + } + SSL_CTX* ssl_ctx = SSL_get_SSL_CTX(ssl); + if (ssl_ctx == NULL) + { + Log(TRACE_MIN, -1, "Error SSL_CTX get_ex_data"); + goto exit; + } + + MQTTClient_SSLOptions* opts = SSL_CTX_get_ex_data(ssl_ctx, tls_ex_index_ssl_opts_for_verify_cb); + if (opts == NULL) + { + Log(TRACE_MIN, -1, "Error opts get_ex_data"); + goto exit; + } + + if (opts->publicKey == NULL || strlen(opts->publicKey) <= 0 || access(opts->publicKey, R_OK) != 0) + { + Log(TRACE_MIN, -1, "Error opts pubKey invalid"); + goto exit; + } + + FILE* pubkey_file = fopen(opts->publicKey, "r"); + if (pubkey_file == NULL) + { + Log(TRACE_MIN, -1,"Error opening public key file"); + goto exit; + } + + EVP_PKEY* pubkey_from_file = PEM_read_PUBKEY(pubkey_file, NULL, NULL, NULL); + fclose(pubkey_file); + if (pubkey_from_file == NULL) + { + Log(TRACE_MIN, -1, "Error reading public key file"); + goto exit; + } + + // 3. compare +#if (OPENSSL_VERSION_NUMBER >= 0x030000000) /* 3.0.0 and later */ + if (EVP_PKEY_eq(pubkey_from_file, pubkey_from_cert) == 1) +#else + if (EVP_PKEY_cmp(pubkey_from_file, pubkey_from_cert) == 1) +#endif + iVerifyOK = 1; + + // 4. cleanup + EVP_PKEY_free(pubkey_from_cert); + EVP_PKEY_free(pubkey_from_file); + } + else + iVerifyOK = preverify_ok; + +exit: + FUNC_EXIT_RC(error); + return iVerifyOK; +} + static struct { int code; @@ -490,6 +579,7 @@ int SSLSocket_initialize(void) SSL_create_mutex(&sslCoreMutex); tls_ex_index_ssl_opts = SSL_get_ex_new_index(0, "paho ssl options", NULL, NULL, NULL); + tls_ex_index_ssl_opts_for_verify_cb = SSL_get_ex_new_index(0, "paho ssl options", NULL, NULL, NULL); exit: FUNC_EXIT_RC(rc); @@ -675,6 +765,8 @@ int SSLSocket_createContext(networkHandles* net, MQTTClient_SSLOptions* opts) SSL_CTX_set_psk_client_callback(net->ctx, call_ssl_psk_cb); } #endif + if (opts->publicKey !=NULL ) + SSL_CTX_set_ex_data(net->ctx, tls_ex_index_ssl_opts_for_verify_cb, opts); #if (OPENSSL_VERSION_NUMBER >= 0x010002000) /* 1.0.2 and later */ if (opts->protos != NULL && opts->protos_len > 0) @@ -722,7 +814,7 @@ int SSLSocket_setSocketForSSL(networkHandles* net, MQTTClient_SSLOptions* opts, SSL_CTX_set_info_callback(net->ctx, SSL_CTX_info_callback); SSL_CTX_set_msg_callback(net->ctx, SSL_CTX_msg_callback); if (opts->enableServerCertAuth) - SSL_CTX_set_verify(net->ctx, SSL_VERIFY_PEER, NULL); + SSL_CTX_set_verify(net->ctx, SSL_VERIFY_PEER, opts->publicKey != NULL ? SSLSocket_certificate_verify_cb : NULL); net->ssl = SSL_new(net->ctx); From b5352bc7b9f6865bb7ec7822bcc0cd4fe7c0232e Mon Sep 17 00:00:00 2001 From: DBL2017 <1578770584@qq.com> Date: Fri, 26 Apr 2024 20:09:44 +0800 Subject: [PATCH 2/4] remove access function, windows compatible --- src/SSLSocket.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SSLSocket.c b/src/SSLSocket.c index 5af12e826..12458bd3c 100644 --- a/src/SSLSocket.c +++ b/src/SSLSocket.c @@ -170,7 +170,7 @@ static int SSLSocket_certificate_verify_cb(int preverify_ok, X509_STORE_CTX *x50 goto exit; } - if (opts->publicKey == NULL || strlen(opts->publicKey) <= 0 || access(opts->publicKey, R_OK) != 0) + if (opts->publicKey == NULL || strlen(opts->publicKey) <= 0 ) { Log(TRACE_MIN, -1, "Error opts pubKey invalid"); goto exit; From 850f9b16bc822c5a0ed16bca60741f1d44f8a95f Mon Sep 17 00:00:00 2001 From: DBL2017 <1578770584@qq.com> Date: Fri, 27 Sep 2024 16:04:11 +0000 Subject: [PATCH 3/4] add ssl struct_version 6 --- src/MQTTAsync.c | 7 +++++-- src/MQTTAsync.h | 10 +++++++--- src/MQTTClient.h | 5 ++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/MQTTAsync.c b/src/MQTTAsync.c index 7cdc99026..901ea5b15 100644 --- a/src/MQTTAsync.c +++ b/src/MQTTAsync.c @@ -753,13 +753,16 @@ int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options) free((void*)m->c->sslopts->privateKeyPassword); if (m->c->sslopts->enabledCipherSuites) free((void*)m->c->sslopts->enabledCipherSuites); - if (m->c->sslopts->publicKey) - free((void*)m->c->sslopts->publicKey); if (m->c->sslopts->struct_version >= 2) { if (m->c->sslopts->CApath) free((void*)m->c->sslopts->CApath); } + if(m->c->sslopts->struct_version >= 6) + { + if (m->c->sslopts->publicKey) + free((void*)m->c->sslopts->publicKey); + } free((void*)m->c->sslopts); m->c->sslopts = NULL; } diff --git a/src/MQTTAsync.h b/src/MQTTAsync.h index 64a6c0a44..42836d877 100644 --- a/src/MQTTAsync.h +++ b/src/MQTTAsync.h @@ -1070,12 +1070,13 @@ typedef struct /** The eyecatcher for this structure. Must be MQTS */ char struct_id[4]; - /** The version number of this structure. Must be 0, 1, 2, 3, 4 or 5. + /** The version number of this structure. Must be 0, 1, 2, 3, 4, 5 or 6. * 0 means no sslVersion * 1 means no verify, CApath * 2 means no ssl_error_context, ssl_error_cb * 3 means no ssl_psk_cb, ssl_psk_context, disableDefaultTrustStore * 4 means no protos, protos_len + * 6 means support public key pinning */ int struct_version; @@ -1095,7 +1096,10 @@ typedef struct /** The password to load the client's privateKey if encrypted. */ const char* privateKeyPassword; - /** This setting points to the file in PEM format containing the server's public key, can be used public key pinning*/ + /** + * This setting points to the file in PEM format containing the server's public key, can be used public key pinning + * Exists only if struct_version >= 6 + * */ const char* publicKey; /** @@ -1179,7 +1183,7 @@ typedef struct unsigned int protos_len; } MQTTAsync_SSLOptions; -#define MQTTAsync_SSLOptions_initializer { {'M', 'Q', 'T', 'S'}, 5, NULL, NULL, NULL, NULL, NULL, NULL, 1, MQTT_SSL_VERSION_DEFAULT, 0, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0 } +#define MQTTAsync_SSLOptions_initializer { {'M', 'Q', 'T', 'S'}, 6, NULL, NULL, NULL, NULL, NULL, NULL, 1, MQTT_SSL_VERSION_DEFAULT, 0, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0 } /** Utility structure where name/value pairs are needed */ typedef struct diff --git a/src/MQTTClient.h b/src/MQTTClient.h index 1af11fda3..0a9b10336 100644 --- a/src/MQTTClient.h +++ b/src/MQTTClient.h @@ -698,7 +698,10 @@ typedef struct /** The password to load the client's privateKey if encrypted. */ const char* privateKeyPassword; - /** This setting points to the file in PEM format containing the server's public key, can be used public key pinning*/ + /** + * This setting points to the file in PEM format containing the server's public key, can be used public key pinning + * Exists only if struct_version >= 6 + * */ const char* publicKey; /** From e3b58dfe5761ead0867a3629a95010c76a65d197 Mon Sep 17 00:00:00 2001 From: DBL2017 <1578770584@qq.com> Date: Sat, 28 Sep 2024 09:16:25 +0000 Subject: [PATCH 4/4] struct_version param check --- src/MQTTAsync.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MQTTAsync.c b/src/MQTTAsync.c index 901ea5b15..d51ecdebf 100644 --- a/src/MQTTAsync.c +++ b/src/MQTTAsync.c @@ -600,7 +600,7 @@ int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options) } if (options->struct_version != 0 && options->ssl) /* check validity of SSL options structure */ { - if (strncmp(options->ssl->struct_id, "MQTS", 4) != 0 || options->ssl->struct_version < 0 || options->ssl->struct_version > 5) + if (strncmp(options->ssl->struct_id, "MQTS", 4) != 0 || options->ssl->struct_version < 0 || options->ssl->struct_version > 6) { rc = MQTTASYNC_BAD_STRUCTURE; goto exit;