For testing purposes (the python3 dummy server can't handle
further communication after TLS handshake succeeded) support
for verification callback was added.
my_bool callback(MYSQL *mysql, unsigned int *flags, my_bool verified)
Parameter:
- mysql connection handle for current connection
- flags verification flags
- verified true if callback was called after verification,
otherwise false
Return value:
- False (0) to continue
- True (1) to abort tls connection
The callback function can be registered via
mysql_optionsv(mysql, MARIADB_OPT_TLS_VERIFICATION_CALLBACK, callback);
Since the MARIADB_TLS_VERIFY_TRUST flag might be cleared in my_auth,
we store the original result of peer certificate verification in
mysql->extension->tls_validation.
This value can be obtained via mariadb_get_infov API function
using option MARIADB_TLS_VERIFY_STATUS.
Peer certificate validation:
Since version 3.4 peer certificate verification is enabled by default.
It can be disabled via `mysql_optionsv`, using option
MYSQL_OPT_SSL_VERIFY_SERVER_CERT:
my_bool verify= 0;
mysql_options(mariadb, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, &verify);
Self signed certificates
If the client obtained a self signed peer certificate from MariaDB server
the verification will fail, with the following exceptions:
* If the connection between client and server is considered to be secure:, e.g.
* a unix_socket is used for client server communication
* hostname is localhost (Windows operating system), 127.0.0.1 or ::1
* a specified fingerprint matches the fingerprint of the peer certificate (see below)
* a client can verify the certificate using account password, it's possible if
* account has a password
* authentication plugin is "secure without TLS", that is, one of
mysql_native_password, ed25519 or parsec.
Fingerprint verification of the peer certificate
A fingerprint is a cryptographic hash (SHA-256, SHA-384 or SHA-512) of the peer
certificate's binary data. Even if the fingerprint matches, an expired or
revoked certificate will not be accepted.
For security reasons support for MD5 and SHA1 has been removed.
Technical details:
==================
- Peer certificate verification call was removed from ma_tls_connect, instead it
will be called directly after the handshake succeeded (my_auth.c)
- mysql->net.tls_self_signed_error was replaced by mysql->net.tls_verify_status which
contains the result of the peer certfificate verification:
The verification status can be obtained with mariadb_get_infov using new parameter
MARIADB_TLS_VERIFY_STATUS.
unsigned int tls_verify_status;
mariadb_get_infov(mysql, MARIADB_TLS_VERIFY_STATUS, &tls_verify_status);
The result is a combination of the following flags:
MARIADB_TLS_VERIFY_OK 0
MARIADB_TLS_VERIFY_TRUST 1
MARIADB_TLS_VERIFY_HOST 2
MARIADB_TLS_VERIFY_PERIOD 4
MARIADB_TLS_VERIFY_FINGERPRINT 8
MARIADB_TLS_VERIFY_REVOKED 16
MARIADB_TLS_VERIFY_UNKNOWN 32
- GnuTLS peer certificate verification callback was removed and replaced by
gnutls_verify_peers2() api function, so the peer certificate validation
will happen after handshake.
- OpenSSL implementation will no longer use SSL_verify_result to check the
validity of the peer certificate. Instead a callback function will be called
during the handshake, which collects all certificate validation errors.
- If the peer certificate is not trusted, hostname verification will be
skipped.
- Testing
Added new test tls, which implements a python based dummy server, which allows
to set different certificates and TLS options. Please note. that tests are
expected to fail, since the server doesn't support further steps like user
authentication etc. after the handshake. Prerequisite for running the tls test
is Python3.
1) Fix check if end was reached (<= instead of <), so last parameter will
not be ignored in case it is an empty string.
2) Empty strings will be passed as NULL`in _mariadb_set_conf_option.
Added a new structure MARIADB_X509_INFO, which
contains information about servers certificate.
The information can be obtained via mysql_get_infov API
function:
MARIADB_X509_INFO *info;
mariadb_get_infov(mysql, MARIADB_TLS_PEER_CERT_INFO, &info);
With MDEV-30366, server now permit to send a result-set containing generated id and Affected rows for each bulk operation. This feature can be enabled with option MARIADB_OPT_BULK_UNIT_RESULTS when server supports it.
This reverts 395641549ac7..536d9e2b9e5b, in particular:
8dffd56936 MDEV-31857 enable MYSQL_OPT_SSL_VERIFY_SERVER_CERT by default
a99570c118 MDEV-31855 SSL cert validation protocol extension
9aa15e72a7 TLS fingerprint
and related commits
because the default value of every option is 0
(option and option.extension are bzero-ed to reset),
tls_verify_server_cert was renamed to tls_allow_invalid_server_cert
with the default value of 0, "do not allow".
API didn't change, it's still MYSQL_OPT_SSL_VERIFY_SERVER_CERT
* 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
they cannot be sent by the server (ma_net_safe_read() guarantees that)
so they all should be safe and not forged
also, use existing macros to check for error ranges, they are
sufficiently parenthesized to avoid compiler warnings (errors with -Werror)
about "you might want to add parentheses here"
MariaDB Connector/C does not distinguish [application-layer error
packets](https://mariadb.com/kb/en/err_packet) that it receives prior to TLS
handshake completion from those that it receives immediately after.
(A trivially modified server built from
https://github.com/dlenski/mariadb-server/commit/demonstration_of_CONC-648_vulnerability
can easily be used to demonstrate this.)
Pre-TLS error packet received from this trivially modified server. This packet
should NOT be trusted to actually originate from the server:
$ mariadb --ssl --ssl-verify-server-cert -uUsername -pVerySecretPassword -h CONC-648.vuln.demo.server.com
ERROR 1815 (HY000): Internal error: Client will accept this error as genuine even if running with --ssl --ssl-verify-server-cert, and even though this error is sent in plaintext PRIOR TO TLS HANDSHAKE.
Post-(TLS handshake) error packet received from a normal MariaDB server upon
an attempt to connect with incorrect credentials. This error packet CAN be
trusted to actually originate from the server, assuming transitive trust in
the TLS protocol implementation and PKI-based certificate validation:
$ mariadb --ssl --ssl-verify-server-cert -uUsername -pWrongPassword -h $NORMAL_MARIADB10.6.14_SERVER
ERROR 1045 (28000): Access denied for user 'Username'@'A.B.C.D' (using password: YES)
This client behavior opens up MariaDB Connector/C clients to an extremely
straightforward [downgrade attack](https://en.wikipedia.org/wiki/Downgrade_attack).
An on-path or pervasive attacker can inject errors into MariaDB
client→server connections that are intended to be protected by TLS, and the
client has no clear mechanism to distinguish such errors from errors that
actually come from the server.
An attacker could easily use this to DOS a client, or even influence its
behavior. For example, consider a client application which is configured…
1. To use TLS with server certificate validation
(`--ssl --ssl-verify-server-cert`), and
2. To wait for a back-off period and then *retry* connection attempts if the server
responds with `ER_CON_COUNT_ERROR` ("Too many connections") from the
server, and
3. To give up and shut down if its connection attempts fail with
`ER_ACCESS_DENIED_ERROR` ("Access denied for user"), on the assumption
that this is due to an incorrect or expired password, and cannot be
resolved without human intervention.
An attacker could completely disable the retry mechanism of this application
by intercepting connection attempts and replying with
`ER_ACCESS_DENIED_ERROR` packets.
This patch modifies MariaDB Connector/C so that if the client is configured
to use TLS, error packets received prior to the completion of the TLS
handshake are untrusted, and are changed to a generic `CR_CONNECTION_ERROR`.
$ mariadb --ssl --ssl-verify-server-cert -uUsername -pVerySecretPassword -h CONC-648.vuln.demo.server.com
ERROR 2002 (HY000): Received error packet before completion of TLS handshake. The authenticity of the following error cannot be verified:
1815 - Internal error: Client will accept this error as genuine even if running with --ssl --ssl-verify-server-cert, and even though this error is sent in plaintext PRIOR TO TLS HANDSHAKE.
All new code of the whole pull request, including one or several files
that are either new files or modified ones, are contributed under the
BSD-new license. I am contributing on behalf of my employer Amazon Web
Services, Inc.
Per @vuvova in
https://github.com/mariadb-corporation/mariadb-connector-c/pull/223#issuecomment-1854720364:
> I don't think the client should accept client-side errors from the server
> at all.
If the server sends an error packet with error codes in the ranges
`CR_{MIN,MAX}_ERROR` (codes [2000, 2999]) or `CER_{MIN,MAX}_ERROR` (codes
[5000, 5999]), we will replace these with `CR_MALFORMED_PACKET`, rather than
propagating them to the client user.
Since the server certification option is used by client
only, there is no need to have this flag in server and or
client capabilities. The server itself validates client
certificate depending on the user definition.
When resetting the connection with mysql reset_connection(), the
server_status must be checked and any other resultsets that mayi
exist must be removed.