View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  
17  package io.netty.handler.ssl;
18  
19  import io.netty.buffer.ByteBufAllocator;
20  import io.netty.util.internal.EmptyArrays;
21  import io.netty.util.internal.logging.InternalLogger;
22  import io.netty.util.internal.logging.InternalLoggerFactory;
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.security.InvalidAlgorithmParameterException;
27  import java.security.KeyException;
28  import java.security.KeyStore;
29  import java.security.KeyStoreException;
30  import java.security.NoSuchAlgorithmException;
31  import java.security.PrivateKey;
32  import java.security.Security;
33  import java.security.UnrecoverableKeyException;
34  import java.security.cert.CertificateException;
35  import java.security.cert.X509Certificate;
36  import java.security.spec.InvalidKeySpecException;
37  import java.util.ArrayList;
38  import java.util.Arrays;
39  import java.util.Collections;
40  import java.util.HashSet;
41  import java.util.List;
42  import java.util.Set;
43  
44  import javax.crypto.NoSuchPaddingException;
45  import javax.net.ssl.KeyManagerFactory;
46  import javax.net.ssl.SSLContext;
47  import javax.net.ssl.SSLEngine;
48  import javax.net.ssl.SSLSessionContext;
49  
50  import static io.netty.handler.ssl.SslUtils.DEFAULT_CIPHER_SUITES;
51  import static io.netty.handler.ssl.SslUtils.addIfSupported;
52  import static io.netty.handler.ssl.SslUtils.useFallbackCiphersIfDefaultIsEmpty;
53  import static io.netty.util.internal.ObjectUtil.checkNotNull;
54  
55  /**
56   * An {@link SslContext} which uses JDK's SSL/TLS implementation.
57   */
58  public class JdkSslContext extends SslContext {
59  
60      private static final InternalLogger logger = InternalLoggerFactory.getInstance(JdkSslContext.class);
61  
62      static final String PROTOCOL = "TLS";
63      private static final String[] DEFAULT_PROTOCOLS;
64      private static final List<String> DEFAULT_CIPHERS;
65      private static final Set<String> SUPPORTED_CIPHERS;
66  
67      static {
68          SSLContext context;
69          int i;
70          try {
71              context = SSLContext.getInstance(PROTOCOL);
72              context.init(null, null, null);
73          } catch (Exception e) {
74              throw new Error("failed to initialize the default SSL context", e);
75          }
76  
77          SSLEngine engine = context.createSSLEngine();
78  
79          // Choose the sensible default list of protocols.
80          final String[] supportedProtocols = engine.getSupportedProtocols();
81          Set<String> supportedProtocolsSet = new HashSet<String>(supportedProtocols.length);
82          for (i = 0; i < supportedProtocols.length; ++i) {
83              supportedProtocolsSet.add(supportedProtocols[i]);
84          }
85          List<String> protocols = new ArrayList<String>();
86          addIfSupported(
87                  supportedProtocolsSet, protocols,
88                  "TLSv1.2", "TLSv1.1", "TLSv1");
89  
90          if (!protocols.isEmpty()) {
91              DEFAULT_PROTOCOLS = protocols.toArray(new String[protocols.size()]);
92          } else {
93              DEFAULT_PROTOCOLS = engine.getEnabledProtocols();
94          }
95  
96          // Choose the sensible default list of cipher suites.
97          final String[] supportedCiphers = engine.getSupportedCipherSuites();
98          SUPPORTED_CIPHERS = new HashSet<String>(supportedCiphers.length);
99          for (i = 0; i < supportedCiphers.length; ++i) {
100             String supportedCipher = supportedCiphers[i];
101             SUPPORTED_CIPHERS.add(supportedCipher);
102             // IBM's J9 JVM utilizes a custom naming scheme for ciphers and only returns ciphers with the "SSL_"
103             // prefix instead of the "TLS_" prefix (as defined in the JSSE cipher suite names [1]). According to IBM's
104             // documentation [2] the "SSL_" prefix is "interchangeable" with the "TLS_" prefix.
105             // See the IBM forum discussion [3] and issue on IBM's JVM [4] for more details.
106             //[1] http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#ciphersuites
107             //[2] https://www.ibm.com/support/knowledgecenter/en/SSYKE2_8.0.0/com.ibm.java.security.component.80.doc/
108             // security-component/jsse2Docs/ciphersuites.html
109             //[3] https://www.ibm.com/developerworks/community/forums/html/topic?id=9b5a56a9-fa46-4031-b33b-df91e28d77c2
110             //[4] https://www.ibm.com/developerworks/rfe/execute?use_case=viewRfe&CR_ID=71770
111             if (supportedCipher.startsWith("SSL_")) {
112                 final String tlsPrefixedCipherName = "TLS_" + supportedCipher.substring("SSL_".length());
113                 try {
114                     engine.setEnabledCipherSuites(new String[]{tlsPrefixedCipherName});
115                     SUPPORTED_CIPHERS.add(tlsPrefixedCipherName);
116                 } catch (IllegalArgumentException ignored) {
117                     // The cipher is not supported ... move on to the next cipher.
118                 }
119             }
120         }
121         List<String> ciphers = new ArrayList<String>();
122         addIfSupported(SUPPORTED_CIPHERS, ciphers, DEFAULT_CIPHER_SUITES);
123         useFallbackCiphersIfDefaultIsEmpty(ciphers, engine.getEnabledCipherSuites());
124         DEFAULT_CIPHERS = Collections.unmodifiableList(ciphers);
125 
126         if (logger.isDebugEnabled()) {
127             logger.debug("Default protocols (JDK): {} ", Arrays.asList(DEFAULT_PROTOCOLS));
128             logger.debug("Default cipher suites (JDK): {}", DEFAULT_CIPHERS);
129         }
130     }
131 
132     private final String[] protocols;
133     private final String[] cipherSuites;
134     private final List<String> unmodifiableCipherSuites;
135     @SuppressWarnings("deprecation")
136     private final JdkApplicationProtocolNegotiator apn;
137     private final ClientAuth clientAuth;
138     private final SSLContext sslContext;
139     private final boolean isClient;
140 
141     /**
142      * Creates a new {@link JdkSslContext} from a pre-configured {@link SSLContext}.
143      *
144      * @param sslContext the {@link SSLContext} to use.
145      * @param isClient {@code true} if this context should create {@link SSLEngine}s for client-side usage.
146      * @param clientAuth the {@link ClientAuth} to use. This will only be used when {@param isClient} is {@code false}.
147      */
148     public JdkSslContext(SSLContext sslContext, boolean isClient,
149                          ClientAuth clientAuth) {
150         this(sslContext, isClient, null, IdentityCipherSuiteFilter.INSTANCE,
151                 JdkDefaultApplicationProtocolNegotiator.INSTANCE, clientAuth, null, false);
152     }
153 
154     /**
155      * Creates a new {@link JdkSslContext} from a pre-configured {@link SSLContext}.
156      *
157      * @param sslContext the {@link SSLContext} to use.
158      * @param isClient {@code true} if this context should create {@link SSLEngine}s for client-side usage.
159      * @param ciphers the ciphers to use or {@code null} if the standard should be used.
160      * @param cipherFilter the filter to use.
161      * @param apn the {@link ApplicationProtocolConfig} to use.
162      * @param clientAuth the {@link ClientAuth} to use. This will only be used when {@param isClient} is {@code false}.
163      */
164     public JdkSslContext(SSLContext sslContext, boolean isClient, Iterable<String> ciphers,
165                          CipherSuiteFilter cipherFilter, ApplicationProtocolConfig apn,
166                          ClientAuth clientAuth) {
167         this(sslContext, isClient, ciphers, cipherFilter, toNegotiator(apn, !isClient), clientAuth, null, false);
168     }
169 
170     @SuppressWarnings("deprecation")
171     JdkSslContext(SSLContext sslContext, boolean isClient, Iterable<String> ciphers, CipherSuiteFilter cipherFilter,
172                   JdkApplicationProtocolNegotiator apn, ClientAuth clientAuth, String[] protocols, boolean startTls) {
173         super(startTls);
174         this.apn = checkNotNull(apn, "apn");
175         this.clientAuth = checkNotNull(clientAuth, "clientAuth");
176         cipherSuites = checkNotNull(cipherFilter, "cipherFilter").filterCipherSuites(
177                 ciphers, DEFAULT_CIPHERS, SUPPORTED_CIPHERS);
178         this.protocols = protocols == null ? DEFAULT_PROTOCOLS : protocols;
179         unmodifiableCipherSuites = Collections.unmodifiableList(Arrays.asList(cipherSuites));
180         this.sslContext = checkNotNull(sslContext, "sslContext");
181         this.isClient = isClient;
182     }
183 
184     /**
185      * Returns the JDK {@link SSLContext} object held by this context.
186      */
187     public final SSLContext context() {
188         return sslContext;
189     }
190 
191     @Override
192     public final boolean isClient() {
193         return isClient;
194     }
195 
196     /**
197      * Returns the JDK {@link SSLSessionContext} object held by this context.
198      */
199     @Override
200     public final SSLSessionContext sessionContext() {
201         if (isServer()) {
202             return context().getServerSessionContext();
203         } else {
204             return context().getClientSessionContext();
205         }
206     }
207 
208     @Override
209     public final List<String> cipherSuites() {
210         return unmodifiableCipherSuites;
211     }
212 
213     @Override
214     public final long sessionCacheSize() {
215         return sessionContext().getSessionCacheSize();
216     }
217 
218     @Override
219     public final long sessionTimeout() {
220         return sessionContext().getSessionTimeout();
221     }
222 
223     @Override
224     public final SSLEngine newEngine(ByteBufAllocator alloc) {
225         return configureAndWrapEngine(context().createSSLEngine(), alloc);
226     }
227 
228     @Override
229     public final SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) {
230         return configureAndWrapEngine(context().createSSLEngine(peerHost, peerPort), alloc);
231     }
232 
233     @SuppressWarnings("deprecation")
234     private SSLEngine configureAndWrapEngine(SSLEngine engine, ByteBufAllocator alloc) {
235         engine.setEnabledCipherSuites(cipherSuites);
236         engine.setEnabledProtocols(protocols);
237         engine.setUseClientMode(isClient());
238         if (isServer()) {
239             switch (clientAuth) {
240                 case OPTIONAL:
241                     engine.setWantClientAuth(true);
242                     break;
243                 case REQUIRE:
244                     engine.setNeedClientAuth(true);
245                     break;
246                 case NONE:
247                     break; // exhaustive cases
248                 default:
249                     throw new Error("Unknown auth " + clientAuth);
250             }
251         }
252         JdkApplicationProtocolNegotiator.SslEngineWrapperFactory factory = apn.wrapperFactory();
253         if (factory instanceof JdkApplicationProtocolNegotiator.AllocatorAwareSslEngineWrapperFactory) {
254             return ((JdkApplicationProtocolNegotiator.AllocatorAwareSslEngineWrapperFactory) factory)
255                     .wrapSslEngine(engine, alloc, apn, isServer());
256         }
257         return factory.wrapSslEngine(engine, apn, isServer());
258     }
259 
260     @Override
261     public final JdkApplicationProtocolNegotiator applicationProtocolNegotiator() {
262         return apn;
263     }
264 
265     /**
266      * Translate a {@link ApplicationProtocolConfig} object to a {@link JdkApplicationProtocolNegotiator} object.
267      * @param config The configuration which defines the translation
268      * @param isServer {@code true} if a server {@code false} otherwise.
269      * @return The results of the translation
270      */
271     @SuppressWarnings("deprecation")
272     static JdkApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig config, boolean isServer) {
273         if (config == null) {
274             return JdkDefaultApplicationProtocolNegotiator.INSTANCE;
275         }
276 
277         switch(config.protocol()) {
278         case NONE:
279             return JdkDefaultApplicationProtocolNegotiator.INSTANCE;
280         case ALPN:
281             if (isServer) {
282                 switch(config.selectorFailureBehavior()) {
283                 case FATAL_ALERT:
284                     return new JdkAlpnApplicationProtocolNegotiator(true, config.supportedProtocols());
285                 case NO_ADVERTISE:
286                     return new JdkAlpnApplicationProtocolNegotiator(false, config.supportedProtocols());
287                 default:
288                     throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
289                     .append(config.selectorFailureBehavior()).append(" failure behavior").toString());
290                 }
291             } else {
292                 switch(config.selectedListenerFailureBehavior()) {
293                 case ACCEPT:
294                     return new JdkAlpnApplicationProtocolNegotiator(false, config.supportedProtocols());
295                 case FATAL_ALERT:
296                     return new JdkAlpnApplicationProtocolNegotiator(true, config.supportedProtocols());
297                 default:
298                     throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
299                     .append(config.selectedListenerFailureBehavior()).append(" failure behavior").toString());
300                 }
301             }
302         case NPN:
303             if (isServer) {
304                 switch(config.selectedListenerFailureBehavior()) {
305                 case ACCEPT:
306                     return new JdkNpnApplicationProtocolNegotiator(false, config.supportedProtocols());
307                 case FATAL_ALERT:
308                     return new JdkNpnApplicationProtocolNegotiator(true, config.supportedProtocols());
309                 default:
310                     throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
311                     .append(config.selectedListenerFailureBehavior()).append(" failure behavior").toString());
312                 }
313             } else {
314                 switch(config.selectorFailureBehavior()) {
315                 case FATAL_ALERT:
316                     return new JdkNpnApplicationProtocolNegotiator(true, config.supportedProtocols());
317                 case NO_ADVERTISE:
318                     return new JdkNpnApplicationProtocolNegotiator(false, config.supportedProtocols());
319                 default:
320                     throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
321                     .append(config.selectorFailureBehavior()).append(" failure behavior").toString());
322                 }
323             }
324         default:
325             throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
326             .append(config.protocol()).append(" protocol").toString());
327         }
328     }
329 
330     /**
331      * Build a {@link KeyManagerFactory} based upon a key file, key file password, and a certificate chain.
332      * @param certChainFile a X.509 certificate chain file in PEM format
333      * @param keyFile a PKCS#8 private key file in PEM format
334      * @param keyPassword the password of the {@code keyFile}.
335      *                    {@code null} if it's not password-protected.
336      * @param kmf The existing {@link KeyManagerFactory} that will be used if not {@code null}
337      * @return A {@link KeyManagerFactory} based upon a key file, key file password, and a certificate chain.
338      * @deprecated will be removed.
339      */
340     @Deprecated
341     protected static KeyManagerFactory buildKeyManagerFactory(File certChainFile, File keyFile, String keyPassword,
342             KeyManagerFactory kmf)
343                     throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException,
344                     NoSuchPaddingException, InvalidKeySpecException, InvalidAlgorithmParameterException,
345                     CertificateException, KeyException, IOException {
346         String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
347         if (algorithm == null) {
348             algorithm = "SunX509";
349         }
350         return buildKeyManagerFactory(certChainFile, algorithm, keyFile, keyPassword, kmf);
351     }
352 
353     /**
354      * Build a {@link KeyManagerFactory} based upon a key algorithm, key file, key file password,
355      * and a certificate chain.
356      * @param certChainFile a X.509 certificate chain file in PEM format
357      * @param keyAlgorithm the standard name of the requested algorithm. See the Java Secure Socket Extension
358      * Reference Guide for information about standard algorithm names.
359      * @param keyFile a PKCS#8 private key file in PEM format
360      * @param keyPassword the password of the {@code keyFile}.
361      *                    {@code null} if it's not password-protected.
362      * @param kmf The existing {@link KeyManagerFactory} that will be used if not {@code null}
363      * @return A {@link KeyManagerFactory} based upon a key algorithm, key file, key file password,
364      * and a certificate chain.
365      * @deprecated will be removed.
366      */
367     @Deprecated
368     protected static KeyManagerFactory buildKeyManagerFactory(File certChainFile,
369             String keyAlgorithm, File keyFile, String keyPassword, KeyManagerFactory kmf)
370                     throws KeyStoreException, NoSuchAlgorithmException, NoSuchPaddingException,
371                     InvalidKeySpecException, InvalidAlgorithmParameterException, IOException,
372                     CertificateException, KeyException, UnrecoverableKeyException {
373         return buildKeyManagerFactory(toX509Certificates(certChainFile), keyAlgorithm,
374                                       toPrivateKey(keyFile, keyPassword), keyPassword, kmf);
375     }
376 }