From 0c6cf40965a897dd3846fc295109bc49c1e720fa Mon Sep 17 00:00:00 2001 From: Evan Elias Date: Thu, 2 Jan 2025 22:45:52 -0500 Subject: [PATCH] Improve TLS documentation for older servers Older/EOL database server versions tend to be built with ancient OpenSSL or yaSSL, which lack support for modern cipher suites and/or lack TLS 1.2+. At the same time, recent Golang versions have updated the default client tls.Config in ways that are incompatible with these old server versions. This commit improves TLS documentation to mention this incompatibility, provide sample code for solving it, and explain how "preferred" plaintext fallback mode is not triggered in cases of TLS incompatibilities. Closes #1635 by providing example code for solving the handshake failure. Additional information which may be helpful for reviewers/maintainers: TLS version * Go 1.18+ changes the default client TLS MinVersion to be TLS 1.2 * MySQL 5.5 and 5.6 supports TLS 1.0 * MySQL 5.7.0-5.7.27 supports TLS 1.1 * MySQL 5.7.28+ supports TLS 1.2 * MariaDB 10.1+ supports TLS 1.2 * I did not examine MySQL 5.1 or MariaDB 10.0 or anything more ancient Cipher suites * Go 1.22+ changes the default client TLS config to remove cipher suites which use RSA key exchange * MySQL 8.0+ and MariaDB 10.2+ fully support ECDHE cipher suites and are compatible with Go's current default cipher suite list. * MySQL 5.x typically needs RSA key exchange cipher suites, due to https://bugs.mysql.com/bug.php?id=82935. Likewise for MariaDB 10.1. * There are some exceptions, for example Percona Server 5.7 is built with a newer OpenSSL, https://docs.percona.com/percona-server/5.7/security/ssl-improvement.html * It is also possible to custom compile MySQL 5.7 with a newer OpenSSL version to solve the cipher suite issue, but this is not common. --- README.md | 9 +++++++-- utils.go | 24 ++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index da4593cc..ce0981c7 100644 --- a/README.md +++ b/README.md @@ -181,7 +181,11 @@ Valid Values: true, false Default: false ``` -`allowFallbackToPlaintext=true` acts like a `--ssl-mode=PREFERRED` MySQL client as described in [Command Options for Connecting to the Server](https://dev.mysql.com/doc/refman/5.7/en/connection-options.html#option_general_ssl-mode) +`allowFallbackToPlaintext=true` permits fallback to an unencrypted connection if the server is not configured to use TLS. This behavior is enabled automatically when using `tls=preferred`, but it is also available as a separate parameter to allow plaintext fallback when using a custom TLS configuration. + +Plaintext fallback only occurs if the server is not configured to use TLS at all. MySQL 5.7+ and MariaDB 11.4+ ship with TLS support pre-enabled by default using a self-signed server cert, so plaintext fallback typically won't occur with these server versions. + +Setting `allowFallbackToPlaintext=true` does **not** allow plaintext fallback in situations where the server has TLS enabled but is incompatible with the client configuration's cipher suites or TLS version. In recent Golang versions, establishing an encrypted connection to older server versions (e.g. MySQL 5.x or MariaDB 10.1) often requires a custom TLS configuration which allows RSA key exchange cipher suites. Very old server versions also require reducing the client's minimum TLS version. See documentation for [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig) for example code. ##### `allowNativePasswords` @@ -431,8 +435,9 @@ Valid Values: true, false, skip-verify, preferred, Default: false ``` -`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side) or use `preferred` to use TLS only when advertised by the server. This is similar to `skip-verify`, but additionally allows a fallback to a connection which is not encrypted. Neither `skip-verify` nor `preferred` add any reliable security. You can use a custom TLS config after registering it with [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig). +`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side) or use `preferred` to use TLS only when advertised by the server. Using `tls=preferred` is equivalent to setting `tls=skip-verify&allowFallbackToPlaintext=true`, which permits fallback to an unencrypted connection if the server is not configured to use TLS at all. Neither `skip-verify` nor `preferred` add any reliable security. You can use a custom TLS config after registering it with [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig). +In recent Golang versions, establishing an encrypted connection to older server versions (MySQL 5.x or MariaDB 10.1) often requires a custom TLS configuration, in order to allow RSA key exchange cipher suites and a lower minimum TLS version. See documentation for [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig) for example code. ##### `writeTimeout` diff --git a/utils.go b/utils.go index 44f43ef7..9aa3ff11 100644 --- a/utils.go +++ b/utils.go @@ -49,9 +49,33 @@ var ( // log.Fatal(err) // } // clientCert = append(clientCert, certs) +// cipherSuites := []uint16{ +// // These 10 are the normal Go 1.22+ defaults +// tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, +// tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, +// tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, +// tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, +// tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, +// tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, +// tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, +// tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, +// tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, +// tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, +// +// // These 4 use RSA key exchange, no longer included by default in Go 1.22+ +// // but often needed to connect to MySQL 5.7, MariaDB 10.1, or anything older +// tls.TLS_RSA_WITH_AES_128_GCM_SHA256, +// tls.TLS_RSA_WITH_AES_256_GCM_SHA384, +// tls.TLS_RSA_WITH_AES_128_CBC_SHA, +// tls.TLS_RSA_WITH_AES_256_CBC_SHA, +// } // mysql.RegisterTLSConfig("custom", &tls.Config{ // RootCAs: rootCertPool, // Certificates: clientCert, +// +// // Only include the following two lines when supporting older servers +// CipherSuites: cipherSuites, // in Go 1.22+, allow TLS connection to MySQL 5.x or MariaDB 10.1 or older +// MinVersion: tls.VersionTLS10, // in Go 1.18+, allow TLS connection to MySQL 5.6 or older // }) // db, err := sql.Open("mysql", "user@tcp(localhost:3306)/test?tls=custom") func RegisterTLSConfig(key string, config *tls.Config) error {