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.handler.ssl.util.SelfSignedCertificate;
21  import io.netty.internal.tcnative.Buffer;
22  import io.netty.internal.tcnative.Library;
23  import io.netty.internal.tcnative.SSL;
24  import io.netty.internal.tcnative.SSLContext;
25  import io.netty.util.ReferenceCountUtil;
26  import io.netty.util.ReferenceCounted;
27  import io.netty.util.internal.NativeLibraryLoader;
28  import io.netty.util.internal.PlatformDependent;
29  import io.netty.util.internal.SystemPropertyUtil;
30  import io.netty.util.internal.logging.InternalLogger;
31  import io.netty.util.internal.logging.InternalLoggerFactory;
32  
33  import java.security.AccessController;
34  import java.security.PrivilegedAction;
35  import java.util.ArrayList;
36  import java.util.Arrays;
37  import java.util.Collections;
38  import java.util.LinkedHashSet;
39  import java.util.List;
40  import java.util.Set;
41  
42  import static io.netty.handler.ssl.SslUtils.DEFAULT_CIPHER_SUITES;
43  import static io.netty.handler.ssl.SslUtils.addIfSupported;
44  import static io.netty.handler.ssl.SslUtils.useFallbackCiphersIfDefaultIsEmpty;
45  import static io.netty.handler.ssl.SslUtils.PROTOCOL_SSL_V2;
46  import static io.netty.handler.ssl.SslUtils.PROTOCOL_SSL_V2_HELLO;
47  import static io.netty.handler.ssl.SslUtils.PROTOCOL_SSL_V3;
48  import static io.netty.handler.ssl.SslUtils.PROTOCOL_TLS_V1;
49  import static io.netty.handler.ssl.SslUtils.PROTOCOL_TLS_V1_1;
50  import static io.netty.handler.ssl.SslUtils.PROTOCOL_TLS_V1_2;
51  
52  /**
53   * Tells if <a href="http://netty.io/wiki/forked-tomcat-native.html">{@code netty-tcnative}</a> and its OpenSSL support
54   * are available.
55   */
56  public final class OpenSsl {
57  
58      private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSsl.class);
59      private static final Throwable UNAVAILABILITY_CAUSE;
60  
61      static final List<String> DEFAULT_CIPHERS;
62      static final Set<String> AVAILABLE_CIPHER_SUITES;
63      private static final Set<String> AVAILABLE_OPENSSL_CIPHER_SUITES;
64      private static final Set<String> AVAILABLE_JAVA_CIPHER_SUITES;
65      private static final boolean SUPPORTS_KEYMANAGER_FACTORY;
66      private static final boolean SUPPORTS_HOSTNAME_VALIDATION;
67      private static final boolean USE_KEYMANAGER_FACTORY;
68      private static final boolean SUPPORTS_OCSP;
69  
70      static final Set<String> SUPPORTED_PROTOCOLS_SET;
71  
72      static {
73          Throwable cause = null;
74  
75          // Test if netty-tcnative is in the classpath first.
76          try {
77              Class.forName("io.netty.internal.tcnative.SSL", false, OpenSsl.class.getClassLoader());
78          } catch (ClassNotFoundException t) {
79              cause = t;
80              logger.debug(
81                      "netty-tcnative not in the classpath; " +
82                      OpenSslEngine.class.getSimpleName() + " will be unavailable.");
83          }
84  
85          // If in the classpath, try to load the native library and initialize netty-tcnative.
86          if (cause == null) {
87              try {
88                  // The JNI library was not already loaded. Load it now.
89                  loadTcNative();
90              } catch (Throwable t) {
91                  cause = t;
92                  logger.debug(
93                      "Failed to load netty-tcnative; " +
94                          OpenSslEngine.class.getSimpleName() + " will be unavailable, unless the " +
95                          "application has already loaded the symbols by some other means. " +
96                          "See http://netty.io/wiki/forked-tomcat-native.html for more information.", t);
97              }
98  
99              try {
100                 initializeTcNative();
101 
102                 // The library was initialized successfully. If loading the library failed above,
103                 // reset the cause now since it appears that the library was loaded by some other
104                 // means.
105                 cause = null;
106             } catch (Throwable t) {
107                 if (cause == null) {
108                     cause = t;
109                 }
110                 logger.debug(
111                     "Failed to initialize netty-tcnative; " +
112                         OpenSslEngine.class.getSimpleName() + " will be unavailable. " +
113                         "See http://netty.io/wiki/forked-tomcat-native.html for more information.", t);
114             }
115         }
116 
117         UNAVAILABILITY_CAUSE = cause;
118 
119         if (cause == null) {
120             logger.debug("netty-tcnative using native library: {}", SSL.versionString());
121 
122             final List<String> defaultCiphers = new ArrayList<String>();
123             final Set<String> availableOpenSslCipherSuites = new LinkedHashSet<String>(128);
124             boolean supportsKeyManagerFactory = false;
125             boolean useKeyManagerFactory = false;
126             boolean supportsHostNameValidation = false;
127             try {
128                 final long sslCtx = SSLContext.make(SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER);
129                 long certBio = 0;
130                 SelfSignedCertificate cert = null;
131                 try {
132                     SSLContext.setCipherSuite(sslCtx, "ALL");
133                     final long ssl = SSL.newSSL(sslCtx, true);
134                     try {
135                         for (String c: SSL.getCiphers(ssl)) {
136                             // Filter out bad input.
137                             if (c == null || c.isEmpty() || availableOpenSslCipherSuites.contains(c)) {
138                                 continue;
139                             }
140                             availableOpenSslCipherSuites.add(c);
141                         }
142 
143                         try {
144                             SSL.setHostNameValidation(ssl, 0, "netty.io");
145                             supportsHostNameValidation = true;
146                         } catch (Throwable ignore) {
147                             logger.debug("Hostname Verification not supported.");
148                         }
149                         try {
150                             cert = new SelfSignedCertificate();
151                             certBio = ReferenceCountedOpenSslContext.toBIO(cert.cert());
152                             SSL.setCertificateChainBio(ssl, certBio, false);
153                             supportsKeyManagerFactory = true;
154                             try {
155                                 useKeyManagerFactory = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
156                                     @Override
157                                     public Boolean run() {
158                                         return SystemPropertyUtil.getBoolean(
159                                                 "io.netty.handler.ssl.openssl.useKeyManagerFactory", true);
160                                     }
161                                 });
162                             } catch (Throwable ignore) {
163                                 logger.debug("Failed to get useKeyManagerFactory system property.");
164                             }
165                         } catch (Throwable ignore) {
166                             logger.debug("KeyManagerFactory not supported.");
167                         }
168                     } finally {
169                         SSL.freeSSL(ssl);
170                         if (certBio != 0) {
171                             SSL.freeBIO(certBio);
172                         }
173                         if (cert != null) {
174                             cert.delete();
175                         }
176                     }
177                 } finally {
178                     SSLContext.free(sslCtx);
179                 }
180             } catch (Exception e) {
181                 logger.warn("Failed to get the list of available OpenSSL cipher suites.", e);
182             }
183             AVAILABLE_OPENSSL_CIPHER_SUITES = Collections.unmodifiableSet(availableOpenSslCipherSuites);
184             final Set<String> availableJavaCipherSuites = new LinkedHashSet<String>(
185                     AVAILABLE_OPENSSL_CIPHER_SUITES.size() * 2);
186             for (String cipher: AVAILABLE_OPENSSL_CIPHER_SUITES) {
187                 // Included converted but also openssl cipher name
188                 availableJavaCipherSuites.add(CipherSuiteConverter.toJava(cipher, "TLS"));
189                 availableJavaCipherSuites.add(CipherSuiteConverter.toJava(cipher, "SSL"));
190             }
191 
192             addIfSupported(availableJavaCipherSuites, defaultCiphers, DEFAULT_CIPHER_SUITES);
193             useFallbackCiphersIfDefaultIsEmpty(defaultCiphers, availableJavaCipherSuites);
194             DEFAULT_CIPHERS = Collections.unmodifiableList(defaultCiphers);
195 
196             AVAILABLE_JAVA_CIPHER_SUITES = Collections.unmodifiableSet(availableJavaCipherSuites);
197 
198             final Set<String> availableCipherSuites = new LinkedHashSet<String>(
199                     AVAILABLE_OPENSSL_CIPHER_SUITES.size() + AVAILABLE_JAVA_CIPHER_SUITES.size());
200             availableCipherSuites.addAll(AVAILABLE_OPENSSL_CIPHER_SUITES);
201             availableCipherSuites.addAll(AVAILABLE_JAVA_CIPHER_SUITES);
202 
203             AVAILABLE_CIPHER_SUITES = availableCipherSuites;
204             SUPPORTS_KEYMANAGER_FACTORY = supportsKeyManagerFactory;
205             SUPPORTS_HOSTNAME_VALIDATION = supportsHostNameValidation;
206             USE_KEYMANAGER_FACTORY = useKeyManagerFactory;
207 
208             Set<String> protocols = new LinkedHashSet<String>(6);
209             // Seems like there is no way to explicitly disable SSLv2Hello in openssl so it is always enabled
210             protocols.add(PROTOCOL_SSL_V2_HELLO);
211             if (doesSupportProtocol(SSL.SSL_PROTOCOL_SSLV2)) {
212                 protocols.add(PROTOCOL_SSL_V2);
213             }
214             if (doesSupportProtocol(SSL.SSL_PROTOCOL_SSLV3)) {
215                 protocols.add(PROTOCOL_SSL_V3);
216             }
217             if (doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1)) {
218                 protocols.add(PROTOCOL_TLS_V1);
219             }
220             if (doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1_1)) {
221                 protocols.add(PROTOCOL_TLS_V1_1);
222             }
223             if (doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1_2)) {
224                 protocols.add(PROTOCOL_TLS_V1_2);
225             }
226 
227             SUPPORTED_PROTOCOLS_SET = Collections.unmodifiableSet(protocols);
228             SUPPORTS_OCSP = doesSupportOcsp();
229 
230             if (logger.isDebugEnabled()) {
231                 logger.debug("Supported protocols (OpenSSL): {} ", Arrays.asList(SUPPORTED_PROTOCOLS_SET));
232                 logger.debug("Default cipher suites (OpenSSL): {}", DEFAULT_CIPHERS);
233             }
234         } else {
235             DEFAULT_CIPHERS = Collections.emptyList();
236             AVAILABLE_OPENSSL_CIPHER_SUITES = Collections.emptySet();
237             AVAILABLE_JAVA_CIPHER_SUITES = Collections.emptySet();
238             AVAILABLE_CIPHER_SUITES = Collections.emptySet();
239             SUPPORTS_KEYMANAGER_FACTORY = false;
240             SUPPORTS_HOSTNAME_VALIDATION = false;
241             USE_KEYMANAGER_FACTORY = false;
242             SUPPORTED_PROTOCOLS_SET = Collections.emptySet();
243             SUPPORTS_OCSP = false;
244         }
245     }
246 
247     private static boolean doesSupportOcsp() {
248         boolean supportsOcsp = false;
249         if (version() >= 0x10002000L) {
250             long sslCtx = -1;
251             try {
252                 sslCtx = SSLContext.make(SSL.SSL_PROTOCOL_TLSV1_2, SSL.SSL_MODE_SERVER);
253                 SSLContext.enableOcsp(sslCtx, false);
254                 supportsOcsp = true;
255             } catch (Exception ignore) {
256                 // ignore
257             } finally {
258                 if (sslCtx != -1) {
259                     SSLContext.free(sslCtx);
260                 }
261             }
262         }
263         return supportsOcsp;
264     }
265     private static boolean doesSupportProtocol(int protocol) {
266         long sslCtx = -1;
267         try {
268             sslCtx = SSLContext.make(protocol, SSL.SSL_MODE_COMBINED);
269             return true;
270         } catch (Exception ignore) {
271             return false;
272         } finally {
273             if (sslCtx != -1) {
274                 SSLContext.free(sslCtx);
275             }
276         }
277     }
278 
279     /**
280      * Returns {@code true} if and only if
281      * <a href="http://netty.io/wiki/forked-tomcat-native.html">{@code netty-tcnative}</a> and its OpenSSL support
282      * are available.
283      */
284     public static boolean isAvailable() {
285         return UNAVAILABILITY_CAUSE == null;
286     }
287 
288     /**
289      * Returns {@code true} if the used version of openssl supports
290      * <a href="https://tools.ietf.org/html/rfc7301">ALPN</a>.
291      */
292     public static boolean isAlpnSupported() {
293         return version() >= 0x10002000L;
294     }
295 
296     /**
297      * Returns {@code true} if the used version of OpenSSL supports OCSP stapling.
298      */
299     public static boolean isOcspSupported() {
300       return SUPPORTS_OCSP;
301     }
302 
303     /**
304      * Returns the version of the used available OpenSSL library or {@code -1} if {@link #isAvailable()}
305      * returns {@code false}.
306      */
307     public static int version() {
308         return isAvailable() ? SSL.version() : -1;
309     }
310 
311     /**
312      * Returns the version string of the used available OpenSSL library or {@code null} if {@link #isAvailable()}
313      * returns {@code false}.
314      */
315     public static String versionString() {
316         return isAvailable() ? SSL.versionString() : null;
317     }
318 
319     /**
320      * Ensure that <a href="http://netty.io/wiki/forked-tomcat-native.html">{@code netty-tcnative}</a> and
321      * its OpenSSL support are available.
322      *
323      * @throws UnsatisfiedLinkError if unavailable
324      */
325     public static void ensureAvailability() {
326         if (UNAVAILABILITY_CAUSE != null) {
327             throw (Error) new UnsatisfiedLinkError(
328                     "failed to load the required native library").initCause(UNAVAILABILITY_CAUSE);
329         }
330     }
331 
332     /**
333      * Returns the cause of unavailability of
334      * <a href="http://netty.io/wiki/forked-tomcat-native.html">{@code netty-tcnative}</a> and its OpenSSL support.
335      *
336      * @return the cause if unavailable. {@code null} if available.
337      */
338     public static Throwable unavailabilityCause() {
339         return UNAVAILABILITY_CAUSE;
340     }
341 
342     /**
343      * @deprecated use {@link #availableOpenSslCipherSuites()}
344      */
345     @Deprecated
346     public static Set<String> availableCipherSuites() {
347         return availableOpenSslCipherSuites();
348     }
349 
350     /**
351      * Returns all the available OpenSSL cipher suites.
352      * Please note that the returned array may include the cipher suites that are insecure or non-functional.
353      */
354     public static Set<String> availableOpenSslCipherSuites() {
355         return AVAILABLE_OPENSSL_CIPHER_SUITES;
356     }
357 
358     /**
359      * Returns all the available cipher suites (Java-style).
360      * Please note that the returned array may include the cipher suites that are insecure or non-functional.
361      */
362     public static Set<String> availableJavaCipherSuites() {
363         return AVAILABLE_JAVA_CIPHER_SUITES;
364     }
365 
366     /**
367      * Returns {@code true} if and only if the specified cipher suite is available in OpenSSL.
368      * Both Java-style cipher suite and OpenSSL-style cipher suite are accepted.
369      */
370     public static boolean isCipherSuiteAvailable(String cipherSuite) {
371         String converted = CipherSuiteConverter.toOpenSsl(cipherSuite);
372         if (converted != null) {
373             cipherSuite = converted;
374         }
375         return AVAILABLE_OPENSSL_CIPHER_SUITES.contains(cipherSuite);
376     }
377 
378     /**
379      * Returns {@code true} if {@link javax.net.ssl.KeyManagerFactory} is supported when using OpenSSL.
380      */
381     public static boolean supportsKeyManagerFactory() {
382         return SUPPORTS_KEYMANAGER_FACTORY;
383     }
384 
385     /**
386      * Returns {@code true} if <a href="https://wiki.openssl.org/index.php/Hostname_validation">Hostname Validation</a>
387      * is supported when using OpenSSL.
388      */
389     public static boolean supportsHostnameValidation() {
390         return SUPPORTS_HOSTNAME_VALIDATION;
391     }
392 
393     static boolean useKeyManagerFactory() {
394         return USE_KEYMANAGER_FACTORY;
395     }
396 
397     static long memoryAddress(ByteBuf buf) {
398         assert buf.isDirect();
399         return buf.hasMemoryAddress() ? buf.memoryAddress() : Buffer.address(buf.nioBuffer());
400     }
401 
402     private OpenSsl() { }
403 
404     private static void loadTcNative() throws Exception {
405         String os = PlatformDependent.normalizedOs();
406         String arch = PlatformDependent.normalizedArch();
407 
408         Set<String> libNames = new LinkedHashSet<String>(4);
409         String staticLibName = "netty_tcnative";
410 
411         // First, try loading the platform-specific library. Platform-specific
412         // libraries will be available if using a tcnative uber jar.
413         libNames.add(staticLibName + "_" + os + '_' + arch);
414         if ("linux".equalsIgnoreCase(os)) {
415             // Fedora SSL lib so naming (libssl.so.10 vs libssl.so.1.0.0)..
416             libNames.add(staticLibName + "_" + os + '_' + arch + "_fedora");
417         }
418         libNames.add(staticLibName + "_" + arch);
419         libNames.add(staticLibName);
420 
421         NativeLibraryLoader.loadFirstAvailable(SSL.class.getClassLoader(),
422             libNames.toArray(new String[libNames.size()]));
423     }
424 
425     private static boolean initializeTcNative() throws Exception {
426         return Library.initialize();
427     }
428 
429     static void releaseIfNeeded(ReferenceCounted counted) {
430         if (counted.refCnt() > 0) {
431             ReferenceCountUtil.safeRelease(counted);
432         }
433     }
434 }