package io.netty.handler.ssl;
import io.netty.buffer.ByteBufAllocator;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.io.File;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.crypto.NoSuchPaddingException;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSessionContext;
import static io.netty.handler.ssl.SslUtils.DEFAULT_CIPHER_SUITES;
import static io.netty.handler.ssl.SslUtils.addIfSupported;
import static io.netty.handler.ssl.SslUtils.useFallbackCiphersIfDefaultIsEmpty;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
public class JdkSslContext extends SslContext {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(JdkSslContext.class);
static final String PROTOCOL = "TLS";
private static final String[] DEFAULT_PROTOCOLS;
private static final List<String> DEFAULT_CIPHERS;
private static final Set<String> SUPPORTED_CIPHERS;
static {
SSLContext context;
int i;
try {
context = SSLContext.getInstance(PROTOCOL);
context.init(null, null, null);
} catch (Exception e) {
throw new Error("failed to initialize the default SSL context", e);
}
SSLEngine engine = context.createSSLEngine();
final String[] supportedProtocols = engine.getSupportedProtocols();
Set<String> supportedProtocolsSet = new HashSet<String>(supportedProtocols.length);
for (i = 0; i < supportedProtocols.length; ++i) {
supportedProtocolsSet.add(supportedProtocols[i]);
}
List<String> protocols = new ArrayList<String>();
addIfSupported(
supportedProtocolsSet, protocols,
"TLSv1.2", "TLSv1.1", "TLSv1");
if (!protocols.isEmpty()) {
DEFAULT_PROTOCOLS = protocols.toArray(new String[protocols.size()]);
} else {
DEFAULT_PROTOCOLS = engine.getEnabledProtocols();
}
final String[] supportedCiphers = engine.getSupportedCipherSuites();
SUPPORTED_CIPHERS = new HashSet<String>(supportedCiphers.length);
for (i = 0; i < supportedCiphers.length; ++i) {
String supportedCipher = supportedCiphers[i];
SUPPORTED_CIPHERS.add(supportedCipher);
if (supportedCipher.startsWith("SSL_")) {
final String tlsPrefixedCipherName = "TLS_" + supportedCipher.substring("SSL_".length());
try {
engine.setEnabledCipherSuites(new String[]{tlsPrefixedCipherName});
SUPPORTED_CIPHERS.add(tlsPrefixedCipherName);
} catch (IllegalArgumentException ignored) {
}
}
}
List<String> ciphers = new ArrayList<String>();
addIfSupported(SUPPORTED_CIPHERS, ciphers, DEFAULT_CIPHER_SUITES);
useFallbackCiphersIfDefaultIsEmpty(ciphers, engine.getEnabledCipherSuites());
DEFAULT_CIPHERS = Collections.unmodifiableList(ciphers);
if (logger.isDebugEnabled()) {
logger.debug("Default protocols (JDK): {} ", Arrays.asList(DEFAULT_PROTOCOLS));
logger.debug("Default cipher suites (JDK): {}", DEFAULT_CIPHERS);
}
}
private final String[] protocols;
private final String[] cipherSuites;
private final List<String> unmodifiableCipherSuites;
@SuppressWarnings("deprecation")
private final JdkApplicationProtocolNegotiator apn;
private final ClientAuth clientAuth;
private final SSLContext sslContext;
private final boolean isClient;
public JdkSslContext(SSLContext sslContext, boolean isClient,
ClientAuth clientAuth) {
this(sslContext, isClient, null, IdentityCipherSuiteFilter.INSTANCE,
JdkDefaultApplicationProtocolNegotiator.INSTANCE, clientAuth, null, false);
}
public JdkSslContext(SSLContext sslContext, boolean isClient, Iterable<String> ciphers,
CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
ClientAuth clientAuth) {
this(sslContext, isClient, ciphers, cipherFilter, toNegotiator(apn, !isClient), clientAuth, null, false);
}
@SuppressWarnings("deprecation")
JdkSslContext(SSLContext sslContext, boolean isClient, Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
JdkApplicationProtocolNegotiator apn, ClientAuth clientAuth, String[] protocols, boolean startTls) {
super(startTls);
this.apn = checkNotNull(apn, "apn");
this.clientAuth = checkNotNull(clientAuth, "clientAuth");
cipherSuites = checkNotNull(cipherFilter, "cipherFilter").filterCipherSuites(
ciphers, DEFAULT_CIPHERS, SUPPORTED_CIPHERS);
this.protocols = protocols == null ? DEFAULT_PROTOCOLS : protocols;
unmodifiableCipherSuites = Collections.unmodifiableList(Arrays.asList(cipherSuites));
this.sslContext = checkNotNull(sslContext, "sslContext");
this.isClient = isClient;
}
public final SSLContext context() {
return sslContext;
}
@Override
public final boolean isClient() {
return isClient;
}
@Override
public final SSLSessionContext sessionContext() {
if (isServer()) {
return context().getServerSessionContext();
} else {
return context().getClientSessionContext();
}
}
@Override
public final List<String> cipherSuites() {
return unmodifiableCipherSuites;
}
@Override
public final long sessionCacheSize() {
return sessionContext().getSessionCacheSize();
}
@Override
public final long sessionTimeout() {
return sessionContext().getSessionTimeout();
}
@Override
public final SSLEngine newEngine(ByteBufAllocator alloc) {
return configureAndWrapEngine(context().createSSLEngine(), alloc);
}
@Override
public final SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) {
return configureAndWrapEngine(context().createSSLEngine(peerHost, peerPort), alloc);
}
@SuppressWarnings("deprecation")
private SSLEngine configureAndWrapEngine(SSLEngine engine, ByteBufAllocator alloc) {
engine.setEnabledCipherSuites(cipherSuites);
engine.setEnabledProtocols(protocols);
engine.setUseClientMode(isClient());
if (isServer()) {
switch (clientAuth) {
case OPTIONAL:
engine.setWantClientAuth(true);
break;
case REQUIRE:
engine.setNeedClientAuth(true);
break;
case NONE:
break;
default:
throw new Error("Unknown auth " + clientAuth);
}
}
JdkApplicationProtocolNegotiator.SslEngineWrapperFactory factory = apn.wrapperFactory();
if (factory instanceof JdkApplicationProtocolNegotiator.AllocatorAwareSslEngineWrapperFactory) {
return ((JdkApplicationProtocolNegotiator.AllocatorAwareSslEngineWrapperFactory) factory)
.wrapSslEngine(engine, alloc, apn, isServer());
}
return factory.wrapSslEngine(engine, apn, isServer());
}
@Override
public final JdkApplicationProtocolNegotiator applicationProtocolNegotiator() {
return apn;
}
@SuppressWarnings("deprecation")
static JdkApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig config, boolean isServer) {
if (config == null) {
return JdkDefaultApplicationProtocolNegotiator.INSTANCE;
}
switch(config.protocol()) {
case NONE:
return JdkDefaultApplicationProtocolNegotiator.INSTANCE;
case ALPN:
if (isServer) {
switch(config.selectorFailureBehavior()) {
case FATAL_ALERT:
return new JdkAlpnApplicationProtocolNegotiator(true, config.supportedProtocols());
case NO_ADVERTISE:
return new JdkAlpnApplicationProtocolNegotiator(false, config.supportedProtocols());
default:
throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
.append(config.selectorFailureBehavior()).append(" failure behavior").toString());
}
} else {
switch(config.selectedListenerFailureBehavior()) {
case ACCEPT:
return new JdkAlpnApplicationProtocolNegotiator(false, config.supportedProtocols());
case FATAL_ALERT:
return new JdkAlpnApplicationProtocolNegotiator(true, config.supportedProtocols());
default:
throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
.append(config.selectedListenerFailureBehavior()).append(" failure behavior").toString());
}
}
case NPN:
if (isServer) {
switch(config.selectedListenerFailureBehavior()) {
case ACCEPT:
return new JdkNpnApplicationProtocolNegotiator(false, config.supportedProtocols());
case FATAL_ALERT:
return new JdkNpnApplicationProtocolNegotiator(true, config.supportedProtocols());
default:
throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
.append(config.selectedListenerFailureBehavior()).append(" failure behavior").toString());
}
} else {
switch(config.selectorFailureBehavior()) {
case FATAL_ALERT:
return new JdkNpnApplicationProtocolNegotiator(true, config.supportedProtocols());
case NO_ADVERTISE:
return new JdkNpnApplicationProtocolNegotiator(false, config.supportedProtocols());
default:
throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
.append(config.selectorFailureBehavior()).append(" failure behavior").toString());
}
}
default:
throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
.append(config.protocol()).append(" protocol").toString());
}
}
@Deprecated
protected static KeyManagerFactory buildKeyManagerFactory(File certChainFile, File keyFile, String keyPassword,
KeyManagerFactory kmf)
throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeySpecException, InvalidAlgorithmParameterException,
CertificateException, KeyException, IOException {
String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
if (algorithm == null) {
algorithm = "SunX509";
}
return buildKeyManagerFactory(certChainFile, algorithm, keyFile, keyPassword, kmf);
}
@Deprecated
protected static KeyManagerFactory buildKeyManagerFactory(File certChainFile,
String keyAlgorithm, File keyFile, String keyPassword, KeyManagerFactory kmf)
throws KeyStoreException, NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeySpecException, InvalidAlgorithmParameterException, IOException,
CertificateException, KeyException, UnrecoverableKeyException {
return buildKeyManagerFactory(toX509Certificates(certChainFile), keyAlgorithm,
toPrivateKey(keyFile, keyPassword), keyPassword, kmf);
}
}