Skip to content

Commit

Permalink
Added TLS support (backported from master)
Browse files Browse the repository at this point in the history
  • Loading branch information
shyiko committed Aug 15, 2016
1 parent d7f8638 commit 6e6b4e6
Show file tree
Hide file tree
Showing 10 changed files with 388 additions and 8 deletions.
5 changes: 5 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [0.4.0](https://github.com/shyiko/mysql-binlog-connector-java/compare/0.3.3...0.4.0) - 2016-08-15

### Added
- TLS support ([#70](https://github.com/shyiko/mysql-binlog-connector-java/issues/70)).

## [0.3.3](https://github.com/shyiko/mysql-binlog-connector-java/compare/0.3.2...0.3.3) - 2016-08-08

### Added
Expand Down
85 changes: 81 additions & 4 deletions src/main/java/com/github/shyiko/mysql/binlog/BinaryLogClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,13 @@
import com.github.shyiko.mysql.binlog.io.ByteArrayInputStream;
import com.github.shyiko.mysql.binlog.jmx.BinaryLogClientMXBean;
import com.github.shyiko.mysql.binlog.network.AuthenticationException;
import com.github.shyiko.mysql.binlog.network.ClientCapabilities;
import com.github.shyiko.mysql.binlog.network.DefaultSSLSocketFactory;
import com.github.shyiko.mysql.binlog.network.SSLMode;
import com.github.shyiko.mysql.binlog.network.SSLSocketFactory;
import com.github.shyiko.mysql.binlog.network.ServerException;
import com.github.shyiko.mysql.binlog.network.SocketFactory;
import com.github.shyiko.mysql.binlog.network.TLSHostnameVerifier;
import com.github.shyiko.mysql.binlog.network.protocol.ErrorPacket;
import com.github.shyiko.mysql.binlog.network.protocol.GreetingPacket;
import com.github.shyiko.mysql.binlog.network.protocol.Packet;
Expand All @@ -44,12 +49,19 @@
import com.github.shyiko.mysql.binlog.network.protocol.command.DumpBinaryLogGtidCommand;
import com.github.shyiko.mysql.binlog.network.protocol.command.PingCommand;
import com.github.shyiko.mysql.binlog.network.protocol.command.QueryCommand;
import com.github.shyiko.mysql.binlog.network.protocol.command.SSLRequestCommand;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.EOFException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
Expand All @@ -74,6 +86,31 @@
*/
public class BinaryLogClient implements BinaryLogClientMXBean {

private static final SSLSocketFactory DEFAULT_REQUIRED_SSL_MODE_SOCKET_FACTORY = new DefaultSSLSocketFactory() {

@Override
protected void initSSLContext(SSLContext sc) throws GeneralSecurityException {
sc.init(null, new TrustManager[]{
new X509TrustManager() {

@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s)
throws CertificateException { }

@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s)
throws CertificateException { }

@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
}, null);
}
};
private static final SSLSocketFactory DEFAULT_VERIFY_CA_SSL_MODE_SOCKET_FACTORY = new DefaultSSLSocketFactory();

// https://dev.mysql.com/doc/internals/en/sending-more-than-16mbyte.html
private static final int MAX_PACKET_LENGTH = 16777215;

Expand All @@ -90,6 +127,7 @@ public class BinaryLogClient implements BinaryLogClientMXBean {
private volatile String binlogFilename;
private volatile long binlogPosition = 4;
private volatile long connectionId;
private SSLMode sslMode = SSLMode.DISABLED;

private GtidSet gtidSet;
private final Object gtidSetAccessLock = new Object();
Expand All @@ -100,6 +138,7 @@ public class BinaryLogClient implements BinaryLogClientMXBean {
private final List<LifecycleListener> lifecycleListeners = new LinkedList<LifecycleListener>();

private SocketFactory socketFactory;
private SSLSocketFactory sslSocketFactory;

private PacketChannel channel;
private volatile boolean connected;
Expand Down Expand Up @@ -166,6 +205,17 @@ public void setBlocking(boolean blocking) {
this.blocking = blocking;
}

public SSLMode getSSLMode() {
return sslMode;
}

public void setSSLMode(SSLMode sslMode) {
if (sslMode == null) {
throw new IllegalArgumentException("SSL mode cannot be NULL");
}
this.sslMode = sslMode;
}

/**
* @return server id (65535 by default)
* @see #setServerId(long)
Expand Down Expand Up @@ -326,6 +376,13 @@ public void setSocketFactory(SocketFactory socketFactory) {
this.socketFactory = socketFactory;
}

/**
* @param sslSocketFactory custom ssl socket factory
*/
public void setSslSocketFactory(SSLSocketFactory sslSocketFactory) {
this.sslSocketFactory = sslSocketFactory;
}

/**
* @param threadFactory custom thread factory. If not provided, threads will be created using simple "new Thread()".
*/
Expand Down Expand Up @@ -357,7 +414,7 @@ public void connect() throws IOException {
". Please make sure it's running.", e);
}
greetingPacket = receiveGreeting();
authenticate(greetingPacket.getScramble(), greetingPacket.getServerCollation());
authenticate(greetingPacket);
if (binlogFilename == null) {
fetchBinlogFilenameAndPosition();
}
Expand Down Expand Up @@ -446,10 +503,30 @@ private void ensureEventDataDeserializer(EventType eventType,
}
}

private void authenticate(String salt, int collation) throws IOException {
AuthenticateCommand authenticateCommand = new AuthenticateCommand(schema, username, password, salt);
private void authenticate(GreetingPacket greetingPacket) throws IOException {
int collation = greetingPacket.getServerCollation();
int packetNumber = 1;
if (sslMode != SSLMode.DISABLED) {
boolean serverSupportsSSL = (greetingPacket.getServerCapabilities() & ClientCapabilities.SSL) != 0;
if (!serverSupportsSSL && (sslMode == SSLMode.REQUIRED || sslMode == SSLMode.VERIFY_CA ||
sslMode == SSLMode.VERIFY_IDENTITY)) {
throw new IOException("MySQL server does not support SSL");
}
if (serverSupportsSSL) {
SSLRequestCommand sslRequestCommand = new SSLRequestCommand();
sslRequestCommand.setCollation(collation);
channel.write(sslRequestCommand, packetNumber++);
SSLSocketFactory sslSocketFactory = this.sslSocketFactory != null ? this.sslSocketFactory :
sslMode == SSLMode.REQUIRED ? DEFAULT_REQUIRED_SSL_MODE_SOCKET_FACTORY :
DEFAULT_VERIFY_CA_SSL_MODE_SOCKET_FACTORY;
channel.upgradeToSSL(sslSocketFactory,
sslMode == SSLMode.VERIFY_IDENTITY ? new TLSHostnameVerifier() : null);
}
}
AuthenticateCommand authenticateCommand = new AuthenticateCommand(schema, username, password,
greetingPacket.getScramble());
authenticateCommand.setCollation(collation);
channel.write(authenticateCommand);
channel.write(authenticateCommand, packetNumber);
byte[] authenticationResult = channel.read();
if (authenticationResult[0] != (byte) 0x00 /* ok */) {
if (authenticationResult[0] == (byte) 0xFF /* error */) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2016 Stanley Shyiko
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.shyiko.mysql.binlog.network;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.security.GeneralSecurityException;

/**
* @author <a href="mailto:[email protected]">Stanley Shyiko</a>
*/
public class DefaultSSLSocketFactory implements SSLSocketFactory {

private final String protocol;

public DefaultSSLSocketFactory() {
this("TLSv1");
}

/**
* @param protocol TLSv1, TLSv1.1 or TLSv1.2 (the last two require JDK 7+)
*/
public DefaultSSLSocketFactory(String protocol) {
this.protocol = protocol;
}

@Override
public SSLSocket createSocket(Socket socket) throws SocketException {
SSLContext sc;
try {
sc = SSLContext.getInstance(this.protocol);
initSSLContext(sc);
} catch (GeneralSecurityException e) {
throw new SocketException(e.getMessage());
}
try {
return (SSLSocket) sc.getSocketFactory()
.createSocket(socket, socket.getInetAddress().getHostName(), socket.getPort(), true);
} catch (IOException e) {
throw new SocketException(e.getMessage());
}
}

protected void initSSLContext(SSLContext sc) throws GeneralSecurityException {
sc.init(null, null, null);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2016 Stanley Shyiko
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.shyiko.mysql.binlog.network;

import javax.net.ssl.SSLException;

/**
* @author <a href="mailto:[email protected]">Stanley Shyiko</a>
*/
public class IdentityVerificationException extends SSLException {

public IdentityVerificationException(String message) {
super(message);
}

}
50 changes: 50 additions & 0 deletions src/main/java/com/github/shyiko/mysql/binlog/network/SSLMode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2016 Stanley Shyiko
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.shyiko.mysql.binlog.network;

/**
* @see <a href="https://dev.mysql.com/doc/refman/5.7/en/secure-connection-options.html#option_general_ssl-mode>
* ssl-mode</a> for the original documentation.
* @author <a href="mailto:[email protected]">Stanley Shyiko</a>
*/
public enum SSLMode {

/**
* Establish a secure (encrypted) connection if the server supports secure connections.
* Fall back to an unencrypted connection otherwise.
*/
PREFERRED,
/**
* Establish an unencrypted connection.
*/
DISABLED,
/**
* Establish a secure connection if the server supports secure connections.
* The connection attempt fails if a secure connection cannot be established.
*/
REQUIRED,
/**
* Like REQUIRED, but additionally verify the server TLS certificate against the configured Certificate Authority
* (CA) certificates. The connection attempt fails if no valid matching CA certificates are found.
*/
VERIFY_CA,
/**
* Like VERIFY_CA, but additionally verify that the server certificate matches the host to which the connection is
* attempted.
*/
VERIFY_IDENTITY

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2016 Stanley Shyiko
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.shyiko.mysql.binlog.network;

import javax.net.ssl.SSLSocket;
import java.net.Socket;
import java.net.SocketException;

/**
* @author <a href="mailto:[email protected]">Stanley Shyiko</a>
*/
public interface SSLSocketFactory {

SSLSocket createSocket(Socket socket) throws SocketException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2016 Stanley Shyiko
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.shyiko.mysql.binlog.network;

import sun.security.util.HostnameChecker;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

/**
* @author <a href="mailto:[email protected]">Stanley Shyiko</a>
*/
public class TLSHostnameVerifier implements HostnameVerifier {

public boolean verify(String hostname, SSLSession session) {
HostnameChecker checker = HostnameChecker.getInstance(HostnameChecker.TYPE_TLS);
try {
Certificate[] peerCertificates = session.getPeerCertificates();
if (peerCertificates.length > 0 && peerCertificates[0] instanceof X509Certificate) {
X509Certificate peerCertificate = (X509Certificate) peerCertificates[0];
try {
checker.match(hostname, peerCertificate);
return true;
} catch (CertificateException ignored) {
}
}
} catch (SSLPeerUnverifiedException ignored) {
}
return false;
}

}
Loading

0 comments on commit 6e6b4e6

Please sign in to comment.