Skip to content

Commit

Permalink
MDEV-31855 SSL cert validation protocol extension
Browse files Browse the repository at this point in the history
* extend the client auth plugin API with a new callback
* relax the plugin version check to allow load a plugin with the
  same major version, even if the minor versions differ
* implement the protocol extension:
  - don't abort at once if the certificate is self signed and
    no CA was explicitly specified
  - allow it if it passes fingerprint check
  - allow it if plugin has hash_password_bin callback, password was
    non-empty and the control hash matches server's
  • Loading branch information
vuvova committed Feb 4, 2024
1 parent 50f65db commit a99570c
Show file tree
Hide file tree
Showing 18 changed files with 144 additions and 26 deletions.
11 changes: 7 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,8 @@ IF(NOT WITH_SSL STREQUAL "OFF")
ENDIF()
IF(OPENSSL_FOUND)
ADD_DEFINITIONS(-DHAVE_OPENSSL -DHAVE_TLS)
SET(SSL_SOURCES "${CC_SOURCE_DIR}/libmariadb/secure/openssl.c")
SET(SSL_SOURCES "${CC_SOURCE_DIR}/libmariadb/secure/openssl.c"
"${CC_SOURCE_DIR}/libmariadb/secure/openssl_crypt.c")
SET(SSL_LIBRARIES ${OPENSSL_SSL_LIBRARY} ${OPENSSL_CRYPTO_LIBRARY})
IF(WIN32)
CHECK_INCLUDE_FILES (${OPENSSL_INCLUDE_DIR}/openssl/applink.c HAVE_OPENSSL_APPLINK_C)
Expand All @@ -332,7 +333,8 @@ IF(NOT WITH_SSL STREQUAL "OFF")
FIND_PACKAGE(GnuTLS "3.3.24" REQUIRED)
IF(GNUTLS_FOUND)
ADD_DEFINITIONS(-DHAVE_GNUTLS -DHAVE_TLS)
SET(SSL_SOURCES "${CC_SOURCE_DIR}/libmariadb/secure/gnutls.c")
SET(SSL_SOURCES "${CC_SOURCE_DIR}/libmariadb/secure/gnutls.c"
"${CC_SOURCE_DIR}/libmariadb/secure/gnutls_crypt.c")
SET(SSL_LIBRARIES ${GNUTLS_LIBRARY})
SET(TLS_LIBRARY_VERSION "GnuTLS ${GNUTLS_VERSION_STRING}")
INCLUDE_DIRECTORIES(${GNUTLS_INCLUDE_DIR})
Expand All @@ -342,12 +344,13 @@ IF(NOT WITH_SSL STREQUAL "OFF")
ENDIF()
IF(WIN32)
IF(WITH_SSL STREQUAL "SCHANNEL")
ADD_DEFINITIONS(-DHAVE_SCHANNEL -DHAVE_TLS)
ADD_DEFINITIONS(-DHAVE_SCHANNEL -DHAVE_TLS -DHAVE_WINCRYPT)
SET(SSL_SOURCES "${CC_SOURCE_DIR}/libmariadb/secure/schannel.c"
"${CC_SOURCE_DIR}/libmariadb/secure/win_crypt.c"
"${CC_SOURCE_DIR}/libmariadb/secure/ma_schannel.c"
"${CC_SOURCE_DIR}/libmariadb/secure/schannel_certs.c")
INCLUDE_DIRECTORIES("${CC_SOURCE_DIR}/plugins/pvio/")
SET(SSL_LIBRARIES secur32)
SET(SSL_LIBRARIES secur32 crypt32 bcrypt)
SET(TLS_LIBRARY_VERSION "Schannel ${CMAKE_SYSTEM_VERSION}")
ENDIF()
ENDIF()
Expand Down
13 changes: 13 additions & 0 deletions include/ma_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,16 @@ typedef struct st_mariadb_field_extension
{
MARIADB_CONST_STRING metadata[MARIADB_FIELD_ATTR_LAST+1]; /* 10.5 */
} MA_FIELD_EXTENSION;

