Using TLS/SSL with MariaDB Connector/J
Overview
This document explains how to configure the MariaDB Java driver to support TLS/SSL.
Data can be encrypted during transfer using the Transport Layer Security (TLS) protocol. TLS/SSL permits transfer encryption, and optionally server and client identity validation.
The term SSL (Secure Sockets Layer) is often used interchangeably with TLS, although strictly-speaking the SSL protocol is the predecessor of TLS, and is not implemented as it is now considered insecure.
Server configuration
To ensure that SSL is correctly configured on the server, the query "SELECT @@have_ssl;" must return YES. If not, please refer to the server documentation.
Protocol
MariaDB servers can be built with different cryptographic tools that support TLS differently.
Tools | Supported TLS protocol |
---|---|
openSSL | TLSv1, TLSv1.1 and TLSv1.2 |
YaSSL | TLSv1, TLSv1.1 |
OpenSSL is used by default on Unix, and YaSSL on windows. The MySQL community server uses YaSSL, MySQL enterprise openSSL.
During TLS/SSL negotiation, the client normally indicates the maximum level supported, but before 10.2, YaSSL doesn't recognize the TLSv1.2 flag and then doesn't fallback to theTLSv1.1 protocol.
If trying to connect with TLSv1.2 with a server before 10.2 using YaSSL, the connection will fail with the following exception :
- with MariaDB server : "Caused by: javax.net.ssl.SSLException: Unsupported record version Unknown-0.0"
- with MySQL server : "Caused by: javax.net.ssl.SSLHandshakeException: Remote host terminated the handshake"
The MariaDB Java driver by default uses only TLSv1, TLSv1.1 protocol. If the servers are MariaDB on Unix or version >= 10.2 , consider adding TLSv1.2 protocol. This can be set using the "enabledSslProtocolSuites" option (example: enabledSslProtocolSuites=TLSv1,TLSv1.1,TLSv1.2).
In addition to the protocol, the driver relies on the Java default cipher list. The Java default enabled ciphers are listed here. JAVA allows cipher suites to be removed/excluded from use in the security policy using the Java system property "jdk.tls.disabledAlgorithms". The specific list of ciphers to be used can be set using the "enabledSslCipherSuites" driver option (example : "enabledSSLCipherSuites=TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,...")
Oracle Java cryptographic limitation
Due to import regulations in some countries, the Java Oracle implementation provides a default cryptographic jurisdiction policy file that limits the strength of cryptographic algorithms.
Here are the maximum key sizes allowed by this "strong" version of the jurisdiction policy files:
Algorithm | Maximum Keysize |
---|---|
DES | 64 |
DESede | * |
RC2 | 128 |
RC4 | 128 |
RC5 | 128 |
RSA | * |
If stronger algorithms are needed (for example, AES with 256-bit keys), the JCE Unlimited Strength Jurisdiction Policy Files must be obtained and installed in the JDK/JRE to remove those limitation.
It is strongly recommended, if permissible under local regulations, to have JCE installed.
One way SSL authentication
By default, the driver can be configured to use SSL, even if the user used for authentication is not set to use SSL, but the recommendation is to use a user created with "REQUIRE SSL". See CREATE USER for more details.
Example;
CREATE USER 'myUser'@'%' IDENTIFIED BY 'MyPwd'; GRANT ALL ON db_name.* TO 'myUser'@'%' REQUIRE SSL;
The "useSSL" option must be enabled to indicate to the driver to connect using SSL.
Testing can be done with the trustServerCertificate=true option to validate encryption (NOT TO BE USED IN PRODUCTION!)
try (Connection con = DriverManager.getConnection("jdbc:mariadb://localhost/myDb?user=myUser&password=MyPwd" + "&useSSL=true&trustServerCertificate=true")) { try (Statement stmt = con.createStatement()) { stmt.execute("select 1"); } }
The connection will then be encrypted.
If the trustServerCertificate option is not set, an exception "unable to find valid certification path to requested target" will be thrown. If the server doesn't support SSL, an exception "Trying to connect with ssl, but ssl not enabled in the server" will be thrown.
The Java system property "-Djavax.net.debug=all" permits logging all TLS exchanges.
To validate the server identity, server root and intermediate certificates must be provided to driver. There are several ways to configure this:
- use the java default truststore in JKS format (or PKCS12 using java 9)
- use a dedicated truststore in JKS format (or PKCS12 using java 9)
- provide certificate
Using self signed certificates has some specificities.
Java default truststore
Java trustStore is a file that contains certificates of trusted SSL servers, or of Certificate Authorities trusted to identify servers. Truststore can be protected by a password.
The Java search order for locating the trust store is:
- system property "javax.net.ssl.trustStore"
- $JAVA_HOME/lib/security/jssecacerts
- $JAVA_HOME/lib/security/cacerts (shipped by default)
To add a certificate from a CA not included in the truststore, locate the default truststore on your system. The default truststore is located in the $JAVA_HOME/jre/lib/security/cacerts.
//copy the java truststore to jssecacerts cp $JAVA_HOME/jre/lib/security/cacerts $JAVA_HOME/jre/lib/security/jssecacerts //add your certificat to truststore keytool -importcert -file myCA-root.cer -alias myCA -keystore /usr/java/default/jre/lib/security/jssecacerts -storepass changeit
with our previous example, the "trustServerCertificate" option can then be removed
try (Connection con = DriverManager.getConnection("jdbc:mariadb://localhost/myDb?user=myUser&password=MyPwd&useSSL=true")) { try (Statement stmt = con.createStatement()) { stmt.execute("select 1"); } }
Use a dedicated truststore
If not using the alternative jssecacerts trustStore, there are 2 choices:
- use Java system properties "javax.net.ssl.trustStore" to define your specific trustStore. Be careful, as this trustore will completely replace the default cacerts/jssecacerts, and only the certificates imported will be trusted!
- indicate to the driver the location of the dedicated trustore using "trustStore" option and if needed "trustStorePassword".
The dedicated trustore can be generated using this command :
//Import the root certificate and create the truststore //You will be prompted to confirm that the root certificate is trustworthy. Be sure to verify that the certificate is genuine before you import it. keytool -importcert -keystore myTrustStore.jks -alias myCA-root -storepass myTrustPwd -file myCA-root.cer //Import the intermediate certificate into the truststore created in Step 1: keytool -importcert -keystore myTrustStore.jks -alias myCA-intermediate -storepass myTrustPwd -file myCA-intermediate.cer
with our previous example, trustServerCertificate can then be removed:
try (Connection con = DriverManager.getConnection("jdbc:mariadb://localhost/myDb?user=myUser&password=MyPwd&trustStore=/pathToTrustStore/myTrustStore.jks&trustStorePassword=mypwd")) { try (Statement stmt = con.createStatement()) { stmt.execute("select 1"); } }
Provide certificate directly
The "serverSslCert" option permits setting the certificate location. The location can be used in one of 3 forms:
- serverSslCert=/path/to/cert.pem (full path to certificate)
- serverSslCert=classpath:relative/cert.pem (relative to current classpath)
- or as verbatim DER-encoded certificate string "------BEGING CERTIFICATE-----..." .
Example :
try (Connection con = DriverManager.getConnection("jdbc:mariadb://localhost/myDb?user=myUser&password=MyPwd&serverSslCert=/path/to/cert.pem)) { try (Statement stmt = con.createStatement()) { stmt.execute("select 1"); } }
Handling self-signed certificates
Using self-signed certificates, you will not only have to register the root and intermediates certificates, but each individual certificate of each server! So if using more than one server, each self-signed certificate must be registered.
Import the test certificate into the truststore:
keytool -importcert -keystore my.truststore -alias server1 -storepass trustChangeMe -file server1.cer
Mutual (2-way) authentication
Mutual SSL authentication or certificate based mutual authentication refers to two parties authenticating each other through verifying the provided digital certificate so that both parties are assured of the others' identity.
To enable mutual authentication, the user must be created with "REQUIRE X509" so the server asks the driver for client certificates. See CREATE USER for more details.
Example:
CREATE USER 'myUser'@'%' IDENTIFIED BY 'MyPwd'; GRANT ALL ON db_name.* TO 'myUser'@'%' REQUIRE X509;
If the user is not set with REQUIRE X509, only one way authentication will be done
The client (driver) must then have its own certificate too (and related private key). If the driver doesn't provide a certificate, and the user used to connect is defined with "REQUIRE X509", the server will then return a basic "Access denied for user". Check how the user is defined with "select SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT FROM mysql.user u where u.User = '<myUser>'".
Java stores this client certificate and private key in a keyStore file. A keystore file is similar to trustore, in fact trustore and keystore are often the same file.
Example of generating a keystore in JKS format :
# generate a keystore with the client cert & key openssl pkcs12 \ -export \ -in "${clientCertFile}" \ -inkey "${clientKeyFile}" \ -out "${tmpKeystoreFile}" \ -name "mariadbAlias" \ -passout pass:kspass # convert PKSC12 to JKS keytool \ -importkeystore \ -deststorepass kspass \ -destkeypass kspass \ -destkeystore "${clientKeystoreFile}" \ -srckeystore ${tmpKeystoreFile} \ -srcstoretype PKCS12 \ -srcstorepass kspass \ -alias "mariadbAlias"
Like truststore, the Java default keystore can be used, then no additional option is needed, or a dedicated keystore by using the "keyStore" option to indicate location and the "keyStorePassword" option to indicate the keystore password. In JKS keystore, an additional password for a specific key may have been set. The "keyPassword" option permits setting this password.
Troubleshooting
Diffie-Hellman size error
If using Java 7, the Diffie-Hellman (DH) prime size is limited to 1024 (JDK-6521495). Java has some artificial restriction that restricts size: Java 7 max size is 1024, Java 8 is 2048, Java 9 will be 8192. Java 7 with extended support has a 2048 limit.
SSL not enabled on server
If the following error occurs: "java.sql.SQLException: Trying to connect with ssl, but ssl not enabled in the server", SSL is not enabled on the server-side. Since the "useSSL=true" option is set, the connection failed. Execute "show variables like '%ssl%';" on the server-side to identify the SSL issue.
Server certificate is not provided to client
When the driver tries to connect using SSL, but no certificate is provided, or the "trustServerCertificate=true" option is not set, the driver will fail with the following exception "Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target"
Solution: - not recommended: set the "trustServerCertificate=true" option. - add the server certificate to the driver (see documentation above).
java.sql.SQLInvalidAuthorizationSpecException: Could not connect: Access denied for user
This can occur for a number of reasons:
- The user / password is incorrect.
- Some SSL options have been set on the user (can be checked using "select SSL_TYPE, SSL_CIPHER, X509_ISSUER, X509_SUBJECT FROM mysql.user u where u.User = '<myUser>';) and the connection attempt doesn't meet those requirements.