Java tutorial
/* * 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.buffer.ByteBufAllocator; import io.netty.util.ReferenceCountUtil; 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.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.Provider; 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.LinkedHashSet; 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; /** * An {@link SslContext} which uses JDK's SSL/TLS implementation. */ 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 List<String> DEFAULT_CIPHERS_NON_TLSV13; private static final Set<String> SUPPORTED_CIPHERS; private static final Set<String> SUPPORTED_CIPHERS_NON_TLSV13; private static final Provider DEFAULT_PROVIDER; static { SSLContext context; try { context = SSLContext.getInstance(PROTOCOL); context.init(null, null, null); } catch (Exception e) { throw new Error("failed to initialize the default SSL context", e); } DEFAULT_PROVIDER = context.getProvider(); SSLEngine engine = context.createSSLEngine(); DEFAULT_PROTOCOLS = defaultProtocols(context, engine); SUPPORTED_CIPHERS = Collections.unmodifiableSet(supportedCiphers(engine)); DEFAULT_CIPHERS = Collections.unmodifiableList(defaultCiphers(engine, SUPPORTED_CIPHERS)); List<String> ciphersNonTLSv13 = new ArrayList<String>(DEFAULT_CIPHERS); ciphersNonTLSv13.removeAll(Arrays.asList(SslUtils.DEFAULT_TLSV13_CIPHER_SUITES)); DEFAULT_CIPHERS_NON_TLSV13 = Collections.unmodifiableList(ciphersNonTLSv13); Set<String> suppertedCiphersNonTLSv13 = new LinkedHashSet<String>(SUPPORTED_CIPHERS); suppertedCiphersNonTLSv13.removeAll(Arrays.asList(SslUtils.DEFAULT_TLSV13_CIPHER_SUITES)); SUPPORTED_CIPHERS_NON_TLSV13 = Collections.unmodifiableSet(suppertedCiphersNonTLSv13); if (logger.isDebugEnabled()) { logger.debug("Default protocols (JDK): {} ", Arrays.asList(DEFAULT_PROTOCOLS)); logger.debug("Default cipher suites (JDK): {}", DEFAULT_CIPHERS); } } private static String[] defaultProtocols(SSLContext context, SSLEngine engine) { // Choose the sensible default list of protocols that respects JDK flags, eg. jdk.tls.client.protocols final String[] supportedProtocols = context.getDefaultSSLParameters().getProtocols(); Set<String> supportedProtocolsSet = new HashSet<String>(supportedProtocols.length); Collections.addAll(supportedProtocolsSet, supportedProtocols); List<String> protocols = new ArrayList<String>(); addIfSupported(supportedProtocolsSet, protocols, // Do not include TLSv1.3 for now by default. SslUtils.PROTOCOL_TLS_V1_2, SslUtils.PROTOCOL_TLS_V1_1, SslUtils.PROTOCOL_TLS_V1); if (!protocols.isEmpty()) { return protocols.toArray(new String[0]); } return engine.getEnabledProtocols(); } private static Set<String> supportedCiphers(SSLEngine engine) { // Choose the sensible default list of cipher suites. final String[] supportedCiphers = engine.getSupportedCipherSuites(); Set<String> supportedCiphersSet = new LinkedHashSet<String>(supportedCiphers.length); for (int i = 0; i < supportedCiphers.length; ++i) { String supportedCipher = supportedCiphers[i]; supportedCiphersSet.add(supportedCipher); // IBM's J9 JVM utilizes a custom naming scheme for ciphers and only returns ciphers with the "SSL_" // prefix instead of the "TLS_" prefix (as defined in the JSSE cipher suite names [1]). According to IBM's // documentation [2] the "SSL_" prefix is "interchangeable" with the "TLS_" prefix. // See the IBM forum discussion [3] and issue on IBM's JVM [4] for more details. //[1] http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#ciphersuites //[2] https://www.ibm.com/support/knowledgecenter/en/SSYKE2_8.0.0/com.ibm.java.security.component.80.doc/ // security-component/jsse2Docs/ciphersuites.html //[3] https://www.ibm.com/developerworks/community/forums/html/topic?id=9b5a56a9-fa46-4031-b33b-df91e28d77c2 //[4] https://www.ibm.com/developerworks/rfe/execute?use_case=viewRfe&CR_ID=71770 if (supportedCipher.startsWith("SSL_")) { final String tlsPrefixedCipherName = "TLS_" + supportedCipher.substring("SSL_".length()); try { engine.setEnabledCipherSuites(new String[] { tlsPrefixedCipherName }); supportedCiphersSet.add(tlsPrefixedCipherName); } catch (IllegalArgumentException ignored) { // The cipher is not supported ... move on to the next cipher. } } } return supportedCiphersSet; } private static List<String> defaultCiphers(SSLEngine engine, Set<String> supportedCiphers) { List<String> ciphers = new ArrayList<String>(); addIfSupported(supportedCiphers, ciphers, DEFAULT_CIPHER_SUITES); useFallbackCiphersIfDefaultIsEmpty(ciphers, engine.getEnabledCipherSuites()); return ciphers; } private static boolean isTlsV13Supported(String[] protocols) { for (String protocol : protocols) { if (SslUtils.PROTOCOL_TLS_V1_3.equals(protocol)) { return true; } } return false; } 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; /** * Creates a new {@link JdkSslContext} from a pre-configured {@link SSLContext}. * * @param sslContext the {@link SSLContext} to use. * @param isClient {@code true} if this context should create {@link SSLEngine}s for client-side usage. * @param clientAuth the {@link ClientAuth} to use. This will only be used when {@param isClient} is {@code false}. * @deprecated Use {@link #JdkSslContext(SSLContext, boolean, Iterable, CipherSuiteFilter, * ApplicationProtocolConfig, ClientAuth, String[], boolean)} */ @Deprecated public JdkSslContext(SSLContext sslContext, boolean isClient, ClientAuth clientAuth) { this(sslContext, isClient, null, IdentityCipherSuiteFilter.INSTANCE, JdkDefaultApplicationProtocolNegotiator.INSTANCE, clientAuth, null, false); } /** * Creates a new {@link JdkSslContext} from a pre-configured {@link SSLContext}. * * @param sslContext the {@link SSLContext} to use. * @param isClient {@code true} if this context should create {@link SSLEngine}s for client-side usage. * @param ciphers the ciphers to use or {@code null} if the standard should be used. * @param cipherFilter the filter to use. * @param apn the {@link ApplicationProtocolConfig} to use. * @param clientAuth the {@link ClientAuth} to use. This will only be used when {@param isClient} is {@code false}. * @deprecated Use {@link #JdkSslContext(SSLContext, boolean, Iterable, CipherSuiteFilter, * ApplicationProtocolConfig, ClientAuth, String[], boolean)} */ @Deprecated public JdkSslContext(SSLContext sslContext, boolean isClient, Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, ClientAuth clientAuth) { this(sslContext, isClient, ciphers, cipherFilter, apn, clientAuth, null, false); } /** * Creates a new {@link JdkSslContext} from a pre-configured {@link SSLContext}. * * @param sslContext the {@link SSLContext} to use. * @param isClient {@code true} if this context should create {@link SSLEngine}s for client-side usage. * @param ciphers the ciphers to use or {@code null} if the standard should be used. * @param cipherFilter the filter to use. * @param apn the {@link ApplicationProtocolConfig} to use. * @param clientAuth the {@link ClientAuth} to use. This will only be used when {@param isClient} is {@code false}. * @param protocols the protocols to enable, or {@code null} to enable the default protocols. * @param startTls {@code true} if the first write request shouldn't be encrypted */ public JdkSslContext(SSLContext sslContext, boolean isClient, Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn, ClientAuth clientAuth, String[] protocols, boolean startTls) { this(sslContext, isClient, ciphers, cipherFilter, toNegotiator(apn, !isClient), clientAuth, protocols == null ? null : protocols.clone(), startTls); } @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"); this.sslContext = checkNotNull(sslContext, "sslContext"); final List<String> defaultCiphers; final Set<String> supportedCiphers; if (DEFAULT_PROVIDER.equals(sslContext.getProvider())) { this.protocols = protocols == null ? DEFAULT_PROTOCOLS : protocols; if (isTlsV13Supported(this.protocols)) { supportedCiphers = SUPPORTED_CIPHERS; defaultCiphers = DEFAULT_CIPHERS; } else { // TLSv1.3 is not supported, ensure we do not include any TLSv1.3 ciphersuite. supportedCiphers = SUPPORTED_CIPHERS_NON_TLSV13; defaultCiphers = DEFAULT_CIPHERS_NON_TLSV13; } } else { // This is a different Provider then the one used by the JDK by default so we can not just assume // the same protocols and ciphers are supported. For example even if Java11+ is used Conscrypt will // not support TLSv1.3 and the TLSv1.3 ciphersuites. SSLEngine engine = sslContext.createSSLEngine(); try { if (protocols == null) { this.protocols = defaultProtocols(sslContext, engine); } else { this.protocols = protocols; } supportedCiphers = supportedCiphers(engine); defaultCiphers = defaultCiphers(engine, supportedCiphers); if (!isTlsV13Supported(this.protocols)) { // TLSv1.3 is not supported, ensure we do not include any TLSv1.3 ciphersuite. for (String cipher : SslUtils.DEFAULT_TLSV13_CIPHER_SUITES) { supportedCiphers.remove(cipher); defaultCiphers.remove(cipher); } } } finally { ReferenceCountUtil.release(engine); } } cipherSuites = checkNotNull(cipherFilter, "cipherFilter").filterCipherSuites(ciphers, defaultCiphers, supportedCiphers); unmodifiableCipherSuites = Collections.unmodifiableList(Arrays.asList(cipherSuites)); this.isClient = isClient; } /** * Returns the JDK {@link SSLContext} object held by this context. */ public final SSLContext context() { return sslContext; } @Override public final boolean isClient() { return isClient; } /** * Returns the JDK {@link SSLSessionContext} object held by this context. */ @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; // exhaustive cases 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; } /** * Translate a {@link ApplicationProtocolConfig} object to a {@link JdkApplicationProtocolNegotiator} object. * @param config The configuration which defines the translation * @param isServer {@code true} if a server {@code false} otherwise. * @return The results of the translation */ @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()); } } /** * Build a {@link KeyManagerFactory} based upon a key file, key file password, and a certificate chain. * @param certChainFile an X.509 certificate chain file in PEM format * @param keyFile a PKCS#8 private key file in PEM format * @param keyPassword the password of the {@code keyFile}. * {@code null} if it's not password-protected. * @param kmf The existing {@link KeyManagerFactory} that will be used if not {@code null} * @param keyStore the {@link KeyStore} that should be used in the {@link KeyManagerFactory} * @return A {@link KeyManagerFactory} based upon a key file, key file password, and a certificate chain. */ static KeyManagerFactory buildKeyManagerFactory(File certChainFile, File keyFile, String keyPassword, KeyManagerFactory kmf, String keyStore) 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, keyStore); } /** * Build a {@link KeyManagerFactory} based upon a key file, key file password, and a certificate chain. * @param certChainFile an X.509 certificate chain file in PEM format * @param keyFile a PKCS#8 private key file in PEM format * @param keyPassword the password of the {@code keyFile}. * {@code null} if it's not password-protected. * @param kmf The existing {@link KeyManagerFactory} that will be used if not {@code null} * @return A {@link KeyManagerFactory} based upon a key file, key file password, and a certificate chain. * @deprecated will be removed. */ @Deprecated protected static KeyManagerFactory buildKeyManagerFactory(File certChainFile, File keyFile, String keyPassword, KeyManagerFactory kmf) throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException, InvalidAlgorithmParameterException, CertificateException, KeyException, IOException { return buildKeyManagerFactory(certChainFile, keyFile, keyPassword, kmf, KeyStore.getDefaultType()); } /** * Build a {@link KeyManagerFactory} based upon a key algorithm, key file, key file password, * and a certificate chain. * @param certChainFile an X.509 certificate chain file in PEM format * @param keyAlgorithm the standard name of the requested algorithm. See the Java Secure Socket Extension * Reference Guide for information about standard algorithm names. * @param keyFile a PKCS#8 private key file in PEM format * @param keyPassword the password of the {@code keyFile}. * {@code null} if it's not password-protected. * @param kmf The existing {@link KeyManagerFactory} that will be used if not {@code null} * @param keyStore the {@link KeyStore} that should be used in the {@link KeyManagerFactory} * @return A {@link KeyManagerFactory} based upon a key algorithm, key file, key file password, * and a certificate chain. */ static KeyManagerFactory buildKeyManagerFactory(File certChainFile, String keyAlgorithm, File keyFile, String keyPassword, KeyManagerFactory kmf, String keyStore) throws KeyStoreException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException, InvalidAlgorithmParameterException, IOException, CertificateException, KeyException, UnrecoverableKeyException { return buildKeyManagerFactory(toX509Certificates(certChainFile), keyAlgorithm, toPrivateKey(keyFile, keyPassword), keyPassword, kmf, keyStore); } /** * Build a {@link KeyManagerFactory} based upon a key algorithm, key file, key file password, * and a certificate chain. * @param certChainFile an buildKeyManagerFactory X.509 certificate chain file in PEM format * @param keyAlgorithm the standard name of the requested algorithm. See the Java Secure Socket Extension * Reference Guide for information about standard algorithm names. * @param keyFile a PKCS#8 private key file in PEM format * @param keyPassword the password of the {@code keyFile}. * {@code null} if it's not password-protected. * @param kmf The existing {@link KeyManagerFactory} that will be used if not {@code null} * @return A {@link KeyManagerFactory} based upon a key algorithm, key file, key file password, * and a certificate chain. * @deprecated will be removed. */ @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, KeyStore.getDefaultType()); } }