#if defined(HAVE_SCHANNEL) || defined(HAVE_GNUTLS)
#define reset_tls_self_signed_error(mysql) \
do { \
free((char*)mysql->net.tls_self_signed_error); \
mysql->net.tls_self_signed_error= 0; \
} while(0)
#else
#define reset_tls_self_signed_error(mysql) \
do { \
mysql->net.tls_self_signed_error= 0; \
} while(0)
#endif
2 changes: 1 addition & 1 deletion include/mariadb_com.h
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ typedef struct st_net {
my_bool unused_2;
my_bool compress;
my_bool unused_3;
void *unused_4;
const char *tls_self_signed_error;
unsigned int last_errno;
unsigned char error;
my_bool unused_5;
Expand Down
3 changes: 2 additions & 1 deletion include/mysql/client_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
#define MYSQL_CLIENT_PLUGIN_RESERVED2 1
#define MYSQL_CLIENT_AUTHENTICATION_PLUGIN 2 /* authentication */

#define MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION 0x0100
#define MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION 0x0101
#define MYSQL_CLIENT_MAX_PLUGINS 3

/* Connector/C specific plugin types */
Expand Down Expand Up @@ -128,6 +128,7 @@ struct st_mysql_client_plugin_AUTHENTICATION
{
MYSQL_CLIENT_PLUGIN_HEADER
int (*authenticate_user)(MYSQL_PLUGIN_VIO *vio, struct st_mysql *mysql);
int (*hash_password_bin)(struct st_mysql *mysql, unsigned char *hash, size_t *hash_length);
};

/******** trace plugin *******/
Expand Down
3 changes: 1 addition & 2 deletions libmariadb/ma_client_plugin.c.in
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,7 @@ static int get_plugin_nr(uint type)

static const char *check_plugin_version(struct st_mysql_client_plugin *plugin, unsigned int version)
{
if (plugin->interface_version < version ||
(plugin->interface_version >> 8) > (version >> 8))
if (plugin->interface_version >> 8 != version >> 8)
return "Incompatible client plugin interface";
return 0;
}
Expand Down
2 changes: 2 additions & 0 deletions libmariadb/ma_pvio.c
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,7 @@ my_bool ma_pvio_start_ssl(MARIADB_PVIO *pvio)
3. verrify finger print
*/
if (pvio->mysql->options.extension->tls_verify_server_cert &&
!pvio->mysql->net.tls_self_signed_error &&
ma_pvio_tls_verify_server_cert(pvio->ctls))
return 1;

Expand All @@ -556,6 +557,7 @@ my_bool ma_pvio_start_ssl(MARIADB_PVIO *pvio)
pvio->mysql->options.extension->tls_fp,
pvio->mysql->options.extension->tls_fp_list))
return 1;
reset_tls_self_signed_error(pvio->mysql); // validated
}

