diff --git a/src/MQTTAsync.c b/src/MQTTAsync.c index c6a11f6d..d51ecdeb 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; @@ -758,6 +758,11 @@ int MQTTAsync_connect(MQTTAsync handle, const MQTTAsync_connectOptions* options) 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 e11af048..42836d87 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,6 +1096,12 @@ 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 + * Exists only if struct_version >= 6 + * */ + 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 +1183,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'}, 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 a5dc7f26..0a9b1033 100644 --- a/src/MQTTClient.h +++ b/src/MQTTClient.h @@ -698,6 +698,12 @@ 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 + * Exists only if struct_version >= 6 + * */ + 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 +785,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 a4941b60..12458bd3 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 ) + { + 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);