google-nomulus/javatests/google/registry/proxy/handler/SslServerInitializerTest.java
jianglai 7e42ee48a4 Open source GCP proxy
Dagger updated to 2.13, along with all its dependencies.

Also allows us to have multiple config files for different environment (prod, sandbox, alpha, local, etc) and specify which one to use on the command line with a --env flag. Therefore the same binary can be used in all environments.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=176551289
2017-11-21 19:19:03 -05:00

347 lines
13 KiB
Java

// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// 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 google.registry.proxy.handler;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.proxy.handler.SslInitializerTestUtils.getKeyPair;
import static google.registry.proxy.handler.SslInitializerTestUtils.setUpClient;
import static google.registry.proxy.handler.SslInitializerTestUtils.setUpServer;
import static google.registry.proxy.handler.SslInitializerTestUtils.signKeyPair;
import static google.registry.proxy.handler.SslInitializerTestUtils.verifySslChannel;
import com.google.common.collect.ImmutableList;
import google.registry.proxy.Protocol;
import google.registry.proxy.Protocol.BackendProtocol;
import google.registry.proxy.handler.SslInitializerTestUtils.DumpHandler;
import google.registry.proxy.handler.SslInitializerTestUtils.EchoHandler;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.channel.local.LocalAddress;
import io.netty.channel.local.LocalChannel;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.util.concurrent.Future;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSession;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Unit tests for {@link SslServerInitializer}.
*
* <p>To validate that the handler accepts & rejects connections as expected, a test server and a
* test client are spun up, and both connect to the {@link LocalAddress} within the JVM. This avoids
* the overhead of routing traffic through the network layer, even if it were to go through
* loopback. It also alleviates the need to pick a free port to use.
*
* <p>The local addresses used in each test method must to be different, otherwise tests run in
* parallel may interfere with each other.
*/
@RunWith(JUnit4.class)
public class SslServerInitializerTest {
/** Fake host to test if the SSL engine gets the correct peer host. */
private static final String SSL_HOST = "www.example.tld";
/** Fake port to test if the SSL engine gets the correct peer port. */
private static final int SSL_PORT = 12345;
/** Fake protocol saved in channel attribute. */
private static final BackendProtocol PROTOCOL =
Protocol.backendBuilder()
.name("ssl")
.host(SSL_HOST)
.port(SSL_PORT)
.handlerProviders(ImmutableList.of())
.build();
private ChannelInitializer<LocalChannel> getServerInitializer(
Lock serverLock,
Exception serverException,
PrivateKey privateKey,
X509Certificate... certificates)
throws Exception {
return new ChannelInitializer<LocalChannel>() {
@Override
protected void initChannel(LocalChannel ch) throws Exception {
ch.pipeline()
.addLast(
new SslServerInitializer<LocalChannel>(SslProvider.JDK, privateKey, certificates),
new EchoHandler(serverLock, serverException));
}
};
}
private ChannelInitializer<LocalChannel> getClientInitializer(
X509Certificate trustedCertificate,
PrivateKey privateKey,
X509Certificate certificate,
Lock clientLock,
ByteBuf buffer,
Exception clientException) {
return new ChannelInitializer<LocalChannel>() {
@Override
protected void initChannel(LocalChannel ch) throws Exception {
SslContextBuilder sslContextBuilder =
SslContextBuilder.forClient().trustManager(trustedCertificate);
if (privateKey != null && certificate != null) {
sslContextBuilder.keyManager(privateKey, certificate);
}
SslHandler sslHandler =
sslContextBuilder.build().newHandler(ch.alloc(), SSL_HOST, SSL_PORT);
// Enable hostname verification.
SSLEngine sslEngine = sslHandler.engine();
SSLParameters sslParameters = sslEngine.getSSLParameters();
sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
sslEngine.setSSLParameters(sslParameters);
ch.pipeline().addLast("Client SSL Handler", sslHandler);
ch.pipeline().addLast(new DumpHandler(clientLock, buffer, clientException));
}
};
}
@Test
public void testSuccess_swappedInitializerWithSslHandler() throws Exception {
SelfSignedCertificate ssc = new SelfSignedCertificate(SSL_HOST);
SslServerInitializer<EmbeddedChannel> sslServerInitializer =
new SslServerInitializer<>(SslProvider.JDK, ssc.key(), ssc.cert());
EmbeddedChannel channel = new EmbeddedChannel();
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(sslServerInitializer);
ChannelHandler firstHandler = pipeline.first();
assertThat(firstHandler.getClass()).isEqualTo(SslHandler.class);
SslHandler sslHandler = (SslHandler) firstHandler;
assertThat(sslHandler.engine().getNeedClientAuth()).isTrue();
assertThat(channel.isActive()).isTrue();
}
@Test
public void testSuccess_trustAnyClientCert() throws Exception {
SelfSignedCertificate serverSsc = new SelfSignedCertificate(SSL_HOST);
LocalAddress localAddress = new LocalAddress("TRUST_ANY_CLIENT_CERT");
Lock clientLock = new ReentrantLock();
Lock serverLock = new ReentrantLock();
ByteBuf buffer = Unpooled.buffer();
Exception clientException = new Exception();
Exception serverException = new Exception();
EventLoopGroup eventLoopGroup =
setUpServer(
getServerInitializer(serverLock, serverException, serverSsc.key(), serverSsc.cert()),
localAddress);
SelfSignedCertificate clientSsc = new SelfSignedCertificate();
Channel channel =
setUpClient(
eventLoopGroup,
getClientInitializer(
serverSsc.cert(),
clientSsc.key(),
clientSsc.cert(),
clientLock,
buffer,
clientException),
localAddress,
PROTOCOL);
SSLSession sslSession =
verifySslChannel(
channel, ImmutableList.of(serverSsc.cert()), clientLock, serverLock, buffer, SSL_HOST);
// Verify that the SSL session gets the client cert. Note that this SslSession is for the client
// channel, therefore its local certificates are the remote certificates of the SslSession for
// the server channel, and vice versa.
assertThat(sslSession.getLocalCertificates()).asList().containsExactly(clientSsc.cert());
assertThat(sslSession.getPeerCertificates()).asList().containsExactly(serverSsc.cert());
Future<?> unusedFuture = eventLoopGroup.shutdownGracefully().syncUninterruptibly();
}
@Test
public void testSuccess_CertSignedByOtherCA() throws Exception {
// The self-signed cert of the CA.
SelfSignedCertificate caSsc = new SelfSignedCertificate();
KeyPair keyPair = getKeyPair();
X509Certificate serverCert = signKeyPair(caSsc, keyPair, SSL_HOST);
LocalAddress localAddress = new LocalAddress("CERT_SIGNED_BY_OTHER_CA");
Lock clientLock = new ReentrantLock();
Lock serverLock = new ReentrantLock();
ByteBuf buffer = Unpooled.buffer();
Exception clientException = new Exception();
Exception serverException = new Exception();
EventLoopGroup eventLoopGroup =
setUpServer(
getServerInitializer(
serverLock,
serverException,
keyPair.getPrivate(),
// Serving both the server cert, and the CA cert
serverCert,
caSsc.cert()),
localAddress);
SelfSignedCertificate clientSsc = new SelfSignedCertificate();
Channel channel =
setUpClient(
eventLoopGroup,
getClientInitializer(
// Client trusts the CA cert
caSsc.cert(),
clientSsc.key(),
clientSsc.cert(),
clientLock,
buffer,
clientException),
localAddress,
PROTOCOL);
SSLSession sslSession =
verifySslChannel(
channel,
ImmutableList.of(serverCert, caSsc.cert()),
clientLock,
serverLock,
buffer,
SSL_HOST);
assertThat(sslSession.getLocalCertificates()).asList().containsExactly(clientSsc.cert());
assertThat(sslSession.getPeerCertificates())
.asList()
.containsExactly(serverCert, caSsc.cert())
.inOrder();
Future<?> unusedFuture = eventLoopGroup.shutdownGracefully().syncUninterruptibly();
}
@Test
public void testFailure_requireClientCertificate() throws Exception {
SelfSignedCertificate serverSsc = new SelfSignedCertificate(SSL_HOST);
LocalAddress localAddress = new LocalAddress("REQUIRE_CLIENT_CERT");
Lock clientLock = new ReentrantLock();
Lock serverLock = new ReentrantLock();
ByteBuf buffer = Unpooled.buffer();
Exception clientException = new Exception();
Exception serverException = new Exception();
EventLoopGroup eventLoopGroup =
setUpServer(
getServerInitializer(serverLock, serverException, serverSsc.key(), serverSsc.cert()),
localAddress);
Channel channel =
setUpClient(
eventLoopGroup,
getClientInitializer(
serverSsc.cert(),
// No client cert/private key used.
null,
null,
clientLock,
buffer,
clientException),
localAddress,
PROTOCOL);
serverLock.lock();
// When the server rejects the client during handshake due to lack of client certificate, only
// the server throws an exception.
assertThat(serverException).hasCauseThat().isInstanceOf(DecoderException.class);
assertThat(serverException)
.hasCauseThat()
.hasCauseThat()
.isInstanceOf(SSLHandshakeException.class);
assertThat(channel.isActive()).isFalse();
Future<?> unusedFuture = eventLoopGroup.shutdownGracefully().syncUninterruptibly();
}
@Test
public void testFailure_wrongHostnameInCertificate() throws Exception {
SelfSignedCertificate serverSsc = new SelfSignedCertificate("wrong.com");
LocalAddress localAddress = new LocalAddress("REQUIRE_CLIENT_CERT");
Lock clientLock = new ReentrantLock();
Lock serverLock = new ReentrantLock();
ByteBuf buffer = Unpooled.buffer();
Exception clientException = new Exception();
Exception serverException = new Exception();
EventLoopGroup eventLoopGroup =
setUpServer(
getServerInitializer(serverLock, serverException, serverSsc.key(), serverSsc.cert()),
localAddress);
SelfSignedCertificate clientSsc = new SelfSignedCertificate();
Channel channel =
setUpClient(
eventLoopGroup,
getClientInitializer(
serverSsc.cert(),
clientSsc.key(),
clientSsc.cert(),
clientLock,
buffer,
clientException),
localAddress,
PROTOCOL);
serverLock.lock();
clientLock.lock();
// When the client rejects the server cert due to wrong hostname, the client error is wrapped
// several layers in the exception. The server also throws an exception.
assertThat(clientException).hasCauseThat().isInstanceOf(DecoderException.class);
assertThat(clientException)
.hasCauseThat()
.hasCauseThat()
.isInstanceOf(SSLHandshakeException.class);
assertThat(clientException)
.hasCauseThat()
.hasCauseThat()
.hasCauseThat()
.isInstanceOf(SSLHandshakeException.class);
assertThat(clientException)
.hasCauseThat()
.hasCauseThat()
.hasCauseThat()
.hasCauseThat()
.isInstanceOf(CertificateException.class);
assertThat(clientException)
.hasCauseThat()
.hasCauseThat()
.hasCauseThat()
.hasCauseThat()
.hasMessageThat()
.contains(SSL_HOST);
assertThat(serverException).hasCauseThat().isInstanceOf(DecoderException.class);
assertThat(serverException).hasCauseThat().hasCauseThat().isInstanceOf(SSLException.class);
assertThat(channel.isActive()).isFalse();
Future<?> unusedFuture = eventLoopGroup.shutdownGracefully().syncUninterruptibly();
}
}