/*
* Copyright (C) 2012-2015 DataStax Inc.
*
* 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 com.datastax.driver.core;
import io.netty.handler.ssl.SslContextBuilder;
import org.testng.annotations.Test;
import javax.net.ssl.*;
import java.net.Socket;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import static com.datastax.driver.core.CreateCCM.TestMode.PER_METHOD;
import static io.netty.handler.ssl.SslProvider.OPENSSL;
@CreateCCM(PER_METHOD)
@CCMConfig(auth = false)
public class Jdk8SSLEncryptionTest extends SSLTestBase {
/**
* Validates that {@link RemoteEndpointAwareSSLOptions} implementations properly pass remote endpoint information
* to the underlying {@link SSLEngine} that is created. This is done by creating a custom {@link TrustManagerFactory}
* that inspects the peer information on the {@link SSLEngine} in
* {@link X509ExtendedTrustManager#checkServerTrusted(X509Certificate[], String, SSLEngine)} and throws a
* {@link CertificateException} if the peer host or port do not match.
* <p>
* This test is prefixed with 'Jdk8' so it only runs against JDK 8+ runtimes. This is required because
* X509ExtendedTrustManager was added in JDK 7. Technically this would also run against JDK 7, but for simplicity
* we only run it against 8+.
*
* @test_category connection:ssl
* @jira_ticket JAVA-1364
* @since 3.2.0
*/
@Test(groups = "short", dataProvider = "sslImplementation", dataProviderClass = SSLTestBase.class)
public void should_pass_peer_address_to_engine(SslImplementation sslImplementation) throws Exception {
String expectedPeerHost = TestUtils.IP_PREFIX + "1";
int expectedPeerPort = ccm().getBinaryPort();
EngineInspectingTrustManagerFactory tmf = new EngineInspectingTrustManagerFactory(expectedPeerHost, expectedPeerPort);
SSLOptions options = null;
switch (sslImplementation) {
case JDK:
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
SSLParameters parameters = sslContext.getDefaultSSLParameters();
parameters.setEndpointIdentificationAlgorithm("HTTPS");
options = RemoteEndpointAwareJdkSSLOptions.builder().withSSLContext(sslContext).build();
break;
case NETTY_OPENSSL:
SslContextBuilder builder = SslContextBuilder
.forClient()
.sslProvider(OPENSSL)
.trustManager(tmf);
options = new RemoteEndpointAwareNettySSLOptions(builder.build());
}
connectWithSSLOptions(options);
}
static class EngineInspectingTrustManagerFactory extends TrustManagerFactory {
private static final Provider provider = new Provider("", 0.0, "") {
};
final EngineInspectingTrustManagerFactorySpi spi;
EngineInspectingTrustManagerFactory(String expectedPeerHost, int expectedPeerPort) {
this(new EngineInspectingTrustManagerFactorySpi(expectedPeerHost, expectedPeerPort));
}
private EngineInspectingTrustManagerFactory(EngineInspectingTrustManagerFactorySpi spi) {
super(spi, provider, "EngineInspectingTrustManagerFactory");
this.spi = spi;
}
}
static class EngineInspectingTrustManagerFactorySpi extends TrustManagerFactorySpi {
String expectedPeerHost;
int expectedPeerPort;
private final TrustManager tm = new X509ExtendedTrustManager() {
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType, SSLEngine sslEngine) throws CertificateException {
// Capture peer address information and compare it to expectation.
String peerHost = sslEngine.getPeerHost();
int peerPort = sslEngine.getPeerPort();
if (peerHost == null || !peerHost.equals(expectedPeerHost)) {
throw new CertificateException(String.format("Expected SSLEngine.getPeerHost() (%s) to equal (%s)", peerHost, expectedPeerHost));
}
if (peerPort != expectedPeerPort) {
throw new CertificateException(String.format("Expected SSLEngine.getPeerPort() (%d) to equal (%d)", peerPort, expectedPeerPort));
}
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType, Socket socket) throws CertificateException {
// no op
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
// no op
}
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException {
// Since we are doing server trust only, this is a no op.
throw new UnsupportedOperationException("TrustManger is for establishing server trust only.");
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType, Socket socket) throws CertificateException {
// Since we are doing server trust only, this is a no op.
throw new UnsupportedOperationException("TrustManger is for establishing server trust only.");
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException {
// Since we are doing server trust only, this is a no op.
throw new UnsupportedOperationException("TrustManger is for establishing server trust only.");
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
EngineInspectingTrustManagerFactorySpi(String expectedPeerHost, int expectedPeerPort) {
this.expectedPeerHost = expectedPeerHost;
this.expectedPeerPort = expectedPeerPort;
}
@Override
protected void engineInit(KeyStore keyStore) throws KeyStoreException {
// no op
}
@Override
protected void engineInit(ManagerFactoryParameters managerFactoryParameters) throws InvalidAlgorithmParameterException {
// no op
}
@Override
protected TrustManager[] engineGetTrustManagers() {
return new TrustManager[]{tm};
}
}
}