Skip to content
This repository was archived by the owner on Dec 13, 2023. It is now read-only.

Commit ecdda28

Browse files
committed
Allow to configure HostnameVerifier
Restore ZERO_DATE option that is deleted due to rebase.
1 parent 6d94a3a commit ecdda28

7 files changed

Lines changed: 100 additions & 26 deletions

File tree

src/main/java/dev/miku/r2dbc/mysql/MySqlConnectionConfiguration.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.netty.handler.ssl.SslContextBuilder;
2323
import reactor.util.annotation.Nullable;
2424

25+
import javax.net.ssl.HostnameVerifier;
2526
import java.net.Socket;
2627
import java.time.Duration;
2728
import java.time.ZoneId;
@@ -264,6 +265,9 @@ public static final class Builder {
264265

265266
private String[] tlsVersion = EMPTY_STRINGS;
266267

268+
@Nullable
269+
private HostnameVerifier sslHostnameVerifier;
270+
267271
@Nullable
268272
private String sslCa;
269273

@@ -304,7 +308,7 @@ public MySqlConnectionConfiguration build() {
304308
require(!sslMode.startSsl(), "sslMode must be disabled when using unix domain socket");
305309
}
306310

307-
MySqlSslConfiguration ssl = MySqlSslConfiguration.create(sslMode, tlsVersion, sslCa, sslKey, sslKeyPassword, sslCert, sslContextBuilderCustomizer);
311+
MySqlSslConfiguration ssl = MySqlSslConfiguration.create(sslMode, tlsVersion, sslHostnameVerifier, sslCa, sslKey, sslKeyPassword, sslCert, sslContextBuilderCustomizer);
308312
return new MySqlConnectionConfiguration(isHost, domain, port, ssl, tcpKeepAlive, tcpNoDelay, connectTimeout, zeroDateOption, serverZoneId,
309313
user, password, database, preferPrepareStatement, Extensions.from(extensions, autodetectExtensions));
310314
}
@@ -465,6 +469,23 @@ public Builder tlsVersion(String... tlsVersion) {
465469
return this;
466470
}
467471

472+
/**
473+
* Configure ssl {@link HostnameVerifier}, it will be available only set {@link #sslMode} as
474+
* {@link SslMode#VERIFY_IDENTITY}. It is useful when server was using special Certificates
475+
* or need special verification.
476+
* <p>
477+
* Default is builtin {@link HostnameVerifier} which use RFC standards.
478+
*
479+
* @param sslHostnameVerifier the custom {@link HostnameVerifier}.
480+
* @return this {@link Builder}
481+
* @throws IllegalArgumentException if {@code sslHostnameVerifier} is {@code null}
482+
* @since 0.8.2
483+
*/
484+
public Builder sslHostnameVerifier(HostnameVerifier sslHostnameVerifier) {
485+
this.sslHostnameVerifier = requireNonNull(sslHostnameVerifier, "sslHostnameVerifier must not be null");
486+
return this;
487+
}
488+
468489
/**
469490
* Configure ssl root cert for server certificate validation. It is only used
470491
* if {@link #sslMode(SslMode)} is configured for verify server certification.

src/main/java/dev/miku/r2dbc/mysql/MySqlConnectionFactoryProvider.java

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io.r2dbc.spi.ConnectionFactoryProvider;
2525
import io.r2dbc.spi.Option;
2626

27+
import javax.net.ssl.HostnameVerifier;
2728
import java.time.ZoneId;
2829
import java.util.function.Function;
2930
import java.util.function.Predicate;
@@ -44,11 +45,18 @@ public final class MySqlConnectionFactoryProvider implements ConnectionFactoryPr
4445
public static final Option<Object> SERVER_ZONE_ID = Option.valueOf("serverZoneId");
4546

4647
/**
47-
* This option indicates special handling when MySQL server returning "zero date" (aka. "0000-00-00 00:00:00")
48+
* Option to configure handling when MySQL server returning "zero date" (aka. "0000-00-00 00:00:00")
4849
*/
49-
public static final Option<String> ZERO_DATE = Option.valueOf("zeroDate");
50+
public static final Option<Object> ZERO_DATE = Option.valueOf("zeroDate");
5051

51-
public static final Option<String> SSL_MODE = Option.valueOf("sslMode");
52+
public static final Option<Object> SSL_MODE = Option.valueOf("sslMode");
53+
54+
/**
55+
* Option to configure {@link HostnameVerifier}.
56+
*
57+
* @since 0.8.2
58+
*/
59+
public static final Option<Object> SSL_HOSTNAME_VERIFIER = Option.valueOf("sslHostnameVerifier");
5260

5361
public static final Option<String> TLS_VERSION = Option.valueOf("tlsVersion");
5462

