mirror of
https://github.com/google/nomulus.git
synced 2025-07-06 03:03:34 +02:00
Remove SSL initializer from the prober (#378)
The prober now uses the common SSL initializer in the networking subproject. Also changed both initializers to take an ImmutableList of certificates other than an array of those, for better immutability. I have no idea where these lockfile changes are coming from. They seem to be pure noise as far as code review is concerned.
This commit is contained in:
parent
e318f47fc6
commit
05d56fe1a2
27 changed files with 257 additions and 770 deletions
|
@ -26,11 +26,11 @@ import google.registry.monitoring.blackbox.connection.Protocol;
|
|||
import google.registry.monitoring.blackbox.exception.UndeterminedStateException;
|
||||
import google.registry.monitoring.blackbox.handler.ActionHandler;
|
||||
import google.registry.monitoring.blackbox.handler.ConversionHandler;
|
||||
import google.registry.monitoring.blackbox.handler.NettyRule;
|
||||
import google.registry.monitoring.blackbox.handler.TestActionHandler;
|
||||
import google.registry.monitoring.blackbox.message.OutboundMessageType;
|
||||
import google.registry.monitoring.blackbox.message.TestMessage;
|
||||
import google.registry.monitoring.blackbox.token.Token;
|
||||
import google.registry.networking.handler.NettyRule;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
|
@ -62,7 +62,7 @@ public class ProbingStepTest {
|
|||
private final Bootstrap bootstrap =
|
||||
new Bootstrap().group(eventLoopGroup).channel(LocalChannel.class);
|
||||
/** Used for testing how well probing step can create connection to blackbox server */
|
||||
@Rule public NettyRule nettyRule = new NettyRule(eventLoopGroup);
|
||||
@Rule public NettyRule nettyRule = new NettyRule();
|
||||
|
||||
/**
|
||||
* The two main handlers we need in any test pipeline used that connects to {@link NettyRule's
|
||||
|
|
|
@ -22,19 +22,17 @@ import static java.nio.charset.StandardCharsets.UTF_8;
|
|||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.monitoring.blackbox.handler.ActionHandler;
|
||||
import google.registry.monitoring.blackbox.handler.ConversionHandler;
|
||||
import google.registry.monitoring.blackbox.handler.NettyRule;
|
||||
import google.registry.monitoring.blackbox.handler.TestActionHandler;
|
||||
import google.registry.monitoring.blackbox.message.TestMessage;
|
||||
import google.registry.networking.handler.NettyRule;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
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.channel.nio.NioEventLoopGroup;
|
||||
import org.joda.time.Duration;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
|
@ -57,10 +55,7 @@ public class ProbingActionTest {
|
|||
private static final String ADDRESS_NAME = "TEST_ADDRESS";
|
||||
private static final int TEST_PORT = 0;
|
||||
|
||||
private static final EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1);
|
||||
|
||||
/** Used for testing how well probing step can create connection to blackbox server */
|
||||
@Rule public NettyRule nettyRule = new NettyRule(eventLoopGroup);
|
||||
@Rule public NettyRule nettyRule = new NettyRule();
|
||||
|
||||
/**
|
||||
* We use custom Test {@link ActionHandler} and {@link ConversionHandler} so test depends only on
|
||||
|
@ -127,7 +122,8 @@ public class ProbingActionTest {
|
|||
// setup
|
||||
|
||||
LocalAddress address = new LocalAddress(ADDRESS_NAME);
|
||||
Bootstrap bootstrap = new Bootstrap().group(eventLoopGroup).channel(LocalChannel.class);
|
||||
Bootstrap bootstrap =
|
||||
new Bootstrap().group(nettyRule.getEventLoopGroup()).channel(LocalChannel.class);
|
||||
|
||||
// Sets up a Protocol corresponding to when a new connection is created.
|
||||
Protocol protocol =
|
||||
|
|
|
@ -1,229 +0,0 @@
|
|||
// Copyright 2019 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.monitoring.blackbox.handler;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.monitoring.blackbox.connection.ProbingAction.REMOTE_ADDRESS_KEY;
|
||||
import static google.registry.monitoring.blackbox.connection.Protocol.PROTOCOL_KEY;
|
||||
import static google.registry.testing.JUnitBackports.assertThrows;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.truth.ThrowableSubject;
|
||||
import google.registry.monitoring.blackbox.ProbingStepTest;
|
||||
import google.registry.monitoring.blackbox.connection.ProbingActionTest;
|
||||
import google.registry.monitoring.blackbox.connection.Protocol;
|
||||
import google.registry.monitoring.blackbox.testserver.TestServer;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.local.LocalAddress;
|
||||
import io.netty.channel.local.LocalChannel;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import org.junit.rules.ExternalResource;
|
||||
|
||||
/**
|
||||
* Helper for setting up and testing client / server connection with netty.
|
||||
*
|
||||
* <p>Code based on and almost identical to {@code NettyRule} in the proxy. Used in {@link
|
||||
* SslClientInitializerTest}, {@link ProbingActionTest}, and {@link ProbingStepTest}
|
||||
*/
|
||||
public final class NettyRule extends ExternalResource {
|
||||
|
||||
private final EventLoopGroup eventLoopGroup;
|
||||
|
||||
// Handler attached to server's channel to record the request received.
|
||||
private EchoHandler echoHandler;
|
||||
// Handler attached to client's channel to record the response received.
|
||||
private DumpHandler dumpHandler;
|
||||
|
||||
private Channel channel;
|
||||
|
||||
// All I/O operations are done inside the single thread within this event loop group, which is
|
||||
// different from the main test thread. Therefore synchronizations are required to make sure that
|
||||
// certain I/O activities are finished when assertions are performed.
|
||||
public NettyRule() {
|
||||
eventLoopGroup = new NioEventLoopGroup(1);
|
||||
}
|
||||
|
||||
public NettyRule(EventLoopGroup e) {
|
||||
eventLoopGroup = e;
|
||||
}
|
||||
|
||||
private static void writeToChannelAndFlush(Channel channel, String data) {
|
||||
ChannelFuture unusedFuture =
|
||||
channel.writeAndFlush(Unpooled.wrappedBuffer(data.getBytes(US_ASCII)));
|
||||
}
|
||||
|
||||
/** Sets up a server channel bound to the given local address. */
|
||||
public void setUpServer(LocalAddress localAddress, ChannelHandler... handlers) {
|
||||
checkState(echoHandler == null, "Can't call setUpServer twice");
|
||||
echoHandler = new EchoHandler();
|
||||
|
||||
new TestServer(
|
||||
eventLoopGroup,
|
||||
localAddress,
|
||||
ImmutableList.<ChannelHandler>builder().add(handlers).add(echoHandler).build());
|
||||
}
|
||||
|
||||
/** Sets up a client channel connecting to the give local address. */
|
||||
void setUpClient(
|
||||
LocalAddress localAddress, Protocol protocol, String host, ChannelHandler handler) {
|
||||
checkState(echoHandler != null, "Must call setUpServer before setUpClient");
|
||||
checkState(dumpHandler == null, "Can't call setUpClient twice");
|
||||
dumpHandler = new DumpHandler();
|
||||
ChannelInitializer<LocalChannel> clientInitializer =
|
||||
new ChannelInitializer<LocalChannel>() {
|
||||
@Override
|
||||
protected void initChannel(LocalChannel ch) throws Exception {
|
||||
// Add the given handler
|
||||
ch.pipeline().addLast(handler);
|
||||
// Add the "dumpHandler" last to log the incoming message
|
||||
ch.pipeline().addLast(dumpHandler);
|
||||
}
|
||||
};
|
||||
Bootstrap b =
|
||||
new Bootstrap()
|
||||
.group(eventLoopGroup)
|
||||
.channel(LocalChannel.class)
|
||||
.handler(clientInitializer)
|
||||
.attr(PROTOCOL_KEY, protocol)
|
||||
.attr(REMOTE_ADDRESS_KEY, host);
|
||||
|
||||
channel = b.connect(localAddress).syncUninterruptibly().channel();
|
||||
}
|
||||
|
||||
private void checkReady() {
|
||||
checkState(channel != null, "Must call setUpClient to finish NettyRule setup");
|
||||
}
|
||||
|
||||
/** Test that custom setup to send message to current server sends right message */
|
||||
public void assertReceivedMessage(String message) throws Exception {
|
||||
assertThat(echoHandler.getRequestFuture().get()).isEqualTo(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a message can go through, both inbound and outbound.
|
||||
*
|
||||
* <p>The client writes the message to the server, which echos it back and saves the string in its
|
||||
* promise. The client receives the echo and saves it in its promise. All these activities happens
|
||||
* in the I/O thread, and this call itself returns immediately.
|
||||
*/
|
||||
void assertThatMessagesWork() throws Exception {
|
||||
checkReady();
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
|
||||
writeToChannelAndFlush(channel, "Hello, world!");
|
||||
assertThat(echoHandler.getRequestFuture().get()).isEqualTo("Hello, world!");
|
||||
assertThat(dumpHandler.getResponseFuture().get()).isEqualTo("Hello, world!");
|
||||
}
|
||||
|
||||
Channel getChannel() {
|
||||
checkReady();
|
||||
return channel;
|
||||
}
|
||||
|
||||
ThrowableSubject assertThatServerRootCause() {
|
||||
checkReady();
|
||||
return assertThat(
|
||||
Throwables.getRootCause(
|
||||
assertThrows(ExecutionException.class, () -> echoHandler.getRequestFuture().get())));
|
||||
}
|
||||
|
||||
ThrowableSubject assertThatClientRootCause() {
|
||||
checkReady();
|
||||
return assertThat(
|
||||
Throwables.getRootCause(
|
||||
assertThrows(ExecutionException.class, () -> dumpHandler.getResponseFuture().get())));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void after() {
|
||||
Future<?> unusedFuture = eventLoopGroup.shutdownGracefully();
|
||||
}
|
||||
|
||||
/**
|
||||
* A handler that echoes back its inbound message. The message is also saved in a promise for
|
||||
* inspection later.
|
||||
*/
|
||||
public static class EchoHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private final CompletableFuture<String> requestFuture = new CompletableFuture<>();
|
||||
|
||||
public Future<String> getRequestFuture() {
|
||||
return requestFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
// In the test we only send messages of type ByteBuf.
|
||||
|
||||
assertThat(msg).isInstanceOf(ByteBuf.class);
|
||||
String request = ((ByteBuf) msg).toString(UTF_8);
|
||||
// After the message is written back to the client, fulfill the promise.
|
||||
ChannelFuture unusedFuture =
|
||||
ctx.writeAndFlush(msg).addListener(f -> requestFuture.complete(request));
|
||||
}
|
||||
|
||||
/** Saves any inbound error as the cause of the promise failure. */
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
ChannelFuture unusedFuture =
|
||||
ctx.channel().closeFuture().addListener(f -> requestFuture.completeExceptionally(cause));
|
||||
}
|
||||
}
|
||||
|
||||
/** A handler that dumps its inbound message to a promise that can be inspected later. */
|
||||
private static class DumpHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private final CompletableFuture<String> responseFuture = new CompletableFuture<>();
|
||||
|
||||
Future<String> getResponseFuture() {
|
||||
return responseFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
// In the test we only send messages of type ByteBuf.
|
||||
assertThat(msg).isInstanceOf(ByteBuf.class);
|
||||
String response = ((ByteBuf) msg).toString(UTF_8);
|
||||
// There is no more use of this message, we should release its reference count so that it
|
||||
// can be more effectively garbage collected by Netty.
|
||||
ReferenceCountUtil.release(msg);
|
||||
// Save the string in the promise and make it as complete.
|
||||
responseFuture.complete(response);
|
||||
}
|
||||
|
||||
/** Saves any inbound error into the failure cause of the promise. */
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
ctx.channel().closeFuture().addListener(f -> responseFuture.completeExceptionally(cause));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,207 +0,0 @@
|
|||
// Copyright 2019 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.monitoring.blackbox.handler;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.monitoring.blackbox.connection.ProbingAction.REMOTE_ADDRESS_KEY;
|
||||
import static google.registry.monitoring.blackbox.connection.Protocol.PROTOCOL_KEY;
|
||||
import static google.registry.monitoring.blackbox.handler.SslInitializerTestUtils.getKeyPair;
|
||||
import static google.registry.monitoring.blackbox.handler.SslInitializerTestUtils.setUpSslChannel;
|
||||
import static google.registry.monitoring.blackbox.handler.SslInitializerTestUtils.signKeyPair;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.monitoring.blackbox.connection.Protocol;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.channel.local.LocalAddress;
|
||||
import io.netty.channel.local.LocalChannel;
|
||||
import io.netty.handler.ssl.OpenSsl;
|
||||
import io.netty.handler.ssl.SniHandler;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
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 java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.CertPathBuilderException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import javax.net.ssl.SSLException;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameter;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SslClientInitializer}.
|
||||
*
|
||||
* <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(Parameterized.class)
|
||||
public class SslClientInitializerTest {
|
||||
|
||||
/** 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 Protocol PROTOCOL =
|
||||
Protocol.builder()
|
||||
.setName("ssl")
|
||||
.setPort(SSL_PORT)
|
||||
.setHandlerProviders(ImmutableList.of())
|
||||
.setPersistentConnection(false)
|
||||
.build();
|
||||
|
||||
@Rule public NettyRule nettyRule = new NettyRule();
|
||||
|
||||
@Parameter(0)
|
||||
public SslProvider sslProvider;
|
||||
/** Saves the SNI hostname received by the server, if sent by the client. */
|
||||
private String sniHostReceived;
|
||||
|
||||
// We do our best effort to test all available SSL providers.
|
||||
@Parameters(name = "{0}")
|
||||
public static SslProvider[] data() {
|
||||
return OpenSsl.isAvailable()
|
||||
? new SslProvider[] {SslProvider.JDK, SslProvider.OPENSSL}
|
||||
: new SslProvider[] {SslProvider.JDK};
|
||||
}
|
||||
|
||||
private ChannelHandler getServerHandler(PrivateKey privateKey, X509Certificate certificate)
|
||||
throws Exception {
|
||||
SslContext sslContext = SslContextBuilder.forServer(privateKey, certificate).build();
|
||||
return new SniHandler(
|
||||
hostname -> {
|
||||
sniHostReceived = hostname;
|
||||
return sslContext;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess_swappedInitializerWithSslHandler() throws Exception {
|
||||
SslClientInitializer<EmbeddedChannel> sslClientInitializer =
|
||||
new SslClientInitializer<>(sslProvider);
|
||||
EmbeddedChannel channel = new EmbeddedChannel();
|
||||
channel.attr(PROTOCOL_KEY).set(PROTOCOL);
|
||||
channel.attr(REMOTE_ADDRESS_KEY).set(SSL_HOST);
|
||||
ChannelPipeline pipeline = channel.pipeline();
|
||||
pipeline.addLast(sslClientInitializer);
|
||||
ChannelHandler firstHandler = pipeline.first();
|
||||
assertThat(firstHandler.getClass()).isEqualTo(SslHandler.class);
|
||||
SslHandler sslHandler = (SslHandler) firstHandler;
|
||||
assertThat(sslHandler.engine().getPeerHost()).isEqualTo(SSL_HOST);
|
||||
assertThat(sslHandler.engine().getPeerPort()).isEqualTo(SSL_PORT);
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess_protocolAttributeNotSet() {
|
||||
SslClientInitializer<EmbeddedChannel> sslClientInitializer =
|
||||
new SslClientInitializer<>(sslProvider);
|
||||
EmbeddedChannel channel = new EmbeddedChannel();
|
||||
ChannelPipeline pipeline = channel.pipeline();
|
||||
pipeline.addLast(sslClientInitializer);
|
||||
// Channel initializer swallows error thrown, and closes the connection.
|
||||
assertThat(channel.isActive()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailure_defaultTrustManager_rejectSelfSignedCert() throws Exception {
|
||||
SelfSignedCertificate ssc = new SelfSignedCertificate(SSL_HOST);
|
||||
LocalAddress localAddress =
|
||||
new LocalAddress("DEFAULT_TRUST_MANAGER_REJECT_SELF_SIGNED_CERT_" + sslProvider);
|
||||
nettyRule.setUpServer(localAddress, getServerHandler(ssc.key(), ssc.cert()));
|
||||
SslClientInitializer<LocalChannel> sslClientInitializer =
|
||||
new SslClientInitializer<>(sslProvider);
|
||||
|
||||
nettyRule.setUpClient(localAddress, PROTOCOL, SSL_HOST, sslClientInitializer);
|
||||
// The connection is now terminated, both the client side and the server side should get
|
||||
// exceptions.
|
||||
nettyRule.assertThatClientRootCause().isInstanceOf(CertPathBuilderException.class);
|
||||
nettyRule.assertThatServerRootCause().isInstanceOf(SSLException.class);
|
||||
assertThat(nettyRule.getChannel().isActive()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccess_customTrustManager_acceptCertSignedByTrustedCa() throws Exception {
|
||||
LocalAddress localAddress =
|
||||
new LocalAddress("CUSTOM_TRUST_MANAGER_ACCEPT_CERT_SIGNED_BY_TRUSTED_CA_" + sslProvider);
|
||||
|
||||
// Generate a new key pair.
|
||||
KeyPair keyPair = getKeyPair();
|
||||
|
||||
// Generate a self signed certificate, and use it to sign the key pair.
|
||||
SelfSignedCertificate ssc = new SelfSignedCertificate();
|
||||
X509Certificate cert = signKeyPair(ssc, keyPair, SSL_HOST);
|
||||
|
||||
// Set up the server to use the signed cert and private key to perform handshake;
|
||||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
nettyRule.setUpServer(localAddress, getServerHandler(privateKey, cert));
|
||||
|
||||
// Set up the client to trust the self signed cert used to sign the cert that server provides.
|
||||
SslClientInitializer<LocalChannel> sslClientInitializer =
|
||||
new SslClientInitializer<>(sslProvider, new X509Certificate[] {ssc.cert()});
|
||||
|
||||
nettyRule.setUpClient(localAddress, PROTOCOL, SSL_HOST, sslClientInitializer);
|
||||
|
||||
setUpSslChannel(nettyRule.getChannel(), cert);
|
||||
nettyRule.assertThatMessagesWork();
|
||||
|
||||
// Verify that the SNI extension is sent during handshake.
|
||||
assertThat(sniHostReceived).isEqualTo(SSL_HOST);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailure_customTrustManager_wrongHostnameInCertificate() throws Exception {
|
||||
LocalAddress localAddress =
|
||||
new LocalAddress("CUSTOM_TRUST_MANAGER_WRONG_HOSTNAME_" + sslProvider);
|
||||
|
||||
// Generate a new key pair.
|
||||
KeyPair keyPair = getKeyPair();
|
||||
|
||||
// Generate a self signed certificate, and use it to sign the key pair.
|
||||
SelfSignedCertificate ssc = new SelfSignedCertificate();
|
||||
X509Certificate cert = signKeyPair(ssc, keyPair, "wrong.com");
|
||||
|
||||
// Set up the server to use the signed cert and private key to perform handshake;
|
||||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
nettyRule.setUpServer(localAddress, getServerHandler(privateKey, cert));
|
||||
|
||||
// Set up the client to trust the self signed cert used to sign the cert that server provides.
|
||||
SslClientInitializer<LocalChannel> sslClientInitializer =
|
||||
new SslClientInitializer<>(sslProvider, new X509Certificate[] {ssc.cert()});
|
||||
|
||||
nettyRule.setUpClient(localAddress, PROTOCOL, SSL_HOST, sslClientInitializer);
|
||||
|
||||
// When the client rejects the server cert due to wrong hostname, both the client and server
|
||||
// should throw exceptions.
|
||||
nettyRule.assertThatClientRootCause().isInstanceOf(CertificateException.class);
|
||||
nettyRule.assertThatClientRootCause().hasMessageThat().contains(SSL_HOST);
|
||||
nettyRule.assertThatServerRootCause().isInstanceOf(SSLException.class);
|
||||
assertThat(nettyRule.getChannel().isActive()).isFalse();
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
// Copyright 2019 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.monitoring.blackbox.handler;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import io.netty.handler.ssl.util.SelfSignedCertificate;
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||
import org.bouncycastle.cert.X509CertificateHolder;
|
||||
import org.bouncycastle.cert.X509v3CertificateBuilder;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||
import org.bouncycastle.crypto.util.PrivateKeyFactory;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.operator.ContentSigner;
|
||||
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
|
||||
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
|
||||
import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
|
||||
|
||||
/** Utility class that provides methods used by {@link SslClientInitializerTest} */
|
||||
public class SslInitializerTestUtils {
|
||||
|
||||
static {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
public static KeyPair getKeyPair() throws Exception {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
|
||||
keyPairGenerator.initialize(2048, new SecureRandom());
|
||||
return keyPairGenerator.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs the given key pair with the given self signed certificate.
|
||||
*
|
||||
* @return signed public key (of the key pair) certificate
|
||||
*/
|
||||
public static X509Certificate signKeyPair(
|
||||
SelfSignedCertificate ssc, KeyPair keyPair, String hostname) throws Exception {
|
||||
X500Name subjectDnName = new X500Name("CN=" + hostname);
|
||||
BigInteger serialNumber = (BigInteger.valueOf(System.currentTimeMillis()));
|
||||
X500Name issuerDnName = new X500Name(ssc.cert().getIssuerDN().getName());
|
||||
Date from = Date.from(Instant.now().minus(Duration.ofDays(1)));
|
||||
Date to = Date.from(Instant.now().plus(Duration.ofDays(1)));
|
||||
SubjectPublicKeyInfo subPubKeyInfo =
|
||||
SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
|
||||
AlgorithmIdentifier sigAlgId =
|
||||
new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WithRSAEncryption");
|
||||
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
|
||||
|
||||
ContentSigner sigGen =
|
||||
new BcRSAContentSignerBuilder(sigAlgId, digAlgId)
|
||||
.build(PrivateKeyFactory.createKey(ssc.key().getEncoded()));
|
||||
X509v3CertificateBuilder v3CertGen =
|
||||
new X509v3CertificateBuilder(
|
||||
issuerDnName, serialNumber, from, to, subjectDnName, subPubKeyInfo);
|
||||
|
||||
X509CertificateHolder certificateHolder = v3CertGen.build(sigGen);
|
||||
return new JcaX509CertificateConverter().setProvider("BC").getCertificate(certificateHolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies tha the SSL channel is established as expected, and also sends a message to the server
|
||||
* and verifies if it is echoed back correctly.
|
||||
*
|
||||
* @param certs The certificate that the server should provide.
|
||||
* @return The SSL session in current channel, can be used for further validation.
|
||||
*/
|
||||
static SSLSession setUpSslChannel(Channel channel, X509Certificate... certs) throws Exception {
|
||||
SslHandler sslHandler = channel.pipeline().get(SslHandler.class);
|
||||
// Wait till the handshake is complete.
|
||||
sslHandler.handshakeFuture().get();
|
||||
|
||||
assertThat(channel.isActive()).isTrue();
|
||||
assertThat(sslHandler.handshakeFuture().isSuccess()).isTrue();
|
||||
assertThat(sslHandler.engine().getSession().isValid()).isTrue();
|
||||
assertThat(sslHandler.engine().getSession().getPeerCertificates())
|
||||
.asList()
|
||||
.containsExactlyElementsIn(certs);
|
||||
// Returns the SSL session for further assertion.
|
||||
return sslHandler.engine().getSession();
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue