Skip to content

Commit 87d451c

Browse files
authored
Add SslOptions (#3980)
Similar to SslOptions in Lettuce library. --- * Add SslOptions from Lettuce library * Add InsecureTrustManagerFactory from Netty library * Separate SSL socket creation in DefaultJedisSocketFactory * Add SslOptions in JedisClientConfig * Add SslHostnameVerifyMode inspired by SslVerifyMode in Lettuce * Allow system default key and trust managers and insecure trust manager * fix jdoc * Address review comments * Revert back to verification mode names in Lettuce, to avoid confusion at this moment. * Rename insecureTrust to noTruststoreVerification; does this reduce confusion? * SslVerifyMode is renamed back and INSECURE option (renamed from NONE) is added back in SslVerifyMode. * Remove InsecureTrustManagerFactory * Disable ALL existing SSL tests * JedisTest with SslOptions * SSLACLJedisTest with SslOptions * SSLOptionsJedisSentinelPoolTest with SslOptions * SSLJedisClusterTest with SslOptions * TODO comment to enable existing SSL tests * TODO command to enable existing SSL tests in csc package * Enable existing SSL tests without impacting new ones * Missing enable existing SSL tests without impacting new ones * Keep only Builder pattern constructor for DefaultJedisClientConfig * Limit HostnameVerifier only for legacy ssl config and document as JavaDoc in JedisClientConfig * Remove unused codes from SSLOptionsJedisTest * Increase code reuse for LocalhostVerifier * Individual JavaDoc for each SslVerifyMode * Custom SocketFactory won't be supported with SslOptions * Deprecate DefaultJedisClientConfig.copyConfig() * Add option to set SSLContext protocol * Remove options to set KeyManager and TrustManager algorithms * Add File checkers * minor user/password change * minor update javadoc * Allow manual HostnameVerifier with SslOptions * Make test connectWithCustomHostNameVerifier() pass * Better SslOptions with custom HostnameVerifier in connectWithCustomHostNameVerifier() test * Shorten sslContextProtocol to sslProtocol * Use null as default password, unlike Lettuce where it uses empty char array. * Make an accidental private truststore builder option public * Remove Lettuce comments * Add JedisPooled tests * Use char array for password * Remove file license * Address code review * Merge fix * Deprecate helper methods in DefaultJedisClientConfig
1 parent 7697b6a commit 87d451c

16 files changed

+1164
-169
lines changed

src/main/java/redis/clients/jedis/DefaultJedisClientConfig.java

+88-43
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public final class DefaultJedisClientConfig implements JedisClientConfig {
2222
private final boolean ssl;
2323
private final SSLSocketFactory sslSocketFactory;
2424
private final SSLParameters sslParameters;
25+
private final SslOptions sslOptions;
2526
private final HostnameVerifier hostnameVerifier;
2627

2728
private final HostAndPortMapper hostAndPortMapper;
@@ -32,29 +33,23 @@ public final class DefaultJedisClientConfig implements JedisClientConfig {
3233

3334
private final AuthXManager authXManager;
3435

35-
private DefaultJedisClientConfig(RedisProtocol protocol, int connectionTimeoutMillis,
36-
int soTimeoutMillis, int blockingSocketTimeoutMillis,
37-
Supplier<RedisCredentials> credentialsProvider, int database, String clientName, boolean ssl,
38-
SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
39-
HostnameVerifier hostnameVerifier, HostAndPortMapper hostAndPortMapper,
40-
ClientSetInfoConfig clientSetInfoConfig, boolean readOnlyForRedisClusterReplicas,
41-
AuthXManager authXManager) {
42-
this.redisProtocol = protocol;
43-
this.connectionTimeoutMillis = connectionTimeoutMillis;
44-
this.socketTimeoutMillis = soTimeoutMillis;
45-
this.blockingSocketTimeoutMillis = blockingSocketTimeoutMillis;
46-
this.credentialsProvider = credentialsProvider;
47-
this.database = database;
48-
this.clientName = clientName;
49-
this.ssl = ssl;
50-
this.sslSocketFactory = sslSocketFactory;
51-
this.sslParameters = sslParameters;
52-
this.hostnameVerifier = hostnameVerifier;
53-
this.hostAndPortMapper = hostAndPortMapper;
54-
this.clientSetInfoConfig = clientSetInfoConfig;
55-
this.readOnlyForRedisClusterReplicas = readOnlyForRedisClusterReplicas;
56-
this.authXManager = authXManager;
57-
36+
private DefaultJedisClientConfig(DefaultJedisClientConfig.Builder builder) {
37+
this.redisProtocol = builder.redisProtocol;
38+
this.connectionTimeoutMillis = builder.connectionTimeoutMillis;
39+
this.socketTimeoutMillis = builder.socketTimeoutMillis;
40+
this.blockingSocketTimeoutMillis = builder.blockingSocketTimeoutMillis;
41+
this.credentialsProvider = builder.credentialsProvider;
42+
this.database = builder.database;
43+
this.clientName = builder.clientName;
44+
this.ssl = builder.ssl;
45+
this.sslSocketFactory = builder.sslSocketFactory;
46+
this.sslParameters = builder.sslParameters;
47+
this.sslOptions = builder.sslOptions;
48+
this.hostnameVerifier = builder.hostnameVerifier;
49+
this.hostAndPortMapper = builder.hostAndPortMapper;
50+
this.clientSetInfoConfig = builder.clientSetInfoConfig;
51+
this.readOnlyForRedisClusterReplicas = builder.readOnlyForRedisClusterReplicas;
52+
this.authXManager = builder.authXManager;
5853
}
5954

6055
@Override
@@ -123,6 +118,11 @@ public SSLParameters getSslParameters() {
123118
return sslParameters;
124119
}
125120

121+
@Override
122+
public SslOptions getSslOptions() {
123+
return sslOptions;
124+
}
125+
126126
@Override
127127
public HostnameVerifier getHostnameVerifier() {
128128
return hostnameVerifier;
@@ -164,6 +164,7 @@ public static class Builder {
164164
private boolean ssl = false;
165165
private SSLSocketFactory sslSocketFactory = null;
166166
private SSLParameters sslParameters = null;
167+
private SslOptions sslOptions = null;
167168
private HostnameVerifier hostnameVerifier = null;
168169

169170
private HostAndPortMapper hostAndPortMapper = null;
@@ -172,7 +173,7 @@ public static class Builder {
172173

173174
private boolean readOnlyForRedisClusterReplicas = false;
174175

175-
private AuthXManager authXManager;
176+
private AuthXManager authXManager = null;
176177

177178
private Builder() {
178179
}
@@ -183,15 +184,13 @@ public DefaultJedisClientConfig build() {
183184
new DefaultRedisCredentials(user, password));
184185
}
185186

186-
return new DefaultJedisClientConfig(redisProtocol, connectionTimeoutMillis,
187-
socketTimeoutMillis, blockingSocketTimeoutMillis, credentialsProvider, database,
188-
clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier, hostAndPortMapper,
189-
clientSetInfoConfig, readOnlyForRedisClusterReplicas, authXManager);
187+
return new DefaultJedisClientConfig(this);
190188
}
191189

192190
/**
193191
* Shortcut to {@link redis.clients.jedis.DefaultJedisClientConfig.Builder#protocol(RedisProtocol)} with
194192
* {@link RedisProtocol#RESP3}.
193+
* @return this
195194
*/
196195
public Builder resp3() {
197196
return protocol(RedisProtocol.RESP3);
@@ -268,6 +267,11 @@ public Builder sslParameters(SSLParameters sslParameters) {
268267
return this;
269268
}
270269

270+
public Builder sslOptions(SslOptions sslOptions) {
271+
this.sslOptions = sslOptions;
272+
return this;
273+
}
274+
271275
public Builder hostnameVerifier(HostnameVerifier hostnameVerifier) {
272276
this.hostnameVerifier = hostnameVerifier;
273277
return this;
@@ -304,6 +308,7 @@ public Builder from(JedisClientConfig instance) {
304308
this.ssl = instance.isSsl();
305309
this.sslSocketFactory = instance.getSslSocketFactory();
306310
this.sslParameters = instance.getSslParameters();
311+
this.sslOptions = instance.getSslOptions();
307312
this.hostnameVerifier = instance.getHostnameVerifier();
308313
this.hostAndPortMapper = instance.getHostAndPortMapper();
309314
this.clientSetInfoConfig = instance.getClientSetInfoConfig();
@@ -313,24 +318,64 @@ public Builder from(JedisClientConfig instance) {
313318
}
314319
}
315320

321+
/**
322+
* @deprecated Use {@link redis.clients.jedis.DefaultJedisClientConfig.Builder}.
323+
*/
324+
@Deprecated
316325
public static DefaultJedisClientConfig create(int connectionTimeoutMillis, int soTimeoutMillis,
317-
int blockingSocketTimeoutMillis, String user, String password, int database,
318-
String clientName, boolean ssl, SSLSocketFactory sslSocketFactory,
319-
SSLParameters sslParameters, HostnameVerifier hostnameVerifier,
320-
HostAndPortMapper hostAndPortMapper, AuthXManager authXManager) {
321-
return new DefaultJedisClientConfig(null, connectionTimeoutMillis, soTimeoutMillis,
322-
blockingSocketTimeoutMillis,
323-
new DefaultRedisCredentialsProvider(new DefaultRedisCredentials(user, password)), database,
324-
clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier, hostAndPortMapper, null,
325-
false, authXManager);
326+
int blockingSocketTimeoutMillis, String user, String password, int database, String clientName,
327+
boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters,
328+
HostnameVerifier hostnameVerifier, HostAndPortMapper hostAndPortMapper) {
329+
Builder builder = builder();
330+
builder.connectionTimeoutMillis(connectionTimeoutMillis).socketTimeoutMillis(soTimeoutMillis)
331+
.blockingSocketTimeoutMillis(blockingSocketTimeoutMillis);
332+
if (user != null || password != null) {
333+
// deliberately not handling 'user != null && password == null' here
334+
builder.credentials(new DefaultRedisCredentials(user, password));
335+
}
336+
builder.database(database).clientName(clientName);
337+
builder.ssl(ssl).sslSocketFactory(sslSocketFactory).sslParameters(sslParameters).hostnameVerifier(hostnameVerifier);
338+
builder.hostAndPortMapper(hostAndPortMapper);
339+
return builder.build();
326340
}
327341

342+
/**
343+
* @deprecated Use
344+
* {@link redis.clients.jedis.DefaultJedisClientConfig.Builder#from(redis.clients.jedis.JedisClientConfig)}.
345+
*/
346+
@Deprecated
328347
public static DefaultJedisClientConfig copyConfig(JedisClientConfig copy) {
329-
return new DefaultJedisClientConfig(copy.getRedisProtocol(), copy.getConnectionTimeoutMillis(),
330-
copy.getSocketTimeoutMillis(), copy.getBlockingSocketTimeoutMillis(),
331-
copy.getCredentialsProvider(), copy.getDatabase(), copy.getClientName(), copy.isSsl(),
332-
copy.getSslSocketFactory(), copy.getSslParameters(), copy.getHostnameVerifier(),
333-
copy.getHostAndPortMapper(), copy.getClientSetInfoConfig(),
334-
copy.isReadOnlyForRedisClusterReplicas(), copy.getAuthXManager());
348+
Builder builder = builder();
349+
builder.protocol(copy.getRedisProtocol());
350+
builder.connectionTimeoutMillis(copy.getConnectionTimeoutMillis());
351+
builder.socketTimeoutMillis(copy.getSocketTimeoutMillis());
352+
builder.blockingSocketTimeoutMillis(copy.getBlockingSocketTimeoutMillis());
353+
354+
Supplier<RedisCredentials> credentialsProvider = copy.getCredentialsProvider();
355+
if (credentialsProvider != null) {
356+
builder.credentialsProvider(credentialsProvider);
357+
} else {
358+
builder.user(copy.getUser());
359+
builder.password(copy.getPassword());
360+
}
361+
362+
builder.database(copy.getDatabase());
363+
builder.clientName(copy.getClientName());
364+
365+
builder.ssl(copy.isSsl());
366+
builder.sslSocketFactory(copy.getSslSocketFactory());
367+
builder.sslParameters(copy.getSslParameters());
368+
builder.hostnameVerifier(copy.getHostnameVerifier());
369+
builder.sslOptions(copy.getSslOptions());
370+
builder.hostAndPortMapper(copy.getHostAndPortMapper());
371+
372+
builder.clientSetInfoConfig(copy.getClientSetInfoConfig());
373+
if (copy.isReadOnlyForRedisClusterReplicas()) {
374+
builder.readOnlyForRedisClusterReplicas();
375+
}
376+
377+
builder.authXManager(copy.getAuthXManager());
378+
379+
return builder.build();
335380
}
336381
}

src/main/java/redis/clients/jedis/DefaultJedisSocketFactory.java

+51-19
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package redis.clients.jedis;
22

3+
import java.io.IOException;
34
import java.net.InetAddress;
45
import java.net.InetSocketAddress;
56
import java.net.Socket;
7+
import java.security.GeneralSecurityException;
68
import java.util.Arrays;
79
import java.util.Collections;
810
import java.util.List;
911
import javax.net.ssl.HostnameVerifier;
12+
import javax.net.ssl.SSLContext;
1013
import javax.net.ssl.SSLParameters;
1114
import javax.net.ssl.SSLSocket;
1215
import javax.net.ssl.SSLSocketFactory;
@@ -24,6 +27,7 @@ public class DefaultJedisSocketFactory implements JedisSocketFactory {
2427
private int socketTimeout = Protocol.DEFAULT_TIMEOUT;
2528
private boolean ssl = false;
2629
private SSLSocketFactory sslSocketFactory = null;
30+
private SslOptions sslOptions = null;
2731
private SSLParameters sslParameters = null;
2832
private HostnameVerifier hostnameVerifier = null;
2933
private HostAndPortMapper hostAndPortMapper = null;
@@ -49,6 +53,7 @@ public DefaultJedisSocketFactory(HostAndPort hostAndPort, JedisClientConfig conf
4953
this.ssl = config.isSsl();
5054
this.sslSocketFactory = config.getSslSocketFactory();
5155
this.sslParameters = config.getSslParameters();
56+
this.sslOptions = config.getSslOptions();
5257
this.hostnameVerifier = config.getHostnameVerifier();
5358
this.hostAndPortMapper = config.getHostAndPortMapper();
5459
}
@@ -89,25 +94,8 @@ public Socket createSocket() throws JedisConnectionException {
8994
socket = connectToFirstSuccessfulHost(_hostAndPort);
9095
socket.setSoTimeout(socketTimeout);
9196

92-
if (ssl) {
93-
SSLSocketFactory _sslSocketFactory = this.sslSocketFactory;
94-
if (null == _sslSocketFactory) {
95-
_sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
96-
}
97-
Socket plainSocket = socket;
98-
socket = _sslSocketFactory.createSocket(socket, _hostAndPort.getHost(), _hostAndPort.getPort(), true);
99-
100-
if (null != sslParameters) {
101-
((SSLSocket) socket).setSSLParameters(sslParameters);
102-
}
103-
socket = new SSLSocketWrapper((SSLSocket) socket, plainSocket);
104-
105-
if (null != hostnameVerifier
106-
&& !hostnameVerifier.verify(_hostAndPort.getHost(), ((SSLSocket) socket).getSession())) {
107-
String message = String.format(
108-
"The connection to '%s' failed ssl/tls hostname verification.", _hostAndPort.getHost());
109-
throw new JedisConnectionException(message);
110-
}
97+
if (ssl || sslOptions != null) {
98+
socket = createSslSocket(_hostAndPort, socket);
11199
}
112100

113101
return socket;
@@ -122,6 +110,50 @@ public Socket createSocket() throws JedisConnectionException {
122110
}
123111
}
124112

113+
/**
114+
* ssl enable check is done before this.
115+
*/
116+
private Socket createSslSocket(HostAndPort _hostAndPort, Socket socket) throws IOException, GeneralSecurityException {
117+
118+
Socket plainSocket = socket;
119+
120+
SSLSocketFactory _sslSocketFactory;
121+
SSLParameters _sslParameters;
122+
123+
if (sslOptions != null) {
124+
125+
SSLContext _sslContext = sslOptions.createSslContext();
126+
_sslSocketFactory = _sslContext.getSocketFactory();
127+
128+
_sslParameters = sslOptions.getSslParameters();
129+
130+
} else {
131+
132+
_sslSocketFactory = this.sslSocketFactory;
133+
_sslParameters = this.sslParameters;
134+
}
135+
136+
if (_sslSocketFactory == null) {
137+
_sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
138+
}
139+
140+
SSLSocket sslSocket = (SSLSocket) _sslSocketFactory.createSocket(socket,
141+
_hostAndPort.getHost(), _hostAndPort.getPort(), true);
142+
143+
if (_sslParameters != null) {
144+
sslSocket.setSSLParameters(_sslParameters);
145+
}
146+
147+
// allowing HostnameVerifier for both SslOptions and legacy ssl config
148+
if (hostnameVerifier != null && !hostnameVerifier.verify(_hostAndPort.getHost(), sslSocket.getSession())) {
149+
String message = String.format("The connection to '%s' failed ssl/tls hostname verification.",
150+
_hostAndPort.getHost());
151+
throw new JedisConnectionException(message);
152+
}
153+
154+
return new SSLSocketWrapper(sslSocket, plainSocket);
155+
}
156+
125157
public void updateHostAndPort(HostAndPort hostAndPort) {
126158
this.hostAndPort = hostAndPort;
127159
}

src/main/java/redis/clients/jedis/JedisClientConfig.java

+11
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ default String getPassword() {
4646
return null;
4747
}
4848

49+
// TODO: return null
4950
default Supplier<RedisCredentials> getCredentialsProvider() {
5051
return new DefaultRedisCredentialsProvider(
5152
new DefaultRedisCredentials(getUser(), getPassword()));
@@ -78,6 +79,16 @@ default SSLParameters getSslParameters() {
7879
return null;
7980
}
8081

82+
/**
83+
* {@link JedisClientConfig#isSsl()}, {@link JedisClientConfig#getSslSocketFactory()} and
84+
* {@link JedisClientConfig#getSslParameters()} will be ignored if
85+
* {@link JedisClientConfig#getSslOptions() this} is set.
86+
* @return ssl options
87+
*/
88+
default SslOptions getSslOptions() {
89+
return null;
90+
}
91+
8192
default HostnameVerifier getHostnameVerifier() {
8293
return null;
8394
}

src/main/java/redis/clients/jedis/JedisPooled.java

+12-7
Original file line numberDiff line numberDiff line change
@@ -292,10 +292,12 @@ public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final S
292292
}
293293

294294
public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final String host, int port,
295-
final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout,
296-
final String user, final String password, final int database, final String clientName) {
297-
this(new HostAndPort(host, port), DefaultJedisClientConfig.create(connectionTimeout, soTimeout,
298-
infiniteSoTimeout, user, password, database, clientName, false, null, null, null, null, null),
295+
final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout, final String user,
296+
final String password, final int database, final String clientName) {
297+
this(new HostAndPort(host, port),
298+
DefaultJedisClientConfig.builder().connectionTimeoutMillis(connectionTimeout).socketTimeoutMillis(soTimeout)
299+
.blockingSocketTimeoutMillis(infiniteSoTimeout).user(user).password(password).database(database)
300+
.clientName(clientName).build(),
299301
poolConfig);
300302
}
301303

@@ -304,9 +306,12 @@ public JedisPooled(final GenericObjectPoolConfig<Connection> poolConfig, final S
304306
final String password, final int database, final String clientName, final boolean ssl,
305307
final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,
306308
final HostnameVerifier hostnameVerifier) {
307-
this(new HostAndPort(host, port), DefaultJedisClientConfig.create(connectionTimeout, soTimeout,
308-
infiniteSoTimeout, user, password, database, clientName, ssl, sslSocketFactory, sslParameters,
309-
hostnameVerifier, null, null), poolConfig);
309+
this(new HostAndPort(host, port),
310+
DefaultJedisClientConfig.builder().connectionTimeoutMillis(connectionTimeout).socketTimeoutMillis(soTimeout)
311+
.blockingSocketTimeoutMillis(infiniteSoTimeout).user(user).password(password).database(database)
312+
.clientName(clientName).ssl(ssl).sslSocketFactory(sslSocketFactory).sslParameters(sslParameters)
313+
.hostnameVerifier(hostnameVerifier).build(),
314+
poolConfig);
310315
}
311316

312317
public JedisPooled(final URI uri) {

0 commit comments

Comments
 (0)