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