return 0;
Expand Down
3 changes: 3 additions & 0 deletions libmariadb/mariadb_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -1440,6 +1440,8 @@ mysql_real_connect(MYSQL *mysql, const char *host, const char *user,
if (!mysql->options.extension || !mysql->options.extension->status_callback)
mysql_optionsv(mysql, MARIADB_OPT_STATUS_CALLBACK, NULL, NULL);

reset_tls_self_signed_error(mysql);

/* if host contains a semicolon, we need to parse connection string */
if (host && strchr(host, ';'))
{
Expand Down Expand Up @@ -2444,6 +2446,7 @@ mysql_close(MYSQL *mysql)
mysql_close_memory(mysql);
mysql_close_options(mysql);
ma_clear_session_state(mysql);
reset_tls_self_signed_error(mysql);

if (mysql->net.extension)
{
Expand Down
20 changes: 16 additions & 4 deletions libmariadb/secure/gnutls.c
Original file line number Diff line number Diff line change
Expand Up @@ -1371,10 +1371,22 @@ static int my_verify_callback(gnutls_session_t ssl)
{
gnutls_datum_t out;
int type;
/* accept self signed certificates if we don't have to verify server cert */
if (!(mysql->options.extension->tls_verify_server_cert) &&
(status & GNUTLS_CERT_SIGNER_NOT_FOUND))
return 0;

if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
{
/* accept self signed certificates if we don't have to verify server cert */
if (!mysql->options.extension->tls_verify_server_cert)
return 0;

/* postpone the error for self signed certificates if CA isn't set */
if (!mysql->options.ssl_ca && !mysql->options.ssl_capath)
{
type= gnutls_certificate_type_get(ssl);
gnutls_certificate_verification_status_print(status, type, &out, 0);
mysql->net.tls_self_signed_error= (char*)out.data;
return 0;
}
}

/* gnutls default error message "certificate validation failed" isn't very
descriptive, so we provide more information about the error here */
Expand Down
7 changes: 7 additions & 0 deletions libmariadb/secure/ma_schannel.c
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,13 @@ my_bool ma_schannel_verify_certs(MARIADB_TLS *ctls, BOOL verify_server_name)
end:
if (!ret)
{
/* postpone the error for self signed certificates if CA isn't set */
if (status == CERT_E_UNTRUSTEDROOT && !ca_file && !ca_path)
{
mysql->net.tls_self_signed_error= strdup(errmsg);
ret= 1;
}
else
pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, 0, errmsg);
}
if (pServerCert)
Expand Down
6 changes: 5 additions & 1 deletion libmariadb/secure/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,11 @@ my_bool ma_tls_connect(MARIADB_TLS *ctls)
mysql->options.ssl_ca || mysql->options.ssl_capath)
{
long x509_err= SSL_get_verify_result(ssl);
if (x509_err != X509_V_OK)
if ((x509_err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
x509_err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) && rc == 1 &&
!mysql->options.ssl_ca && !mysql->options.ssl_capath)
mysql->net.tls_self_signed_error= X509_verify_cert_error_string(x509_err);
else if (x509_err != X509_V_OK)
{
my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN,
ER(CR_SSL_CONNECTION_ERROR), X509_verify_cert_error_string(x509_err));
Expand Down
3 changes: 2 additions & 1 deletion plugins/auth/auth_gssapi_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,6 @@ struct st_mysql_client_plugin_AUTHENTICATION _mysql_client_plugin_declaration_ =
NULL,
NULL,
NULL,
gssapi_auth_client
gssapi_auth_client,
NULL
};
3 changes: 2 additions & 1 deletion plugins/auth/caching_sha2_pw.c
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ struct st_mysql_client_plugin_AUTHENTICATION _mysql_client_plugin_declaration_ =
auth_caching_sha2_init,
auth_caching_sha2_deinit,
NULL,
auth_caching_sha2_client
auth_caching_sha2_client,
NULL
};

#ifdef HAVE_WINCRYPT
Expand Down
3 changes: 2 additions & 1 deletion plugins/auth/dialog.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ struct st_mysql_client_plugin_AUTHENTICATION _mysql_client_plugin_declaration_ =
auth_dialog_init,
NULL,
NULL,
auth_dialog_open
auth_dialog_open,
NULL
};


Expand Down
3 changes: 2 additions & 1 deletion plugins/auth/ed25519.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ struct st_mysql_client_plugin_AUTHENTICATION _mysql_client_plugin_declaration_ =
auth_ed25519_init,
auth_ed25519_deinit,
NULL,
auth_ed25519_client
auth_ed25519_client,
NULL
};


Expand Down
3 changes: 2 additions & 1 deletion plugins/auth/mariadb_cleartext.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ struct st_mysql_client_plugin_AUTHENTICATION _mysql_client_plugin_declaration_ =
NULL,
NULL,
NULL,
clear_password_auth_client
clear_password_auth_client,
NULL
};


79 changes: 73 additions & 6 deletions plugins/auth/my_auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <errmsg.h>
#include <string.h>
#include <ma_common.h>
#include <ma_crypt.h>
#include <mysql/client_plugin.h>

typedef struct st_mysql_client_plugin_AUTHENTICATION auth_plugin_t;
Expand All @@ -14,6 +15,16 @@ extern char *ma_send_connect_attr(MYSQL *mysql, unsigned char *buffer);
extern int ma_read_ok_packet(MYSQL *mysql, uchar *pos, ulong length);
extern unsigned char *mysql_net_store_length(unsigned char *packet, ulonglong length);

#define hashing(p) (p->interface_version >= 0x0101 && p->hash_password_bin)

static int set_error_from_tls_self_signed_error(MYSQL *mysql)
{
my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN,
ER(CR_SSL_CONNECTION_ERROR), mysql->net.tls_self_signed_error);
reset_tls_self_signed_error(mysql);
return 1;
}

typedef struct {
int (*read_packet)(struct st_plugin_vio *vio, uchar **buf);
int (*write_packet)(struct st_plugin_vio *vio, const uchar *pkt, size_t pkt_len);
Expand Down Expand Up @@ -50,7 +61,8 @@ auth_plugin_t mysql_native_password_client_plugin=
NULL,
NULL,
NULL,
native_password_auth_client
native_password_auth_client,
NULL
};


