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.ByteBuf;
20  import io.netty.buffer.ByteBufAllocator;
21  import io.netty.buffer.ByteBufInputStream;
22  import io.netty.util.internal.EmptyArrays;
23  import io.netty.util.internal.logging.InternalLogger;
24  import io.netty.util.internal.logging.InternalLoggerFactory;
25  
26  import javax.crypto.NoSuchPaddingException;
27  import javax.net.ssl.KeyManagerFactory;
28  import javax.net.ssl.SSLContext;
29  import javax.net.ssl.SSLEngine;
30  import javax.net.ssl.SSLSessionContext;
31  import javax.net.ssl.TrustManagerFactory;
32  import javax.security.auth.x500.X500Principal;
33  import java.io.File;
34  import java.io.IOException;
35  import java.security.InvalidAlgorithmParameterException;
36  import java.security.KeyException;
37  import java.security.KeyFactory;
38  import java.security.KeyStore;
39  import java.security.KeyStoreException;
40  import java.security.NoSuchAlgorithmException;
41  import java.security.PrivateKey;
42  import java.security.Security;
43  import java.security.UnrecoverableKeyException;
44  import java.security.cert.Certificate;
45  import java.security.cert.CertificateException;
46  import java.security.cert.CertificateFactory;
47  import java.security.cert.X509Certificate;
48  import java.security.spec.InvalidKeySpecException;
49  import java.security.spec.PKCS8EncodedKeySpec;
50  import java.util.ArrayList;
51  import java.util.Arrays;
52  import java.util.Collections;
53  import java.util.HashSet;
54  import java.util.List;
55  import java.util.Set;
56  
57  import static io.netty.util.internal.ObjectUtil.*;
58  
59  /**
60   * An {@link SslContext} which uses JDK's SSL/TLS implementation.
61   */
62  public abstract class JdkSslContext extends SslContext {
63  
64      private static final InternalLogger logger = InternalLoggerFactory.getInstance(JdkSslContext.class);
65  
66      static final String PROTOCOL = "TLS";
67      static final String[] PROTOCOLS;
68      static final List<String> DEFAULT_CIPHERS;
69      static final Set<String> SUPPORTED_CIPHERS;
70  
71      static {
72          SSLContext context;
73          int i;
74          try {
75              context = SSLContext.getInstance(PROTOCOL);
76              context.init(null, null, null);
77          } catch (Exception e) {
78              throw new Error("failed to initialize the default SSL context", e);
79          }
80  
81          SSLEngine engine = context.createSSLEngine();
82  
83          // Choose the sensible default list of protocols.
84          final String[] supportedProtocols = engine.getSupportedProtocols();
85          Set<String> supportedProtocolsSet = new HashSet<String>(supportedProtocols.length);
86          for (i = 0; i < supportedProtocols.length; ++i) {
87              supportedProtocolsSet.add(supportedProtocols[i]);
88          }
89          List<String> protocols = new ArrayList<String>();
90          addIfSupported(
91                  supportedProtocolsSet, protocols,
92                  "TLSv1.2", "TLSv1.1", "TLSv1");
93  
94          if (!protocols.isEmpty()) {
95              PROTOCOLS = protocols.toArray(new String[protocols.size()]);
96          } else {
97              PROTOCOLS = engine.getEnabledProtocols();
98          }
99  
100         // Choose the sensible default list of cipher suites.
101         final String[] supportedCiphers = engine.getSupportedCipherSuites();
102         SUPPORTED_CIPHERS = new HashSet<String>(supportedCiphers.length);
103         for (i = 0; i < supportedCiphers.length; ++i) {
104             SUPPORTED_CIPHERS.add(supportedCiphers[i]);
105         }
106         List<String> ciphers = new ArrayList<String>();
107         addIfSupported(
108                 SUPPORTED_CIPHERS, ciphers,
109                 // XXX: Make sure to sync this list with OpenSslEngineFactory.
110                 // GCM (Galois/Counter Mode) requires JDK 8.
111                 "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
112                 "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
113                 // AES256 requires JCE unlimited strength jurisdiction policy files.
114                 "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
115                 // GCM (Galois/Counter Mode) requires JDK 8.
116                 "TLS_RSA_WITH_AES_128_GCM_SHA256",
117                 "TLS_RSA_WITH_AES_128_CBC_SHA",
118                 // AES256 requires JCE unlimited strength jurisdiction policy files.
119                 "TLS_RSA_WITH_AES_256_CBC_SHA",
120                 "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
121                 "SSL_RSA_WITH_RC4_128_SHA");
122 
123         if (!ciphers.isEmpty()) {
124             DEFAULT_CIPHERS = Collections.unmodifiableList(ciphers);
125         } else {
126             // Use the default from JDK as fallback.
127             DEFAULT_CIPHERS = Collections.unmodifiableList(Arrays.asList(engine.getEnabledCipherSuites()));
128         }
129 
130         if (logger.isDebugEnabled()) {
131             logger.debug("Default protocols (JDK): {} ", Arrays.asList(PROTOCOLS));
132             logger.debug("Default cipher suites (JDK): {}", DEFAULT_CIPHERS);
133         }
134     }
135 
136     private static void addIfSupported(Set<String> supported, List<String> enabled, String... names) {
137         for (String n: names) {
138             if (supported.contains(n)) {
139                 enabled.add(n);
140             }
141         }
142     }
143 
144     private final String[] cipherSuites;
145     private final List<String> unmodifiableCipherSuites;
146     private final JdkApplicationProtocolNegotiator apn;
147 
148     JdkSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter, ApplicationProtocolConfig config,
149             boolean isServer) {
150         this(ciphers, cipherFilter, toNegotiator(config, isServer));
151     }
152 
153     JdkSslContext(Iterable<String> ciphers, CipherSuiteFilter cipherFilter, JdkApplicationProtocolNegotiator apn) {
154         this.apn = checkNotNull(apn, "apn");
155         cipherSuites = checkNotNull(cipherFilter, "cipherFilter").filterCipherSuites(
156                 ciphers, DEFAULT_CIPHERS, SUPPORTED_CIPHERS);
157         unmodifiableCipherSuites = Collections.unmodifiableList(Arrays.asList(cipherSuites));
158     }
159 
160     /**
161      * Returns the JDK {@link SSLContext} object held by this context.
162      */
163     public abstract SSLContext context();
164 
165     /**
166      * Returns the JDK {@link SSLSessionContext} object held by this context.
167      */
168     public final SSLSessionContext sessionContext() {
169         if (isServer()) {
170             return context().getServerSessionContext();
171         } else {
172             return context().getClientSessionContext();
173         }
174     }
175 
176     @Override
177     public final List<String> cipherSuites() {
178         return unmodifiableCipherSuites;
179     }
180 
181     @Override
182     public final long sessionCacheSize() {
183         return sessionContext().getSessionCacheSize();
184     }
185 
186     @Override
187     public final long sessionTimeout() {
188         return sessionContext().getSessionTimeout();
189     }
190 
191     @Override
192     public final SSLEngine newEngine(ByteBufAllocator alloc) {
193         SSLEngine engine = context().createSSLEngine();
194         engine.setEnabledCipherSuites(cipherSuites);
195         engine.setEnabledProtocols(PROTOCOLS);
196         engine.setUseClientMode(isClient());
197         return wrapEngine(engine);
198     }
199 
200     @Override
201     public final SSLEngine newEngine(ByteBufAllocator alloc, String peerHost, int peerPort) {
202         SSLEngine engine = context().createSSLEngine(peerHost, peerPort);
203         engine.setEnabledCipherSuites(cipherSuites);
204         engine.setEnabledProtocols(PROTOCOLS);
205         engine.setUseClientMode(isClient());
206         return wrapEngine(engine);
207     }
208 
209     private SSLEngine wrapEngine(SSLEngine engine) {
210         return apn.wrapperFactory().wrapSslEngine(engine, apn, isServer());
211     }
212 
213     @Override
214     public JdkApplicationProtocolNegotiator applicationProtocolNegotiator() {
215         return apn;
216     }
217 
218     /**
219      * Translate a {@link ApplicationProtocolConfig} object to a {@link JdkApplicationProtocolNegotiator} object.
220      * @param config The configuration which defines the translation
221      * @param isServer {@code true} if a server {@code false} otherwise.
222      * @return The results of the translation
223      */
224     static JdkApplicationProtocolNegotiator toNegotiator(ApplicationProtocolConfig config, boolean isServer) {
225         if (config == null) {
226             return JdkDefaultApplicationProtocolNegotiator.INSTANCE;
227         }
228 
229         switch(config.protocol()) {
230         case NONE:
231             return JdkDefaultApplicationProtocolNegotiator.INSTANCE;
232         case ALPN:
233             if (isServer) {
234                 switch(config.selectorFailureBehavior()) {
235                 case FATAL_ALERT:
236                     return new JdkAlpnApplicationProtocolNegotiator(true, config.supportedProtocols());
237                 case NO_ADVERTISE:
238                     return new JdkAlpnApplicationProtocolNegotiator(false, config.supportedProtocols());
239                 default:
240                     throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
241                     .append(config.selectorFailureBehavior()).append(" failure behavior").toString());
242                 }
243             } else {
244                 switch(config.selectedListenerFailureBehavior()) {
245                 case ACCEPT:
246                     return new JdkAlpnApplicationProtocolNegotiator(false, config.supportedProtocols());
247                 case FATAL_ALERT:
248                     return new JdkAlpnApplicationProtocolNegotiator(true, config.supportedProtocols());
249                 default:
250                     throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
251                     .append(config.selectedListenerFailureBehavior()).append(" failure behavior").toString());
252                 }
253             }
254         case NPN:
255             if (isServer) {
256                 switch(config.selectedListenerFailureBehavior()) {
257                 case ACCEPT:
258                     return new JdkNpnApplicationProtocolNegotiator(false, config.supportedProtocols());
259                 case FATAL_ALERT:
260                     return new JdkNpnApplicationProtocolNegotiator(true, config.supportedProtocols());
261                 default:
262                     throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
263                     .append(config.selectedListenerFailureBehavior()).append(" failure behavior").toString());
264                 }
265             } else {
266                 switch(config.selectorFailureBehavior()) {
267                 case FATAL_ALERT:
268                     return new JdkNpnApplicationProtocolNegotiator(true, config.supportedProtocols());
269                 case NO_ADVERTISE:
270                     return new JdkNpnApplicationProtocolNegotiator(false, config.supportedProtocols());
271                 default:
272                     throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
273                     .append(config.selectorFailureBehavior()).append(" failure behavior").toString());
274                 }
275             }
276         default:
277             throw new UnsupportedOperationException(new StringBuilder("JDK provider does not support ")
278             .append(config.protocol()).append(" protocol").toString());
279         }
280     }
281 
282     /**
283      * Build a {@link KeyManagerFactory} based upon a key file, key file password, and a certificate chain.
284      * @param certChainFile a X.509 certificate chain file in PEM format
285      * @param keyFile a PKCS#8 private key file in PEM format
286      * @param keyPassword the password of the {@code keyFile}.
287      *                    {@code null} if it's not password-protected.
288      * @param kmf The existing {@link KeyManagerFactory} that will be used if not {@code null}
289      * @return A {@link KeyManagerFactory} based upon a key file, key file password, and a certificate chain.
290      */
291     protected static KeyManagerFactory buildKeyManagerFactory(File certChainFile, File keyFile, String keyPassword,
292             KeyManagerFactory kmf)
293                     throws UnrecoverableKeyException, KeyStoreException, NoSuchAlgorithmException,
294                     NoSuchPaddingException, InvalidKeySpecException, InvalidAlgorithmParameterException,
295                     CertificateException, KeyException, IOException {
296         String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm");
297         if (algorithm == null) {
298             algorithm = "SunX509";
299         }
300         return buildKeyManagerFactory(certChainFile, algorithm, keyFile, keyPassword, kmf);
301     }
302 
303     /**
304      * Build a {@link KeyManagerFactory} based upon a key algorithm, key file, key file password,
305      * and a certificate chain.
306      * @param certChainFile a X.509 certificate chain file in PEM format
307      * @param keyAlgorithm the standard name of the requested algorithm. See the Java Secure Socket Extension
308      * Reference Guide for information about standard algorithm names.
309      * @param keyFile a PKCS#8 private key file in PEM format
310      * @param keyPassword the password of the {@code keyFile}.
311      *                    {@code null} if it's not password-protected.
312      * @param kmf The existing {@link KeyManagerFactory} that will be used if not {@code null}
313      * @return A {@link KeyManagerFactory} based upon a key algorithm, key file, key file password,
314      * and a certificate chain.
315      */
316     protected static KeyManagerFactory buildKeyManagerFactory(File certChainFile,
317             String keyAlgorithm, File keyFile, String keyPassword, KeyManagerFactory kmf)
318                     throws KeyStoreException, NoSuchAlgorithmException, NoSuchPaddingException,
319                     InvalidKeySpecException, InvalidAlgorithmParameterException, IOException,
320                     CertificateException, KeyException, UnrecoverableKeyException {
321         KeyStore ks = KeyStore.getInstance("JKS");
322         ks.load(null, null);
323         CertificateFactory cf = CertificateFactory.getInstance("X.509");
324         KeyFactory rsaKF = KeyFactory.getInstance("RSA");
325         KeyFactory dsaKF = KeyFactory.getInstance("DSA");
326 
327         ByteBuf encodedKeyBuf = PemReader.readPrivateKey(keyFile);
328         byte[] encodedKey = new byte[encodedKeyBuf.readableBytes()];
329         encodedKeyBuf.readBytes(encodedKey).release();
330 
331         char[] keyPasswordChars = keyPassword == null ? EmptyArrays.EMPTY_CHARS : keyPassword.toCharArray();
332         PKCS8EncodedKeySpec encodedKeySpec = generateKeySpec(keyPasswordChars, encodedKey);
333 
334         PrivateKey key;
335         try {
336             key = rsaKF.generatePrivate(encodedKeySpec);
337         } catch (InvalidKeySpecException ignore) {
338             key = dsaKF.generatePrivate(encodedKeySpec);
339         }
340 
341         List<Certificate> certChain = new ArrayList<Certificate>();
342         ByteBuf[] certs = PemReader.readCertificates(certChainFile);
343         try {
344             for (ByteBuf buf: certs) {
345                 certChain.add(cf.generateCertificate(new ByteBufInputStream(buf)));
346             }
347         } finally {
348             for (ByteBuf buf: certs) {
349                 buf.release();
350             }
351         }
352 
353         ks.setKeyEntry("key", key, keyPasswordChars, certChain.toArray(new Certificate[certChain.size()]));
354 
355         // Set up key manager factory to use our key store
356         if (kmf == null) {
357             kmf = KeyManagerFactory.getInstance(keyAlgorithm);
358         }
359         kmf.init(ks, keyPasswordChars);
360 
361         return kmf;
362     }
363 
364     /**
365      * Build a {@link TrustManagerFactory} from a certificate chain file.
366      * @param certChainFile The certificate file to build from.
367      * @param trustManagerFactory The existing {@link TrustManagerFactory} that will be used if not {@code null}.
368      * @return A {@link TrustManagerFactory} which contains the certificates in {@code certChainFile}
369      */
370     protected static TrustManagerFactory buildTrustManagerFactory(File certChainFile,
371             TrustManagerFactory trustManagerFactory)
372                     throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
373         KeyStore ks = KeyStore.getInstance("JKS");
374         ks.load(null, null);
375         CertificateFactory cf = CertificateFactory.getInstance("X.509");
376 
377         ByteBuf[] certs = PemReader.readCertificates(certChainFile);
378         try {
379             for (ByteBuf buf: certs) {
380                 X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteBufInputStream(buf));
381                 X500Principal principal = cert.getSubjectX500Principal();
382                 ks.setCertificateEntry(principal.getName("RFC2253"), cert);
383             }
384         } finally {
385             for (ByteBuf buf: certs) {
386                 buf.release();
387             }
388         }
389 
390         // Set up trust manager factory to use our key store.
391         if (trustManagerFactory == null) {
392             trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
393         }
394         trustManagerFactory.init(ks);
395 
396         return trustManagerFactory;
397     }
398 }