@@ -107,16 +115,33 @@ public ConnectionFactory create(ConnectionFactoryOptions options) {
107115
builder.sslMode(isSsl ? SslMode.PREFERRED : SslMode.DISABLED);
108116
}
109117

110-
String sslMode = options.getValue(SSL_MODE);
118+
Object sslMode = options.getValue(SSL_MODE);
111119
if (sslMode != null) {
112-
builder.sslMode(SslMode.valueOf(sslMode.toUpperCase()));
120+
if (sslMode instanceof SslMode) {
121+
builder.sslMode((SslMode) sslMode);
122+
} else if (sslMode instanceof String) {
123+
builder.sslMode(SslMode.valueOf(((String) sslMode).toUpperCase()));
124+
} else {
125+
throw new IllegalArgumentException("sslMode must be SslMode or a string of SslMode");
126+
}
113127
}
114128

115129
String tlsVersion = options.getValue(TLS_VERSION);
116130
if (tlsVersion != null) {
117131
builder.tlsVersion(tlsVersion.split(","));
118132
}
119133

134+
Object sslHostnameVerifier = options.getValue(SSL_HOSTNAME_VERIFIER);
135+
if (sslHostnameVerifier != null) {
136+
if (sslHostnameVerifier instanceof HostnameVerifier) {
137+
builder.sslHostnameVerifier((HostnameVerifier) sslHostnameVerifier);
138+
} else if (sslHostnameVerifier instanceof String) {
139+
builder.sslHostnameVerifier(newInstance((String) sslHostnameVerifier, HostnameVerifier.class));
140+
} else {
141+
throw new IllegalArgumentException("sslHostnameVerifier must be HostnameVerifier");
142+
}
143+
}
144+
120145
String sslCert = options.getValue(SSL_CERT);
121146
String sslKey = options.getValue(SSL_KEY);
122147
CharSequence sslKeyPassword = options.getValue(SSL_KEY_PASSWORD);
@@ -162,8 +187,18 @@ public ConnectionFactory create(ConnectionFactoryOptions options) {
162187
.parseBoolean(tcpNoDelay.toString()) : (Boolean) tcpNoDelay);
163188
}
164189

165-
Object serverPreparing = options.getValue(USE_SERVER_PREPARE_STATEMENT);
190+
Object zeroDate = options.getValue(ZERO_DATE);
191+
if (zeroDate != null) {
192+
if (zeroDate instanceof ZeroDateOption) {
193+
builder.zeroDateOption((ZeroDateOption) zeroDate);
194+
} else if (zeroDate instanceof String) {
195+
builder.zeroDateOption(ZeroDateOption.valueOf(((String) zeroDate).toUpperCase()));
196+
} else {
197+
throw new IllegalArgumentException("zeroDate must be ZeroDateOption or a string of ZeroDateOption");
198+
}
199+
}
166200

