/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you 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 io.netty.handler.ssl;
import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
import io.netty.handler.ssl.JdkApplicationProtocolNegotiator.ProtocolSelector;
import io.netty.handler.ssl.JdkApplicationProtocolNegotiator.ProtocolSelectorFactory;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import java.security.Provider;
import java.util.ArrayList;
import java.util.Collection;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLHandshakeException;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeNoException;
@RunWith(Parameterized.class)
public class JdkSslEngineTest extends SSLEngineTest {
public enum ProviderType {
NPN_DEFAULT {
@Override
boolean isAvailable() {
return JettyNpnSslEngine.isAvailable();
}
@Override
Protocol protocol() {
return Protocol.NPN;
}
@Override
Provider provider() {
return null;
}
},
ALPN_DEFAULT {
@Override
boolean isAvailable() {
return JettyAlpnSslEngine.isAvailable();
}
@Override
Protocol protocol() {
return Protocol.ALPN;
}
@Override
Provider provider() {
// Use the default provider.
return null;
}
},
ALPN_CONSCRYPT {
private Provider provider;
@Override
boolean isAvailable() {
return ConscryptAlpnSslEngine.isAvailable();
}
@Override
Protocol protocol() {
return Protocol.ALPN;
}
@Override
Provider provider() {
try {
if (provider == null) {
provider = (Provider) Class.forName("org.conscrypt.OpenSSLProvider")
.getConstructor().newInstance();
}
return provider;
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
};
abstract boolean isAvailable();
abstract Protocol protocol();
abstract Provider provider();
final void activate(JdkSslEngineTest instance) {
// Typical code will not have to check this, but will get a initialization error on class load.
// Check in this test just in case we have multiple tests that just the class and we already ignored the
// initialization error.
if (!isAvailable()) {
throw tlsExtensionNotFound(protocol());
}
instance.provider = provider();
}
}
private static final String PREFERRED_APPLICATION_LEVEL_PROTOCOL = "my-protocol-http2";
private static final String FALLBACK_APPLICATION_LEVEL_PROTOCOL = "my-protocol-http1_1";
private static final String APPLICATION_LEVEL_PROTOCOL_NOT_COMPATIBLE = "my-protocol-FOO";
@Parameterized.Parameters(name = "{index}: providerType = {0}, bufferType = {1}")
public static Collection<Object[]> data() {
List<Object[]> params = new ArrayList<Object[]>();
for (ProviderType providerType : ProviderType.values()) {
for (BufferType bufferType : BufferType.values()) {
params.add(new Object[]{providerType, bufferType});
}
}
return params;
}
private final ProviderType providerType;
private Provider provider;
public JdkSslEngineTest(ProviderType providerType, BufferType bufferType) {
super(bufferType);
this.providerType = providerType;
}
@Test
public void testTlsExtension() throws Exception {
try {
providerType.activate(this);
ApplicationProtocolConfig apn = failingNegotiator(providerType.protocol(),
PREFERRED_APPLICATION_LEVEL_PROTOCOL);
setupHandlers(apn);
runTest();
} catch (SkipTestException e) {
// ALPN availability is dependent on the java version. If ALPN is not available because of
// java version incompatibility don't fail the test, but instead just skip the test
assumeNoException(e);
}
}
@Test
public void testTlsExtensionNoCompatibleProtocolsNoHandshakeFailure() throws Exception {
try {
providerType.activate(this);
ApplicationProtocolConfig clientApn = acceptingNegotiator(providerType.protocol(),
PREFERRED_APPLICATION_LEVEL_PROTOCOL);
ApplicationProtocolConfig serverApn = acceptingNegotiator(providerType.protocol(),
APPLICATION_LEVEL_PROTOCOL_NOT_COMPATIBLE);
setupHandlers(serverApn, clientApn);
runTest(null);
} catch (SkipTestException e) {
// ALPN availability is dependent on the java version. If ALPN is not available because of
// java version incompatibility don't fail the test, but instead just skip the test
assumeNoException(e);
}
}
@Test
public void testTlsExtensionNoCompatibleProtocolsClientHandshakeFailure() throws Exception {
try {
providerType.activate(this);
if (providerType == ProviderType.NPN_DEFAULT) {
ApplicationProtocolConfig clientApn = failingNegotiator(providerType.protocol(),
PREFERRED_APPLICATION_LEVEL_PROTOCOL);
ApplicationProtocolConfig serverApn = acceptingNegotiator(providerType.protocol(),
APPLICATION_LEVEL_PROTOCOL_NOT_COMPATIBLE);
setupHandlers(serverApn, clientApn);
assertTrue(clientLatch.await(2, TimeUnit.SECONDS));
assertTrue(clientException instanceof SSLHandshakeException);
} else {
// ALPN
SelfSignedCertificate ssc = new SelfSignedCertificate();
JdkApplicationProtocolNegotiator clientApn = new JdkAlpnApplicationProtocolNegotiator(true, true,
PREFERRED_APPLICATION_LEVEL_PROTOCOL);
JdkApplicationProtocolNegotiator serverApn = new JdkAlpnApplicationProtocolNegotiator(
new ProtocolSelectorFactory() {
@Override
public ProtocolSelector newSelector(SSLEngine engine, Set<String> supportedProtocols) {
return new ProtocolSelector() {
@Override
public void unsupported() {
}
@Override
public String select(List<String> protocols) {
return APPLICATION_LEVEL_PROTOCOL_NOT_COMPATIBLE;
}
};
}
}, JdkBaseApplicationProtocolNegotiator.FAIL_SELECTION_LISTENER_FACTORY,
APPLICATION_LEVEL_PROTOCOL_NOT_COMPATIBLE);
SslContext serverSslCtx = new JdkSslServerContext(providerType.provider(),
ssc.certificate(), ssc.privateKey(), null, null,
IdentityCipherSuiteFilter.INSTANCE, serverApn, 0, 0);
SslContext clientSslCtx = new JdkSslClientContext(providerType.provider(), null,
InsecureTrustManagerFactory.INSTANCE, null,
IdentityCipherSuiteFilter.INSTANCE, clientApn, 0, 0);
setupHandlers(serverSslCtx, clientSslCtx);
assertTrue(clientLatch.await(2, TimeUnit.SECONDS));
assertTrue(clientException instanceof SSLHandshakeException);
}
} catch (SkipTestException e) {
// ALPN availability is dependent on the java version. If ALPN is not available because of
// java version incompatibility don't fail the test, but instead just skip the test
assumeNoException(e);
}
}
@Test
public void testTlsExtensionNoCompatibleProtocolsServerHandshakeFailure() throws Exception {
try {
providerType.activate(this);
ApplicationProtocolConfig clientApn = acceptingNegotiator(providerType.protocol(),
PREFERRED_APPLICATION_LEVEL_PROTOCOL);
ApplicationProtocolConfig serverApn = failingNegotiator(providerType.protocol(),
APPLICATION_LEVEL_PROTOCOL_NOT_COMPATIBLE);
setupHandlers(serverApn, clientApn);
assertTrue(serverLatch.await(2, TimeUnit.SECONDS));
assertTrue(serverException instanceof SSLHandshakeException);
} catch (SkipTestException e) {
// ALPN availability is dependent on the java version. If ALPN is not available because of
// java version incompatibility don't fail the test, but instead just skip the test
assumeNoException(e);
}
}
@Test
public void testAlpnCompatibleProtocolsDifferentClientOrder() throws Exception {
try {
providerType.activate(this);
if (providerType == ProviderType.NPN_DEFAULT) {
// This test only applies to ALPN.
throw tlsExtensionNotFound(providerType.protocol());
}
// Even the preferred application protocol appears second in the client's list, it will be picked
// because it's the first one on server's list.
ApplicationProtocolConfig clientApn = acceptingNegotiator(Protocol.ALPN,
FALLBACK_APPLICATION_LEVEL_PROTOCOL, PREFERRED_APPLICATION_LEVEL_PROTOCOL);
ApplicationProtocolConfig serverApn = failingNegotiator(Protocol.ALPN,
PREFERRED_APPLICATION_LEVEL_PROTOCOL, FALLBACK_APPLICATION_LEVEL_PROTOCOL);
setupHandlers(serverApn, clientApn);
assertNull(serverException);
runTest(PREFERRED_APPLICATION_LEVEL_PROTOCOL);
} catch (SkipTestException e) {
// ALPN availability is dependent on the java version. If ALPN is not available because of
// java version incompatibility don't fail the test, but instead just skip the test
assumeNoException(e);
}
}
@Test
public void testEnablingAnAlreadyDisabledSslProtocol() throws Exception {
testEnablingAnAlreadyDisabledSslProtocol(new String[]{}, new String[]{PROTOCOL_TLS_V1_2});
}
@Ignore /* Does the JDK support a "max certificate chain length"? */
@Override
public void testMutualAuthValidClientCertChainTooLongFailOptionalClientAuth() throws Exception {
}
@Ignore /* Does the JDK support a "max certificate chain length"? */
@Override
public void testMutualAuthValidClientCertChainTooLongFailRequireClientAuth() throws Exception {
}
@Override
protected boolean mySetupMutualAuthServerIsValidException(Throwable cause) {
// TODO(scott): work around for a JDK issue. The exception should be SSLHandshakeException.
return super.mySetupMutualAuthServerIsValidException(cause) || causedBySSLException(cause);
}
private void runTest() throws Exception {
runTest(PREFERRED_APPLICATION_LEVEL_PROTOCOL);
}
@Override
protected SslProvider sslClientProvider() {
return SslProvider.JDK;
}
@Override
protected SslProvider sslServerProvider() {
return SslProvider.JDK;
}
@Override
protected Provider clientSslContextProvider() {
return provider;
}
@Override
protected Provider serverSslContextProvider() {
return provider;
}
private ApplicationProtocolConfig failingNegotiator(Protocol protocol,
String... supportedProtocols) {
return new ApplicationProtocolConfig(protocol,
SelectorFailureBehavior.FATAL_ALERT,
SelectedListenerFailureBehavior.FATAL_ALERT,
supportedProtocols);
}
private ApplicationProtocolConfig acceptingNegotiator(Protocol protocol,
String... supportedProtocols) {
return new ApplicationProtocolConfig(protocol,
SelectorFailureBehavior.NO_ADVERTISE,
SelectedListenerFailureBehavior.ACCEPT,
supportedProtocols);
}
private static SkipTestException tlsExtensionNotFound(Protocol protocol) {
throw new SkipTestException(protocol + " not on classpath");
}
private static final class SkipTestException extends RuntimeException {
SkipTestException(String message) {
super(message);
}
}
}