com.mysql.jdbc.ExportControlled Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mysql-connector-java
Show all versions of mysql-connector-java
MySQL JDBC Type 4 driver
/*
Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
The MySQL Connector/J is licensed under the terms of the GPLv2
, like most MySQL Connectors.
There are special exceptions to the terms and conditions of the GPLv2 as it is applied to
this software, see the FOSS License Exception
.
This program is free software; you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation; version 2
of the License.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this
program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth
Floor, Boston, MA 02110-1301 USA
*/
package com.mysql.jdbc;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.SocketException;
import java.net.URL;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyFactory;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertPathValidatorResult;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.PKIXCertPathValidatorResult;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import javax.crypto.Cipher;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import com.mysql.jdbc.util.Base64Decoder;
/**
* Holds functionality that falls under export-control regulations.
*/
public class ExportControlled {
private static final String SQL_STATE_BAD_SSL_PARAMS = "08000";
private static final String TLSv1 = "TLSv1";
private static final String TLSv1_1 = "TLSv1.1";
private static final String TLSv1_2 = "TLSv1.2";
private static final String[] TLS_PROTOCOLS = new String[] { TLSv1_2, TLSv1_1, TLSv1 };
protected static boolean enabled() {
// we may wish to un-static-ify this class this static method call may be removed entirely by the compiler
return true;
}
/**
* Converts the socket being used in the given MysqlIO to an SSLSocket by
* performing the SSL/TLS handshake.
*
* @param mysqlIO
* the MysqlIO instance containing the socket to convert to an
* SSLSocket.
*
* @throws CommunicationsException
* if the handshake fails, or if this distribution of
* Connector/J doesn't contain the SSL crypto hooks needed to
* perform the handshake.
*/
protected static void transformSocketToSSLSocket(MysqlIO mysqlIO) throws SQLException {
SocketFactory sslFact = new StandardSSLSocketFactory(getSSLSocketFactoryDefaultOrConfigured(mysqlIO), mysqlIO.socketFactory, mysqlIO.mysqlConnection);
try {
mysqlIO.mysqlConnection = sslFact.connect(mysqlIO.host, mysqlIO.port, null);
String[] tryProtocols = null;
// If enabledTLSProtocols configuration option is set then override the default TLS version restrictions. This allows enabling TLSv1.2 for
// self-compiled MySQL versions supporting it, as well as the ability for users to restrict TLS connections to approved protocols (e.g., prohibiting
// TLSv1) on the client side.
// Note that it is problematic to enable TLSv1.2 on the client side when the server is compiled with yaSSL. When client attempts to connect with
// TLSv1.2 yaSSL just closes the socket instead of re-attempting handshake with lower TLS version.
String enabledTLSProtocols = mysqlIO.connection.getEnabledTLSProtocols();
if (enabledTLSProtocols != null && enabledTLSProtocols.length() > 0) {
tryProtocols = enabledTLSProtocols.split("\\s*,\\s*");
} else if (mysqlIO.versionMeetsMinimum(8, 0, 4) || mysqlIO.versionMeetsMinimum(5, 6, 0) && Util.isEnterpriseEdition(mysqlIO.getServerVersion())) {
// allow all known TLS versions for this subset of server versions by default
tryProtocols = TLS_PROTOCOLS;
} else {
// allow TLSv1 and TLSv1.1 for all server versions by default
tryProtocols = new String[] { TLSv1_1, TLSv1 };
}
List configuredProtocols = new ArrayList(Arrays.asList(tryProtocols));
List jvmSupportedProtocols = Arrays.asList(((SSLSocket) mysqlIO.mysqlConnection).getSupportedProtocols());
List allowedProtocols = new ArrayList();
for (String protocol : TLS_PROTOCOLS) {
if (jvmSupportedProtocols.contains(protocol) && configuredProtocols.contains(protocol)) {
allowedProtocols.add(protocol);
}
}
((SSLSocket) mysqlIO.mysqlConnection).setEnabledProtocols(allowedProtocols.toArray(new String[0]));
// check allowed cipher suites
String enabledSSLCipherSuites = mysqlIO.connection.getEnabledSSLCipherSuites();
boolean overrideCiphers = enabledSSLCipherSuites != null && enabledSSLCipherSuites.length() > 0;
List allowedCiphers = null;
if (overrideCiphers) {
// If "enabledSSLCipherSuites" is set we just check that JVM allows provided values,
// we don't disable DH algorithm, that allows c/J to deal with custom server builds with different security restrictions
allowedCiphers = new ArrayList();
List availableCiphers = Arrays.asList(((SSLSocket) mysqlIO.mysqlConnection).getEnabledCipherSuites());
for (String cipher : enabledSSLCipherSuites.split("\\s*,\\s*")) {
if (availableCiphers.contains(cipher)) {
allowedCiphers.add(cipher);
}
}
} else {
// If we don't override ciphers, then we check for known restrictions
boolean disableDHAlgorithm = false;
if (mysqlIO.versionMeetsMinimum(5, 5, 45) && !mysqlIO.versionMeetsMinimum(5, 6, 0)
|| mysqlIO.versionMeetsMinimum(5, 6, 26) && !mysqlIO.versionMeetsMinimum(5, 7, 0) || mysqlIO.versionMeetsMinimum(5, 7, 6)) {
// Workaround for JVM bug http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6521495
// Starting from 5.5.45, 5.6.26 and 5.7.6 server the key length used for creating Diffie-Hellman keys has been
// increased from 512 to 2048 bits, while JVMs affected by this bug allow only range from 512 to 1024 (inclusive).
// Bug is fixed in Java 8.
if (Util.getJVMVersion() < 8) {
disableDHAlgorithm = true;
}
} else if (Util.getJVMVersion() >= 8) { // TODO check later for Java 9 behavior
// Java 8 default java.security contains jdk.tls.disabledAlgorithms=DH keySize < 768
// That causes handshake failures with older MySQL servers, eg 5.6.11. Thus we have to disable DH for them when running on Java 8+
disableDHAlgorithm = true;
}
if (disableDHAlgorithm) {
allowedCiphers = new ArrayList();
for (String cipher : ((SSLSocket) mysqlIO.mysqlConnection).getEnabledCipherSuites()) {
if (!(disableDHAlgorithm && (cipher.indexOf("_DHE_") > -1 || cipher.indexOf("_DH_") > -1))) {
allowedCiphers.add(cipher);
}
}
}
}
// if some ciphers were filtered into allowedCiphers
if (allowedCiphers != null) {
((SSLSocket) mysqlIO.mysqlConnection).setEnabledCipherSuites(allowedCiphers.toArray(new String[0]));
}
((SSLSocket) mysqlIO.mysqlConnection).startHandshake();
if (mysqlIO.connection.getUseUnbufferedInput()) {
mysqlIO.mysqlInput = mysqlIO.mysqlConnection.getInputStream();
} else {
mysqlIO.mysqlInput = new BufferedInputStream(mysqlIO.mysqlConnection.getInputStream(), 16384);
}
mysqlIO.mysqlOutput = new BufferedOutputStream(mysqlIO.mysqlConnection.getOutputStream(), 16384);
mysqlIO.mysqlOutput.flush();
mysqlIO.socketFactory = sslFact;
} catch (IOException ioEx) {
throw SQLError.createCommunicationsException(mysqlIO.connection, mysqlIO.getLastPacketSentTimeMs(), mysqlIO.getLastPacketReceivedTimeMs(), ioEx,
mysqlIO.getExceptionInterceptor());
}
}
/**
* Implementation of internal socket factory to wrap the SSL socket.
*/
public static class StandardSSLSocketFactory implements SocketFactory, SocketMetadata {
private SSLSocket rawSocket = null;
private final SSLSocketFactory sslFact;
private final SocketFactory existingSocketFactory;
private final Socket existingSocket;
public StandardSSLSocketFactory(SSLSocketFactory sslFact, SocketFactory existingSocketFactory, Socket existingSocket) {
this.sslFact = sslFact;
this.existingSocketFactory = existingSocketFactory;
this.existingSocket = existingSocket;
}
public Socket afterHandshake() throws SocketException, IOException {
this.existingSocketFactory.afterHandshake();
return this.rawSocket;
}
public Socket beforeHandshake() throws SocketException, IOException {
return this.rawSocket;
}
public Socket connect(String host, int portNumber, Properties props) throws SocketException, IOException {
this.rawSocket = (SSLSocket) this.sslFact.createSocket(this.existingSocket, host, portNumber, true);
return this.rawSocket;
}
public boolean isLocallyConnected(ConnectionImpl conn) throws SQLException {
return SocketMetadata.Helper.isLocallyConnected(conn);
}
}
private ExportControlled() { /* prevent instantiation */
}
/**
* Implementation of X509TrustManager wrapping JVM X509TrustManagers to add expiration check
*/
public static class X509TrustManagerWrapper implements X509TrustManager {
private X509TrustManager origTm = null;
private boolean verifyServerCert = false;
private CertificateFactory certFactory = null;
private PKIXParameters validatorParams = null;
private CertPathValidator validator = null;
public X509TrustManagerWrapper(X509TrustManager tm, boolean verifyServerCertificate) throws CertificateException {
this.origTm = tm;
this.verifyServerCert = verifyServerCertificate;
if (verifyServerCertificate) {
try {
Set anch = new HashSet();
for (X509Certificate cert : tm.getAcceptedIssuers()) {
anch.add(new TrustAnchor(cert, null));
}
this.validatorParams = new PKIXParameters(anch);
this.validatorParams.setRevocationEnabled(false);
this.validator = CertPathValidator.getInstance("PKIX");
this.certFactory = CertificateFactory.getInstance("X.509");
} catch (Exception e) {
throw new CertificateException(e);
}
}
}
public X509TrustManagerWrapper() {
}
public X509Certificate[] getAcceptedIssuers() {
return this.origTm != null ? this.origTm.getAcceptedIssuers() : new X509Certificate[0];
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
for (int i = 0; i < chain.length; i++) {
chain[i].checkValidity();
}
if (this.validatorParams != null) {
X509CertSelector certSelect = new X509CertSelector();
certSelect.setSerialNumber(chain[0].getSerialNumber());
try {
CertPath certPath = this.certFactory.generateCertPath(Arrays.asList(chain));
// Validate against truststore
CertPathValidatorResult result = this.validator.validate(certPath, this.validatorParams);
// Check expiration for the CA used to validate this path
((PKIXCertPathValidatorResult) result).getTrustAnchor().getTrustedCert().checkValidity();
} catch (InvalidAlgorithmParameterException e) {
throw new CertificateException(e);
} catch (CertPathValidatorException e) {
throw new CertificateException(e);
}
}
if (this.verifyServerCert) {
this.origTm.checkServerTrusted(chain, authType);
}
}
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
this.origTm.checkClientTrusted(chain, authType);
}
};
private static SSLSocketFactory getSSLSocketFactoryDefaultOrConfigured(MysqlIO mysqlIO) throws SQLException {
String clientCertificateKeyStoreUrl = mysqlIO.connection.getClientCertificateKeyStoreUrl();
String clientCertificateKeyStorePassword = mysqlIO.connection.getClientCertificateKeyStorePassword();
String clientCertificateKeyStoreType = mysqlIO.connection.getClientCertificateKeyStoreType();
String trustCertificateKeyStoreUrl = mysqlIO.connection.getTrustCertificateKeyStoreUrl();
String trustCertificateKeyStorePassword = mysqlIO.connection.getTrustCertificateKeyStorePassword();
String trustCertificateKeyStoreType = mysqlIO.connection.getTrustCertificateKeyStoreType();
if (StringUtils.isNullOrEmpty(clientCertificateKeyStoreUrl)) {
clientCertificateKeyStoreUrl = System.getProperty("javax.net.ssl.keyStore");
clientCertificateKeyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword");
clientCertificateKeyStoreType = System.getProperty("javax.net.ssl.keyStoreType");
if (StringUtils.isNullOrEmpty(clientCertificateKeyStoreType)) {
clientCertificateKeyStoreType = "JKS";
}
// check URL
if (!StringUtils.isNullOrEmpty(clientCertificateKeyStoreUrl)) {
try {
new URL(clientCertificateKeyStoreUrl);
} catch (MalformedURLException e) {
clientCertificateKeyStoreUrl = "file:" + clientCertificateKeyStoreUrl;
}
}
}
if (StringUtils.isNullOrEmpty(trustCertificateKeyStoreUrl)) {
trustCertificateKeyStoreUrl = System.getProperty("javax.net.ssl.trustStore");
trustCertificateKeyStorePassword = System.getProperty("javax.net.ssl.trustStorePassword");
trustCertificateKeyStoreType = System.getProperty("javax.net.ssl.trustStoreType");
if (StringUtils.isNullOrEmpty(trustCertificateKeyStoreType)) {
trustCertificateKeyStoreType = "JKS";
}
// check URL
if (!StringUtils.isNullOrEmpty(trustCertificateKeyStoreUrl)) {
try {
new URL(trustCertificateKeyStoreUrl);
} catch (MalformedURLException e) {
trustCertificateKeyStoreUrl = "file:" + trustCertificateKeyStoreUrl;
}
}
}
TrustManagerFactory tmf = null;
KeyManagerFactory kmf = null;
KeyManager[] kms = null;
List tms = new ArrayList();
try {
tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
} catch (NoSuchAlgorithmException nsae) {
throw SQLError.createSQLException(
"Default algorithm definitions for TrustManager and/or KeyManager are invalid. Check java security properties file.",
SQL_STATE_BAD_SSL_PARAMS, 0, false, mysqlIO.getExceptionInterceptor());
}
if (!StringUtils.isNullOrEmpty(clientCertificateKeyStoreUrl)) {
InputStream ksIS = null;
try {
if (!StringUtils.isNullOrEmpty(clientCertificateKeyStoreType)) {
KeyStore clientKeyStore = KeyStore.getInstance(clientCertificateKeyStoreType);
URL ksURL = new URL(clientCertificateKeyStoreUrl);
char[] password = (clientCertificateKeyStorePassword == null) ? new char[0] : clientCertificateKeyStorePassword.toCharArray();
ksIS = ksURL.openStream();
clientKeyStore.load(ksIS, password);
kmf.init(clientKeyStore, password);
kms = kmf.getKeyManagers();
}
} catch (UnrecoverableKeyException uke) {
throw SQLError.createSQLException("Could not recover keys from client keystore. Check password?", SQL_STATE_BAD_SSL_PARAMS, 0, false,
mysqlIO.getExceptionInterceptor());
} catch (NoSuchAlgorithmException nsae) {
throw SQLError.createSQLException("Unsupported keystore algorithm [" + nsae.getMessage() + "]", SQL_STATE_BAD_SSL_PARAMS, 0, false,
mysqlIO.getExceptionInterceptor());
} catch (KeyStoreException kse) {
throw SQLError.createSQLException("Could not create KeyStore instance [" + kse.getMessage() + "]", SQL_STATE_BAD_SSL_PARAMS, 0, false,
mysqlIO.getExceptionInterceptor());
} catch (CertificateException nsae) {
throw SQLError.createSQLException("Could not load client" + clientCertificateKeyStoreType + " keystore from " + clientCertificateKeyStoreUrl,
mysqlIO.getExceptionInterceptor());
} catch (MalformedURLException mue) {
throw SQLError.createSQLException(clientCertificateKeyStoreUrl + " does not appear to be a valid URL.", SQL_STATE_BAD_SSL_PARAMS, 0, false,
mysqlIO.getExceptionInterceptor());
} catch (IOException ioe) {
SQLException sqlEx = SQLError.createSQLException("Cannot open " + clientCertificateKeyStoreUrl + " [" + ioe.getMessage() + "]",
SQL_STATE_BAD_SSL_PARAMS, 0, false, mysqlIO.getExceptionInterceptor());
sqlEx.initCause(ioe);
throw sqlEx;
} finally {
if (ksIS != null) {
try {
ksIS.close();
} catch (IOException e) {
// can't close input stream, but keystore can be properly initialized so we shouldn't throw this exception
}
}
}
}
InputStream trustStoreIS = null;
try {
KeyStore trustKeyStore = null;
if (!StringUtils.isNullOrEmpty(trustCertificateKeyStoreUrl) && !StringUtils.isNullOrEmpty(trustCertificateKeyStoreType)) {
trustStoreIS = new URL(trustCertificateKeyStoreUrl).openStream();
char[] trustStorePassword = (trustCertificateKeyStorePassword == null) ? new char[0] : trustCertificateKeyStorePassword.toCharArray();
trustKeyStore = KeyStore.getInstance(trustCertificateKeyStoreType);
trustKeyStore.load(trustStoreIS, trustStorePassword);
}
tmf.init(trustKeyStore); // (trustKeyStore == null) initializes the TrustManagerFactory with the default truststore.
// building the customized list of TrustManagers from original one if it's available
TrustManager[] origTms = tmf.getTrustManagers();
final boolean verifyServerCert = mysqlIO.connection.getVerifyServerCertificate();
for (TrustManager tm : origTms) {
// wrap X509TrustManager or put original if non-X509 TrustManager
tms.add(tm instanceof X509TrustManager ? new X509TrustManagerWrapper((X509TrustManager) tm, verifyServerCert) : tm);
}
} catch (MalformedURLException e) {
throw SQLError.createSQLException(trustCertificateKeyStoreUrl + " does not appear to be a valid URL.", SQL_STATE_BAD_SSL_PARAMS, 0, false,
mysqlIO.getExceptionInterceptor());
} catch (KeyStoreException e) {
throw SQLError.createSQLException("Could not create KeyStore instance [" + e.getMessage() + "]", SQL_STATE_BAD_SSL_PARAMS, 0, false,
mysqlIO.getExceptionInterceptor());
} catch (NoSuchAlgorithmException e) {
throw SQLError.createSQLException("Unsupported keystore algorithm [" + e.getMessage() + "]", SQL_STATE_BAD_SSL_PARAMS, 0, false,
mysqlIO.getExceptionInterceptor());
} catch (CertificateException e) {
throw SQLError.createSQLException("Could not load trust" + trustCertificateKeyStoreType + " keystore from " + trustCertificateKeyStoreUrl,
SQL_STATE_BAD_SSL_PARAMS, 0, false, mysqlIO.getExceptionInterceptor());
} catch (IOException e) {
SQLException sqlEx = SQLError.createSQLException("Cannot open " + trustCertificateKeyStoreType + " [" + e.getMessage() + "]",
SQL_STATE_BAD_SSL_PARAMS, 0, false, mysqlIO.getExceptionInterceptor());
sqlEx.initCause(e);
throw sqlEx;
} finally {
if (trustStoreIS != null) {
try {
trustStoreIS.close();
} catch (IOException e) {
// can't close input stream, but keystore can be properly initialized so we shouldn't throw this exception
}
}
}
// if original TrustManagers are not available then putting one X509TrustManagerWrapper which take care only about expiration check
if (tms.size() == 0) {
tms.add(new X509TrustManagerWrapper());
}
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kms, tms.toArray(new TrustManager[tms.size()]), null);
return sslContext.getSocketFactory();
} catch (NoSuchAlgorithmException nsae) {
throw SQLError.createSQLException("TLS is not a valid SSL protocol.", SQL_STATE_BAD_SSL_PARAMS, 0, false, mysqlIO.getExceptionInterceptor());
} catch (KeyManagementException kme) {
throw SQLError.createSQLException("KeyManagementException: " + kme.getMessage(), SQL_STATE_BAD_SSL_PARAMS, 0, false,
mysqlIO.getExceptionInterceptor());
}
}
public static boolean isSSLEstablished(MysqlIO mysqlIO) {
return SSLSocket.class.isAssignableFrom(mysqlIO.mysqlConnection.getClass());
}
public static RSAPublicKey decodeRSAPublicKey(String key, ExceptionInterceptor interceptor) throws SQLException {
try {
if (key == null) {
throw new SQLException("key parameter is null");
}
int offset = key.indexOf("\n") + 1;
int len = key.indexOf("-----END PUBLIC KEY-----") - offset;
// TODO: use standard decoders with Java 6+
byte[] certificateData = Base64Decoder.decode(key.getBytes(), offset, len);
X509EncodedKeySpec spec = new X509EncodedKeySpec(certificateData);
KeyFactory kf = KeyFactory.getInstance("RSA");
return (RSAPublicKey) kf.generatePublic(spec);
} catch (Exception ex) {
throw SQLError.createSQLException("Unable to decode public key", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, ex, interceptor);
}
}
public static byte[] encryptWithRSAPublicKey(byte[] source, RSAPublicKey key, String transformation, ExceptionInterceptor interceptor) throws SQLException {
try {
Cipher cipher = Cipher.getInstance(transformation);
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(source);
} catch (Exception ex) {
throw SQLError.createSQLException(ex.getMessage(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, ex, interceptor);
}
}
}