Skip to content

Commit 6e6b4e6

Browse files
committed
Added TLS support (backported from master)
1 parent d7f8638 commit 6e6b4e6

File tree

10 files changed

+388
-8
lines changed

10 files changed

+388
-8
lines changed

changelog.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
All notable changes to this project will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/).
44

5+
## [0.4.0](https://github.com/shyiko/mysql-binlog-connector-java/compare/0.3.3...0.4.0) - 2016-08-15
6+
7+
### Added
8+
- TLS support ([#70](https://github.com/shyiko/mysql-binlog-connector-java/issues/70)).
9+
510
## [0.3.3](https://github.com/shyiko/mysql-binlog-connector-java/compare/0.3.2...0.3.3) - 2016-08-08
611

712
### Added

src/main/java/com/github/shyiko/mysql/binlog/BinaryLogClient.java

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,13 @@
3131
import com.github.shyiko.mysql.binlog.io.ByteArrayInputStream;
3232
import com.github.shyiko.mysql.binlog.jmx.BinaryLogClientMXBean;
3333
import com.github.shyiko.mysql.binlog.network.AuthenticationException;
34+
import com.github.shyiko.mysql.binlog.network.ClientCapabilities;
35+
import com.github.shyiko.mysql.binlog.network.DefaultSSLSocketFactory;
36+
import com.github.shyiko.mysql.binlog.network.SSLMode;
37+
import com.github.shyiko.mysql.binlog.network.SSLSocketFactory;
3438
import com.github.shyiko.mysql.binlog.network.ServerException;
3539
import com.github.shyiko.mysql.binlog.network.SocketFactory;
40+
import com.github.shyiko.mysql.binlog.network.TLSHostnameVerifier;
3641
import com.github.shyiko.mysql.binlog.network.protocol.ErrorPacket;
3742
import com.github.shyiko.mysql.binlog.network.protocol.GreetingPacket;
3843
import com.github.shyiko.mysql.binlog.network.protocol.Packet;
@@ -44,12 +49,19 @@
4449
import com.github.shyiko.mysql.binlog.network.protocol.command.DumpBinaryLogGtidCommand;
4550
import com.github.shyiko.mysql.binlog.network.protocol.command.PingCommand;
4651
import com.github.shyiko.mysql.binlog.network.protocol.command.QueryCommand;
52+
import com.github.shyiko.mysql.binlog.network.protocol.command.SSLRequestCommand;
4753

54+
import javax.net.ssl.SSLContext;
55+
import javax.net.ssl.TrustManager;
56+
import javax.net.ssl.X509TrustManager;
4857
import java.io.EOFException;
4958
import java.io.IOException;
5059
import java.net.InetSocketAddress;
5160
import java.net.Socket;
5261
import java.net.SocketException;
62+
import java.security.GeneralSecurityException;
63+
import java.security.cert.CertificateException;
64+
import java.security.cert.X509Certificate;
5365
import java.util.Arrays;
5466
import java.util.Collections;
5567
import java.util.Iterator;
@@ -74,6 +86,31 @@
7486
*/
7587
public class BinaryLogClient implements BinaryLogClientMXBean {
7688

89+
private static final SSLSocketFactory DEFAULT_REQUIRED_SSL_MODE_SOCKET_FACTORY = new DefaultSSLSocketFactory() {
90+
91+
@Override
92+
protected void initSSLContext(SSLContext sc) throws GeneralSecurityException {
93+
sc.init(null, new TrustManager[]{
94+
new X509TrustManager() {
95+
96+
@Override
97+
public void checkClientTrusted(X509Certificate[] x509Certificates, String s)
98+
throws CertificateException { }
99+
100+
@Override
101+
public void checkServerTrusted(X509Certificate[] x509Certificates, String s)
102+
throws CertificateException { }
103+
104+
@Override
105+
public X509Certificate[] getAcceptedIssuers() {
106+
return new X509Certificate[0];
107+
}
108+
}
109+
}, null);
110+
}
111+
};
112+
private static final SSLSocketFactory DEFAULT_VERIFY_CA_SSL_MODE_SOCKET_FACTORY = new DefaultSSLSocketFactory();
113+
77114
// https://dev.mysql.com/doc/internals/en/sending-more-than-16mbyte.html
78115
private static final int MAX_PACKET_LENGTH = 16777215;
79116

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

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

102140
private SocketFactory socketFactory;
141+
private SSLSocketFactory sslSocketFactory;
103142

104143
private PacketChannel channel;
105144
private volatile boolean connected;
@@ -166,6 +205,17 @@ public void setBlocking(boolean blocking) {
166205
this.blocking = blocking;
167206
}
168207

208+
public SSLMode getSSLMode() {
209+
return sslMode;
210+
}
211+
212+
public void setSSLMode(SSLMode sslMode) {
213+
if (sslMode == null) {
214+
throw new IllegalArgumentException("SSL mode cannot be NULL");
215+
}
216+
this.sslMode = sslMode;
217+
}
218+
169219
/**
170220
* @return server id (65535 by default)
171221
* @see #setServerId(long)
@@ -326,6 +376,13 @@ public void setSocketFactory(SocketFactory socketFactory) {
326376
this.socketFactory = socketFactory;
327377
}
328378

379+
/**
380+
* @param sslSocketFactory custom ssl socket factory
381+
*/
382+
public void setSslSocketFactory(SSLSocketFactory sslSocketFactory) {
383+
this.sslSocketFactory = sslSocketFactory;
384+
}
385+
329386
/**
330387
* @param threadFactory custom thread factory. If not provided, threads will be created using simple "new Thread()".
331388
*/
@@ -357,7 +414,7 @@ public void connect() throws IOException {
357414
". Please make sure it's running.", e);
358415
}
359416
greetingPacket = receiveGreeting();
360-
authenticate(greetingPacket.getScramble(), greetingPacket.getServerCollation());
417+
authenticate(greetingPacket);
361418
if (binlogFilename == null) {
362419
fetchBinlogFilenameAndPosition();
363420
}
@@ -446,10 +503,30 @@ private void ensureEventDataDeserializer(EventType eventType,
446503
}
447504
}
448505

449-
private void authenticate(String salt, int collation) throws IOException {
450-
AuthenticateCommand authenticateCommand = new AuthenticateCommand(schema, username, password, salt);
506+
private void authenticate(GreetingPacket greetingPacket) throws IOException {
507+
int collation = greetingPacket.getServerCollation();
508+
int packetNumber = 1;
509+
if (sslMode != SSLMode.DISABLED) {
510+
boolean serverSupportsSSL = (greetingPacket.getServerCapabilities() & ClientCapabilities.SSL) != 0;
511+
if (!serverSupportsSSL && (sslMode == SSLMode.REQUIRED || sslMode == SSLMode.VERIFY_CA ||
512+
sslMode == SSLMode.VERIFY_IDENTITY)) {
513+
throw new IOException("MySQL server does not support SSL");
514+
}
515+
if (serverSupportsSSL) {
516+
SSLRequestCommand sslRequestCommand = new SSLRequestCommand();
517+
sslRequestCommand.setCollation(collation);
518+
channel.write(sslRequestCommand, packetNumber++);
519+
SSLSocketFactory sslSocketFactory = this.sslSocketFactory != null ? this.sslSocketFactory :
520+
sslMode == SSLMode.REQUIRED ? DEFAULT_REQUIRED_SSL_MODE_SOCKET_FACTORY :
521+
DEFAULT_VERIFY_CA_SSL_MODE_SOCKET_FACTORY;
522+
channel.upgradeToSSL(sslSocketFactory,
523+
sslMode == SSLMode.VERIFY_IDENTITY ? new TLSHostnameVerifier() : null);
524+
}
525+
}
526+
AuthenticateCommand authenticateCommand = new AuthenticateCommand(schema, username, password,
527+
greetingPacket.getScramble());
451528
authenticateCommand.setCollation(collation);
452-
channel.write(authenticateCommand);
529+
channel.write(authenticateCommand, packetNumber);
453530
byte[] authenticationResult = channel.read();
454531
if (authenticationResult[0] != (byte) 0x00 /* ok */) {
455532
if (authenticationResult[0] == (byte) 0xFF /* error */) {
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2016 Stanley Shyiko
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.github.shyiko.mysql.binlog.network;
17+
18+
import javax.net.ssl.SSLContext;
19+
import javax.net.ssl.SSLSocket;
20+
import java.io.IOException;
21+
import java.net.Socket;
22+
import java.net.SocketException;
23+
import java.security.GeneralSecurityException;
24+
25+
/**
26+
* @author <a href="mailto:[email protected]">Stanley Shyiko</a>
27+
*/
28+
public class DefaultSSLSocketFactory implements SSLSocketFactory {
29+
30+
private final String protocol;
31+
32+
public DefaultSSLSocketFactory() {
33+
this("TLSv1");
34+
}
35+
36+
/**
37+
* @param protocol TLSv1, TLSv1.1 or TLSv1.2 (the last two require JDK 7+)
38+
*/
39+
public DefaultSSLSocketFactory(String protocol) {
40+
this.protocol = protocol;
41+
}
42+
43+
@Override
44+
public SSLSocket createSocket(Socket socket) throws SocketException {
45+
SSLContext sc;
46+
try {
47+
sc = SSLContext.getInstance(this.protocol);
48+
initSSLContext(sc);
49+
} catch (GeneralSecurityException e) {
50+
throw new SocketException(e.getMessage());
51+
}
52+
try {
53+
return (SSLSocket) sc.getSocketFactory()
54+
.createSocket(socket, socket.getInetAddress().getHostName(), socket.getPort(), true);
55+
} catch (IOException e) {
56+
throw new SocketException(e.getMessage());
57+
}
58+
}
59+
60+
protected void initSSLContext(SSLContext sc) throws GeneralSecurityException {
61+
sc.init(null, null, null);
62+
}
63+
64+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2016 Stanley Shyiko
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.github.shyiko.mysql.binlog.network;
17+
18+
import javax.net.ssl.SSLException;
19+
20+
/**
21+
* @author <a href="mailto:[email protected]">Stanley Shyiko</a>
22+
*/
23+
public class IdentityVerificationException extends SSLException {
24+
25+
public IdentityVerificationException(String message) {
26+
super(message);
27+
}
28+
29+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2016 Stanley Shyiko
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.github.shyiko.mysql.binlog.network;
17+
18+
/**
19+
* @see <a href="https://dev.mysql.com/doc/refman/5.7/en/secure-connection-options.html#option_general_ssl-mode>
20+
* ssl-mode</a> for the original documentation.
21+
* @author <a href="mailto:[email protected]">Stanley Shyiko</a>
22+
*/
23+
public enum SSLMode {
24+
25+
/**
26+
* Establish a secure (encrypted) connection if the server supports secure connections.
27+
* Fall back to an unencrypted connection otherwise.
28+
*/
29+
PREFERRED,
30+
/**
31+
* Establish an unencrypted connection.
32+
*/
33+
DISABLED,
34+
/**
35+
* Establish a secure connection if the server supports secure connections.
36+
* The connection attempt fails if a secure connection cannot be established.
37+
*/
38+
REQUIRED,
39+
/**
40+
* Like REQUIRED, but additionally verify the server TLS certificate against the configured Certificate Authority
41+
* (CA) certificates. The connection attempt fails if no valid matching CA certificates are found.
42+
*/
43+
VERIFY_CA,
44+
/**
45+
* Like VERIFY_CA, but additionally verify that the server certificate matches the host to which the connection is
46+
* attempted.
47+
*/
48+
VERIFY_IDENTITY
49+
50+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2016 Stanley Shyiko
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.github.shyiko.mysql.binlog.network;
17+
18+
import javax.net.ssl.SSLSocket;
19+
import java.net.Socket;
20+
import java.net.SocketException;
21+
22+
/**
23+
* @author <a href="mailto:[email protected]">Stanley Shyiko</a>
24+
*/
25+
public interface SSLSocketFactory {
26+
27+
SSLSocket createSocket(Socket socket) throws SocketException;
28+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2016 Stanley Shyiko
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.github.shyiko.mysql.binlog.network;
17+
18+
import sun.security.util.HostnameChecker;
19+
20+
import javax.net.ssl.HostnameVerifier;
21+
import javax.net.ssl.SSLPeerUnverifiedException;
22+
import javax.net.ssl.SSLSession;
23+
import java.security.cert.Certificate;
24+
import java.security.cert.CertificateException;
25+
import java.security.cert.X509Certificate;
26+
27+
/**
28+
* @author <a href="mailto:[email protected]">Stanley Shyiko</a>
29+
*/
30+
public class TLSHostnameVerifier implements HostnameVerifier {
31+
32+
public boolean verify(String hostname, SSLSession session) {
33+
HostnameChecker checker = HostnameChecker.getInstance(HostnameChecker.TYPE_TLS);
34+
try {
35+
Certificate[] peerCertificates = session.getPeerCertificates();
36+
if (peerCertificates.length > 0 && peerCertificates[0] instanceof X509Certificate) {
37+
X509Certificate peerCertificate = (X509Certificate) peerCertificates[0];
38+
try {
39+
checker.match(hostname, peerCertificate);
40+
return true;
41+
} catch (CertificateException ignored) {
42+
}
43+
}
44+
} catch (SSLPeerUnverifiedException ignored) {
45+
}
46+
return false;
47+
}
48+
49+
}

0 commit comments

Comments
 (0)