201+
Object serverPreparing = options.getValue(USE_SERVER_PREPARE_STATEMENT);
167202
if (serverPreparing != null) {
168203
// Convert stringify option.
169204
if (serverPreparing instanceof String) {
@@ -216,13 +251,12 @@ public String getDriver() {
216251
return MYSQL_DRIVER;
217252
}
218253

219-
@SuppressWarnings("unchecked")
220254
private static <T> T newInstance(String className, Class<T> target) {
221255
try {
222256
Class<?> type = Class.forName(className);
223257

224258
if (target.isAssignableFrom(type)) {
225-
return (T) type.newInstance();
259+
return target.cast(type.getDeclaredConstructor().newInstance());
226260
}
227261
} catch (ReflectiveOperationException e) {
228262
throw new IllegalArgumentException("Cannot instantiate '" + className + "'", e);

src/main/java/dev/miku/r2dbc/mysql/MySqlSslConfiguration.java

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import io.netty.handler.ssl.SslContextBuilder;
2121
import reactor.util.annotation.Nullable;
2222

23+
import javax.net.ssl.HostnameVerifier;
2324
import java.util.Arrays;
2425
import java.util.Objects;
2526
import java.util.function.Function;
@@ -32,12 +33,15 @@
3233
*/
3334
public final class MySqlSslConfiguration {
3435

35-
private static final MySqlSslConfiguration DISABLED = new MySqlSslConfiguration(SslMode.DISABLED, EMPTY_STRINGS, null, null, null, null, null);
36+
private static final MySqlSslConfiguration DISABLED = new MySqlSslConfiguration(SslMode.DISABLED, EMPTY_STRINGS, null, null, null, null, null, null);
3637

3738
private final SslMode sslMode;
3839

3940
private final String[] tlsVersion;
4041

42+
@Nullable
43+
private final HostnameVerifier sslHostnameVerifier;
44+
4145
@Nullable
4246
private final String sslCa;
4347

@@ -54,12 +58,13 @@ public final class MySqlSslConfiguration {
5458
private final Function<SslContextBuilder, SslContextBuilder> sslContextBuilderCustomizer;
5559

5660
private MySqlSslConfiguration(
57-
SslMode sslMode, String[] tlsVersion, @Nullable String sslCa,
61+
SslMode sslMode, String[] tlsVersion, @Nullable HostnameVerifier sslHostnameVerifier, @Nullable String sslCa,
5862
@Nullable String sslKey, @Nullable CharSequence sslKeyPassword, @Nullable String sslCert,
5963
@Nullable Function<SslContextBuilder, SslContextBuilder> sslContextBuilderCustomizer
6064
) {
6165
this.sslMode = sslMode;
6266
this.tlsVersion = tlsVersion;
67+
this.sslHostnameVerifier = sslHostnameVerifier;
6368
this.sslCa = sslCa;
6469
this.sslKey = sslKey;
6570
this.sslKeyPassword = sslKeyPassword;
@@ -75,6 +80,11 @@ public String[] getTlsVersion() {
7580
return tlsVersion;
7681
}
7782

83+
@Nullable
84+
public HostnameVerifier getSslHostnameVerifier() {
85+
return sslHostnameVerifier;
86+
}
87+
7888
@Nullable
7989
public String getSslCa() {
8090
return sslCa;
@@ -114,6 +124,7 @@ public boolean equals(Object o) {
114124
MySqlSslConfiguration that = (MySqlSslConfiguration) o;
115125
return sslMode == that.sslMode &&
116126
Arrays.equals(tlsVersion, that.tlsVersion) &&
127+
Objects.equals(sslHostnameVerifier, that.sslHostnameVerifier) &&
117128
Objects.equals(sslCa, that.sslCa) &&
118129
Objects.equals(sslKey, that.sslKey) &&
119130
Objects.equals(sslKeyPassword, that.sslKeyPassword) &&
@@ -123,38 +134,37 @@ public boolean equals(Object o) {
123134

124135
@Override
125136
public int hashCode() {
126-
int result = Objects.hash(sslMode, sslCa, sslKey, sslKeyPassword, sslCert, sslContextBuilderCustomizer);
127-
result = 31 * result + Arrays.hashCode(tlsVersion);
128-
return result;
137+
int result = Objects.hash(sslMode, sslHostnameVerifier, sslCa, sslKey, sslKeyPassword, sslCert, sslContextBuilderCustomizer);
138+
return 31 * result + Arrays.hashCode(tlsVersion);
129139
}
130140

131141
@Override
132142
public String toString() {
133-
if (sslMode.startSsl()) {
134-
return String.format("MySqlSslConfiguration{sslMode=%s, tlsVersion=%s, sslCa='%s', sslKey='%s', sslKeyPassword=REDACTED, sslCert='%s', sslContextBuilderCustomizer=%s}",
135-
sslMode, Arrays.toString(tlsVersion), sslCa, sslKey, sslCert, sslContextBuilderCustomizer);
143+
if (sslMode == SslMode.DISABLED) {
144+
return "DISABLED";
136145
}
137146

138-
return "DISABLED";
147+
return String.format("MySqlSslConfiguration{sslMode=%s, tlsVersion=%s, sslHostnameVerifier=%s, sslCa='%s', sslKey='%s', sslKeyPassword=REDACTED, sslCert='%s', sslContextBuilderCustomizer=%s}",
148+
sslMode, Arrays.toString(tlsVersion), sslHostnameVerifier, sslCa, sslKey, sslCert, sslContextBuilderCustomizer);
139149
}
140150

141151
static MySqlSslConfiguration disabled() {
142152
return DISABLED;
143153
}
144154

145155
static MySqlSslConfiguration create(
146-
SslMode sslMode, String[] tlsVersion, @Nullable String sslCa,
156+
SslMode sslMode, String[] tlsVersion, @Nullable HostnameVerifier sslHostnameVerifier, @Nullable String sslCa,
147157
@Nullable String sslKey, @Nullable CharSequence sslKeyPassword, @Nullable String sslCert,
148158
@Nullable Function<SslContextBuilder, SslContextBuilder> sslContextBuilderCustomizer
149159
) {
150160
requireNonNull(sslMode, "sslMode must not be null");
151161

152-
if (!sslMode.startSsl()) {
162+
if (sslMode == SslMode.DISABLED) {
153163
return DISABLED;
154164
}
155165

156166
requireNonNull(tlsVersion, "tlsVersion must not be null");
157167

158-
return new MySqlSslConfiguration(sslMode, tlsVersion, sslCa, sslKey, sslKeyPassword, sslCert, sslContextBuilderCustomizer);
168+
return new MySqlSslConfiguration(sslMode, tlsVersion, sslHostnameVerifier, sslCa, sslKey, sslKeyPassword, sslCert, sslContextBuilderCustomizer);
159169
}
160170
}

src/main/java/dev/miku/r2dbc/mysql/client/SslBridgeHandler.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,9 @@ private void handleSslCompleted(ChannelHandlerContext ctx, SslHandshakeCompletio
108108
return;
109109
}
110110

111-
HostnameVerifier verifier = DefaultHostnameVerifier.INSTANCE;
112111
String host = ((InetSocketAddress) ctx.channel().remoteAddress()).getHostName();
113112

114-
if (!verifier.verify(host, sslEngine.getSession())) {
113+
if (!hostnameVerifier().verify(host, sslEngine.getSession())) {
115114
// Verify failed, emit an exception.
116115
ctx.fireExceptionCaught(new SSLException("The hostname '" + host + "' could not be verified"));
117116
return;
@@ -150,6 +149,11 @@ private void handleSslState(ChannelHandlerContext ctx, SslState state) {
150149
// Ignore another custom SSL states because they are useless.
151150
}
152151

152+
private HostnameVerifier hostnameVerifier() {
153+
HostnameVerifier verifier = ssl.getSslHostnameVerifier();
154+
return verifier == null ? DefaultHostnameVerifier.INSTANCE : verifier;
155+
}
156+
153157
private static SslProvider buildProvider(MySqlSslConfiguration ssl, ServerVersion version) {
154158
return SslProvider.builder()
155159
.sslContext(buildContext(ssl, version))

src/main/java/dev/miku/r2dbc/mysql/util/AddressUtils.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,6 @@ public final class AddressUtils {
3131

3232
private static final int IPV6_COLONS = 7;
3333

34-
private AddressUtils() {
35-
}
36-
3734
public static boolean isIpv4(String host) {
3835
// TODO: Use faster matches instead of regex.
3936
return IPV4_PATTERN.matcher(host).matches();
@@ -57,4 +54,7 @@ private static boolean isIpv6Compressed(String host) {
5754
// TODO: Use faster matches instead of regex.
5855
return colons <= IPV6_COLONS && IPV6_COMPRESSED_PATTERN.matcher(host).matches();
5956
}
57+
58+
private AddressUtils() {
59+
}
6060
}

src/test/java/dev/miku/r2dbc/mysql/MySqlConnectionConfigurationTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ private static MySqlConnectionConfiguration filledUp() {
195195
.tlsVersion(TlsVersions.TLS1_1, TlsVersions.TLS1_2, TlsVersions.TLS1_3)
196196
.serverZoneId(ZoneId.systemDefault())
197197
.zeroDateOption(ZeroDateOption.USE_NULL)
198+
.sslHostnameVerifier((host, s) -> true)
198199
.autodetectExtensions(false)
199200
.build();
200201
}

src/test/java/dev/miku/r2dbc/mysql/util/AddressUtilsTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@ class AddressUtilsTest {
2828

2929
@Test
3030
void isIpv4() {
31+
assertTrue(AddressUtils.isIpv4("1.0.0.0"));
3132
assertTrue(AddressUtils.isIpv4("127.0.0.1"));
3233
assertTrue(AddressUtils.isIpv4("10.11.12.13"));
3334
assertTrue(AddressUtils.isIpv4("192.168.0.0"));
3435
assertTrue(AddressUtils.isIpv4("255.255.255.255"));
3536

37+
assertFalse(AddressUtils.isIpv4("0.0.0.0"));
3638
assertFalse(AddressUtils.isIpv4(" 127.0.0.1 "));
3739
assertFalse(AddressUtils.isIpv4("01.11.12.13"));
3840
assertFalse(AddressUtils.isIpv4("092.168.0.1"));
@@ -79,6 +81,8 @@ void isIpv6() {
7981
assertFalse(AddressUtils.isIpv6("1:2::4:5:6:7:8:9"));
8082
assertFalse(AddressUtils.isIpv6("1:2:3:4:5:6::8:9"));
8183

84+
assertFalse(AddressUtils.isIpv6("0.0.0.0"));
85+
assertFalse(AddressUtils.isIpv6("1.0.0.0"));
8286
assertFalse(AddressUtils.isIpv6("127.0.0.1"));
8387
assertFalse(AddressUtils.isIpv6("10.11.12.13"));
8488
assertFalse(AddressUtils.isIpv6("192.168.0.0"));

0 commit comments

Comments
 (0)