Skip to content

Commit

Permalink
Option to configure SSL/TLS verification level #1460
Browse files Browse the repository at this point in the history
RedisURI.setVerifyPeer(…) now accepts SslVerifyMode to configure the level of SSL verification. setVerifyPeer(true) (default) maps to FULL,  setVerifyPeer(false) maps to NONE. SslVerifyMode.CA allows verifying the certificate validity using the default (or configured) trust manager without peer name verification.
  • Loading branch information
mp911de committed Jan 8, 2021
1 parent e3196cd commit 65e9162
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 25 deletions.
84 changes: 68 additions & 16 deletions src/main/java/io/lettuce/core/RedisURI.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,25 +74,25 @@
*
* <b>Redis Standalone</b> <blockquote> <i>redis</i><b>{@code ://}</b>[[<i>username</i>{@code :}]<i>password@</i>]<i>host</i>
* [<b>{@code :} </b> <i>port</i>][<b>{@code /}</b><i>database</i>][<b>{@code ?}</b>
* [<i>timeout=timeout</i>[<i>d|h|m|s|ms|us|ns</i>]] [ <i>&amp;database=database</i>] [<i>&amp;clientName=clientName</i>]]
* </blockquote>
* [<i>timeout=timeout</i>[<i>d|h|m|s|ms|us|ns</i>]] [ <i>&amp;database=database</i>] [<i>&amp;clientName=clientName</i>]
* [<i>&amp;verifyPeer=NONE|CA|FULL</i>]] </blockquote>
*
* <b>Redis Standalone (SSL)</b> <blockquote>
* <i>rediss</i><b>{@code ://}</b>[[<i>username</i>{@code :}]<i>password@</i>]<i>host</i> [<b>{@code :} </b>
* <i>port</i>][<b>{@code /}</b><i>database</i>][<b>{@code ?}</b> [<i>timeout=timeout</i>[<i>d|h|m|s|ms|us|ns</i>]] [
* <i>&amp;database=database</i>] [<i>&amp;clientName=clientName</i>]] </blockquote>
* <i>&amp;database=database</i>] [<i>&amp;clientName=clientName</i>] [<i>&amp;verifyPeer=NONE|CA|FULL</i>]] </blockquote>
*
* Redis Standalone (Unix Domain Sockets)</b> <blockquote> <i>redis-socket</i><b>{@code ://}
* </b>[[<i>username</i>{@code :}]<i>password@</i>]<i>path</i>[
* <b>{@code ?}</b>[<i>timeout=timeout</i>[<i>d|h|m|s|ms|us|ns</i>]][<i>&amp;database=database</i>]
* [<i>&amp;clientName=clientName</i>]] </blockquote>
* [<i>&amp;clientName=clientName</i>] [<i>&amp;verifyPeer=NONE|CA|FULL</i>]] </blockquote>
*
* <b>Redis Sentinel</b> <blockquote>
* <i>redis-sentinel</i><b>{@code ://}</b>[[<i>username</i>{@code :}]<i>password@</i>]<i>host1</i> [<b>{@code :} </b>
* <i>port1</i>][, <i>host2</i> [<b>{@code :}</b><i>port2</i>]][, <i>hostN</i> [<b>{@code :}</b><i>portN</i>]][<b>{@code /} </b>
* <i>database</i>][<b>{@code ?} </b>[<i>timeout=timeout</i>[<i>d|h|m|s|ms|us|ns</i>]] [
* <i>&amp;sentinelMasterId=sentinelMasterId</i>] [<i>&amp;database=database</i>] [<i>&amp;clientName=clientName</i>]]
* </blockquote>
* <i>&amp;sentinelMasterId=sentinelMasterId</i>] [<i>&amp;database=database</i>] [<i>&amp;clientName=clientName</i>]
* [<i>&amp;verifyPeer=NONE|CA|FULL</i>]] </blockquote>
*
* <p>
* Note: When using Redis Sentinel, the password from the URI applies to the data nodes only. Sentinel authentication must be
Expand Down Expand Up @@ -166,6 +166,8 @@ public class RedisURI implements Serializable, ConnectionPoint {

public static final String PARAMETER_NAME_CLIENT_NAME = "clientName";

public static final String PARAMETER_NAME_VERIFY_PEER = "verifyPeer";

public static final Map<String, LongFunction<Duration>> CONVERTER_MAP;

static {
Expand Down Expand Up @@ -215,7 +217,7 @@ public class RedisURI implements Serializable, ConnectionPoint {

private boolean ssl = false;

private boolean verifyPeer = true;
private SslVerifyMode verifyMode = SslVerifyMode.FULL;

private boolean startTls = false;

Expand Down Expand Up @@ -577,12 +579,22 @@ public void setSsl(boolean ssl) {
}

/**
* Sets whether to verify peers when using {@link #isSsl() SSL}.
* Returns whether to verify peers when using {@link #isSsl() SSL}.
*
* @return {@code true} to verify peers when using {@link #isSsl() SSL}.
*/
public boolean isVerifyPeer() {
return verifyPeer;
return verifyMode != SslVerifyMode.NONE;
}

/**
* Returns the mode to verify peers when using {@link #isSsl() SSL}.
*
* @return the verification mode
* @since 6.1
*/
public SslVerifyMode getVerifyMode() {
return verifyMode;
}

/**
Expand All @@ -592,8 +604,20 @@ public boolean isVerifyPeer() {
* @param verifyPeer {@code true} to verify peers when using {@link #isSsl() SSL}.
*/
public void setVerifyPeer(boolean verifyPeer) {
this.verifyPeer = verifyPeer;
this.sentinels.forEach(it -> it.setVerifyPeer(verifyPeer));
setVerifyPeer(verifyPeer ? SslVerifyMode.FULL : SslVerifyMode.NONE);
}

/**
* Sets how to verify peers when using {@link #isSsl() SSL}. Sets peer verification also for already configured Redis
* Sentinel nodes.
*
* @param verifyMode verification mode to use when using {@link #isSsl() SSL}.
* @since 6.1
*/
public void setVerifyPeer(SslVerifyMode verifyMode) {
LettuceAssert.notNull(verifyMode, "VerifyMode must not be null");
this.verifyMode = verifyMode;
this.sentinels.forEach(it -> it.setVerifyPeer(this.verifyMode));
}

/**
Expand Down Expand Up @@ -718,6 +742,10 @@ private static RedisURI buildRedisUriFromUri(URI uri) {
parseClientName(builder, queryParam);
}

if (forStartWith.startsWith(PARAMETER_NAME_VERIFY_PEER.toLowerCase() + "=")) {
parseVerifyPeer(builder, queryParam);
}

if (forStartWith.startsWith(PARAMETER_NAME_SENTINEL_MASTER_ID.toLowerCase() + "=")) {
parseSentinelMasterId(builder, queryParam);
}
Expand Down Expand Up @@ -786,6 +814,10 @@ private String getQueryString() {
queryPairs.add(PARAMETER_NAME_CLIENT_NAME + "=" + urlEncode(clientName));
}

if (isSsl() && getVerifyMode() != SslVerifyMode.FULL) {
queryPairs.add(PARAMETER_NAME_VERIFY_PEER + "=" + verifyMode.name());
}

if (sentinelMasterId != null) {
queryPairs.add(PARAMETER_NAME_SENTINEL_MASTER_ID + "=" + urlEncode(sentinelMasterId));
}
Expand Down Expand Up @@ -958,6 +990,14 @@ private static void parseClientName(Builder builder, String queryParam) {
}
}

private static void parseVerifyPeer(Builder builder, String queryParam) {

String verifyPeer = getValuePart(queryParam);
if (isNotEmpty(verifyPeer)) {
builder.withVerifyPeer(SslVerifyMode.valueOf(verifyPeer.toUpperCase()));
}
}

private static void parseSentinelMasterId(Builder builder, String queryParam) {

String masterIdString = getValuePart(queryParam);
Expand Down Expand Up @@ -1077,7 +1117,6 @@ private static boolean isSentinel(String scheme) {
return URI_SCHEME_REDIS_SENTINEL.equals(scheme) || URI_SCHEME_REDIS_SENTINEL_SECURE.equals(scheme);
}


/**
* Builder for Redis URI.
*/
Expand All @@ -1103,7 +1142,7 @@ public static class Builder {

private boolean ssl = false;

private boolean verifyPeer = true;
private SslVerifyMode verifyMode = SslVerifyMode.FULL;

private boolean startTls = false;

Expand Down Expand Up @@ -1379,9 +1418,22 @@ public Builder withStartTls(boolean startTls) {
* @return the builder
*/
public Builder withVerifyPeer(boolean verifyPeer) {
return withVerifyPeer(verifyPeer ? SslVerifyMode.FULL : SslVerifyMode.NONE);
}

/**
* Configures peer verification mode. Sets peer verification also for already configured Redis Sentinel nodes.
*
* @param verifyMode the mode to verify hosts when using SSL
* @return the builder
* @since 6.1
*/
public Builder withVerifyPeer(SslVerifyMode verifyMode) {

LettuceAssert.notNull(verifyMode, "VerifyMode must not be null");

this.verifyPeer = verifyPeer;
this.sentinels.forEach(it -> it.setVerifyPeer(verifyPeer));
this.verifyMode = verifyMode;
this.sentinels.forEach(it -> it.setVerifyPeer(verifyMode));
return this;
}

Expand Down Expand Up @@ -1576,7 +1628,7 @@ public RedisURI build() {
redisURI.setSocket(socket);
redisURI.setSsl(ssl);
redisURI.setStartTls(startTls);
redisURI.setVerifyPeer(verifyPeer);
redisURI.setVerifyPeer(verifyMode);
redisURI.setTimeout(timeout);

return redisURI;
Expand Down
12 changes: 7 additions & 5 deletions src/main/java/io/lettuce/core/SslConnectionBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ protected List<ChannelHandler> buildHandlers() {

@Override
public ChannelInitializer<Channel> build(SocketAddress socketAddress) {
return new SslChannelInitializer(this::buildHandlers, toHostAndPort(socketAddress), redisURI.isVerifyPeer(),
return new SslChannelInitializer(this::buildHandlers, toHostAndPort(socketAddress), redisURI.getVerifyMode(),
redisURI.isStartTls(), clientResources(), clientOptions().getSslOptions());
}

Expand All @@ -90,15 +90,15 @@ static class SslChannelInitializer extends io.netty.channel.ChannelInitializer<C

private final HostAndPort hostAndPort;

private final boolean verifyPeer;
private final SslVerifyMode verifyPeer;

private final boolean startTls;

private final ClientResources clientResources;

private final SslOptions sslOptions;

public SslChannelInitializer(Supplier<List<ChannelHandler>> handlers, HostAndPort hostAndPort, boolean verifyPeer,
public SslChannelInitializer(Supplier<List<ChannelHandler>> handlers, HostAndPort hostAndPort, SslVerifyMode verifyPeer,
boolean startTls, ClientResources clientResources, SslOptions sslOptions) {

this.handlers = handlers;
Expand Down Expand Up @@ -131,9 +131,11 @@ private SSLEngine initializeSSLEngine(ByteBufAllocator alloc) throws IOException
SSLParameters sslParams = sslOptions.createSSLParameters();
SslContextBuilder sslContextBuilder = sslOptions.createSslContextBuilder();

if (verifyPeer) {
if (verifyPeer == SslVerifyMode.FULL) {
sslParams.setEndpointIdentificationAlgorithm("HTTPS");
} else {
} else if (verifyPeer == SslVerifyMode.CA) {
sslParams.setEndpointIdentificationAlgorithm("");
} else if (verifyPeer == SslVerifyMode.NONE) {
sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
}

Expand Down
40 changes: 40 additions & 0 deletions src/main/java/io/lettuce/core/SslVerifyMode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2021 the original author or authors.
*
* 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
*
* https://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 io.lettuce.core;

/**
* Enumeration of SSL/TLS verification modes.
*
* @author Mark Paluch
* @since 6.1
*/
public enum SslVerifyMode {

/**
* No verification at all.
*/
NONE,

/**
* Verify the CA and certificate without verifying that the hostname matches.
*/
CA,

/**
* Full certificate verification.
*/
FULL;
}
4 changes: 4 additions & 0 deletions src/test/java/io/lettuce/core/RedisURIUnitTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ void shouldThrowIllegalArgumentExceptionOnMalformedUri() {
void sslUriTest() {
RedisURI redisURI = RedisURI.create("redis+ssl://localhost:6379");
assertThat(redisURI).hasToString("rediss://localhost:6379");

redisURI = RedisURI.create("redis+ssl://localhost:6379?verifyPeer=ca");
assertThat(redisURI.getVerifyMode()).isEqualTo(SslVerifyMode.CA);
assertThat(redisURI).hasToString("rediss://localhost:6379?verifyPeer=CA");
}

@Test
Expand Down
19 changes: 15 additions & 4 deletions src/test/java/io/lettuce/core/SslIntegrationTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@
*/
package io.lettuce.core;

import static io.lettuce.test.settings.TestSettings.sslPort;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static io.lettuce.test.settings.TestSettings.*;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assumptions.*;

import java.io.File;
import java.net.MalformedURLException;
Expand Down Expand Up @@ -121,6 +120,18 @@ void standaloneWithJdkSsl() {
verifyConnection(URI_VERIFY);
}

@Test
void standaloneWithVerifyCaOnly() {

SslOptions sslOptions = SslOptions.builder() //
.jdkSslProvider() //
.truststore(TRUSTSTORE_FILE) //
.build();
setOptions(sslOptions);

verifyConnection(sslURIBuilder(1).withVerifyPeer(SslVerifyMode.CA).build());
}

@Test
void standaloneWithPemCert() {

Expand Down

0 comments on commit 65e9162

Please sign in to comment.