From fba194b3245ccc217e40f290fa6c7200769a9886 Mon Sep 17 00:00:00 2001 From: DBL2017 <1578770584@qq.com> Date: Thu, 25 Apr 2024 22:53:33 +0800 Subject: [PATCH] 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);