Expand Down Expand Up @@ -110,7 +122,8 @@ auth_plugin_t dummy_fallback_client_plugin=
NULL,
NULL,
NULL,
dummy_fallback_auth_client
dummy_fallback_auth_client,
NULL
};


Expand Down Expand Up @@ -350,6 +363,13 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio,
}
if (ma_pvio_start_ssl(mysql->net.pvio))
goto error;
if (mysql->net.tls_self_signed_error &&
(!mysql->passwd || !mysql->passwd[0] || !hashing(mpvio->plugin)))
{
/* cannot use auth to validate the cert */
set_error_from_tls_self_signed_error(mysql);
goto error;
}
}
#endif /* HAVE_TLS */

Expand Down Expand Up @@ -727,15 +747,62 @@ int run_plugin_auth(MYSQL *mysql, char *data, uint data_len,
auth_plugin_name, MYSQL_CLIENT_AUTHENTICATION_PLUGIN)))
auth_plugin= &dummy_fallback_client_plugin;

/* can we use this plugin with this tls server cert ? */
if (mysql->net.tls_self_signed_error && !hashing(auth_plugin))
return set_error_from_tls_self_signed_error(mysql);
goto retry;

}
/*
net->read_pos[0] should always be 0 here if the server implements
the protocol correctly
*/
if (mysql->net.read_pos[0] == 0)
return ma_read_ok_packet(mysql, mysql->net.read_pos + 1, pkt_length);
return 1;
if (mysql->net.read_pos[0] != 0)
return 1;
if (ma_read_ok_packet(mysql, mysql->net.read_pos + 1, pkt_length))
return -1;

if (!mysql->net.tls_self_signed_error)
return 0;

assert(mysql->options.use_ssl);
assert(mysql->options.extension->tls_verify_server_cert);
assert(!mysql->options.ssl_ca);
assert(!mysql->options.ssl_capath);
assert(!mysql->options.extension->tls_fp);
assert(!mysql->options.extension->tls_fp_list);
assert(hashing(auth_plugin));
assert(mysql->passwd[0]);
if (mysql->info && mysql->info[0] == '\1')
{
MA_HASH_CTX *ctx = NULL;
unsigned char buf[1024], digest[MA_SHA256_HASH_SIZE];
char fp[128], hexdigest[sizeof(digest)*2+1], *hexsig= mysql->info + 1;
size_t buflen= sizeof(buf) - 1, fplen;

mysql->info= NULL; /* no need to confuse the client with binary info */

if (!(fplen= ma_tls_get_finger_print(mysql->net.pvio->ctls, MA_HASH_SHA256,
fp, sizeof(fp))))
return 1; /* error is already set */

if (auth_plugin->hash_password_bin(mysql, buf, &buflen) ||
!(ctx= ma_hash_new(MA_HASH_SHA256)))
{
SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0);
return 1;
}

ma_hash_input(ctx, (unsigned char*)buf, buflen);
ma_hash_input(ctx, (unsigned char*)mysql->scramble_buff, SCRAMBLE_LENGTH);
ma_hash_input(ctx, (unsigned char*)fp, fplen);
ma_hash_result(ctx, digest);
ma_hash_free(ctx);

mysql_hex_string(hexdigest, (char*)digest, sizeof(digest));
if (strcmp(hexdigest, hexsig) == 0)
return 0; /* phew. self-signed certificate is validated! */
}

return set_error_from_tls_self_signed_error(mysql);
}

3 changes: 2 additions & 1 deletion plugins/auth/old_password.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ struct st_mysql_client_plugin_AUTHENTICATION _mysql_client_plugin_declaration_ =
NULL,
NULL,
NULL,
auth_old_password
auth_old_password,
NULL
};

/**
Expand Down
3 changes: 2 additions & 1 deletion plugins/auth/sha256_pw.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ struct st_mysql_client_plugin_AUTHENTICATION _mysql_client_plugin_declaration_ =
auth_sha256_init,
NULL,
NULL,
auth_sha256_client
auth_sha256_client,
NULL
};

#ifdef HAVE_WINCRYPT
Expand Down

0 comments on commit a99570c

Please sign in to comment.