/* * 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.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.UnpooledByteBufAllocator; import io.netty.internal.tcnative.Buffer; import io.netty.internal.tcnative.Library; import io.netty.internal.tcnative.SSL; import io.netty.internal.tcnative.SSLContext; import io.netty.util.CharsetUtil; import io.netty.util.ReferenceCountUtil; import io.netty.util.ReferenceCounted; import io.netty.util.internal.NativeLibraryLoader; import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.SystemPropertyUtil; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; import java.io.ByteArrayInputStream; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import static io.netty.handler.ssl.SslUtils.*; /** * Tells if {@code netty-tcnative} and its OpenSSL support * are available. */ public final class OpenSsl { private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSsl.class); private static final Throwable UNAVAILABILITY_CAUSE; static final List DEFAULT_CIPHERS; static final Set AVAILABLE_CIPHER_SUITES; private static final Set AVAILABLE_OPENSSL_CIPHER_SUITES; private static final Set AVAILABLE_JAVA_CIPHER_SUITES; private static final boolean SUPPORTS_KEYMANAGER_FACTORY; private static final boolean SUPPORTS_OCSP; private static final boolean TLSV13_SUPPORTED; private static final boolean IS_BORINGSSL; static final Set SUPPORTED_PROTOCOLS_SET; // self-signed certificate for netty.io and the matching private-key private static final String CERT = "-----BEGIN CERTIFICATE-----\n" + "MIICrjCCAZagAwIBAgIIdSvQPv1QAZQwDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAxMLZXhhbXBs\n" + "ZS5jb20wIBcNMTgwNDA2MjIwNjU5WhgPOTk5OTEyMzEyMzU5NTlaMBYxFDASBgNVBAMTC2V4YW1w\n" + "bGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAggbWsmDQ6zNzRZ5AW8E3eoGl\n" + "qWvOBDb5Fs1oBRrVQHuYmVAoaqwDzXYJ0LOwa293AgWEQ1jpcbZ2hpoYQzqEZBTLnFhMrhRFlH6K\n" + "bJND8Y33kZ/iSVBBDuGbdSbJShlM+4WwQ9IAso4MZ4vW3S1iv5fGGpLgbtXRmBf/RU8omN0Gijlv\n" + "WlLWHWijLN8xQtySFuBQ7ssW8RcKAary3pUm6UUQB+Co6lnfti0Tzag8PgjhAJq2Z3wbsGRnP2YS\n" + "vYoaK6qzmHXRYlp/PxrjBAZAmkLJs4YTm/XFF+fkeYx4i9zqHbyone5yerRibsHaXZWLnUL+rFoe\n" + "MdKvr0VS3sGmhQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQADQi441pKmXf9FvUV5EHU4v8nJT9Iq\n" + "yqwsKwXnr7AsUlDGHBD7jGrjAXnG5rGxuNKBQ35wRxJATKrUtyaquFUL6H8O6aGQehiFTk6zmPbe\n" + "12Gu44vqqTgIUxnv3JQJiox8S2hMxsSddpeCmSdvmalvD6WG4NthH6B9ZaBEiep1+0s0RUaBYn73\n" + "I7CCUaAtbjfR6pcJjrFk5ei7uwdQZFSJtkP2z8r7zfeANJddAKFlkaMWn7u+OIVuB4XPooWicObk\n" + "NAHFtP65bocUYnDpTVdiyvn8DdqyZ/EO8n1bBKBzuSLplk2msW4pdgaFgY7Vw/0wzcFXfUXmL1uy\n" + "G8sQD/wx\n" + "-----END CERTIFICATE-----"; private static final String KEY = "-----BEGIN PRIVATE KEY-----\n" + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCCBtayYNDrM3NFnkBbwTd6gaWp\n" + "a84ENvkWzWgFGtVAe5iZUChqrAPNdgnQs7Brb3cCBYRDWOlxtnaGmhhDOoRkFMucWEyuFEWUfops\n" + "k0PxjfeRn+JJUEEO4Zt1JslKGUz7hbBD0gCyjgxni9bdLWK/l8YakuBu1dGYF/9FTyiY3QaKOW9a\n" + "UtYdaKMs3zFC3JIW4FDuyxbxFwoBqvLelSbpRRAH4KjqWd+2LRPNqDw+COEAmrZnfBuwZGc/ZhK9\n" + "ihorqrOYddFiWn8/GuMEBkCaQsmzhhOb9cUX5+R5jHiL3OodvKid7nJ6tGJuwdpdlYudQv6sWh4x\n" + "0q+vRVLewaaFAgMBAAECggEAP8tPJvFtTxhNJAkCloHz0D0vpDHqQBMgntlkgayqmBqLwhyb18pR\n" + "i0qwgh7HHc7wWqOOQuSqlEnrWRrdcI6TSe8R/sErzfTQNoznKWIPYcI/hskk4sdnQ//Yn9/Jvnsv\n" + "U/BBjOTJxtD+sQbhAl80JcA3R+5sArURQkfzzHOL/YMqzAsn5hTzp7HZCxUqBk3KaHRxV7NefeOE\n" + "xlZuWSmxYWfbFIs4kx19/1t7h8CHQWezw+G60G2VBtSBBxDnhBWvqG6R/wpzJ3nEhPLLY9T+XIHe\n" + "ipzdMOOOUZorfIg7M+pyYPji+ZIZxIpY5OjrOzXHciAjRtr5Y7l99K1CG1LguQKBgQDrQfIMxxtZ\n" + "vxU/1cRmUV9l7pt5bjV5R6byXq178LxPKVYNjdZ840Q0/OpZEVqaT1xKVi35ohP1QfNjxPLlHD+K\n" + "iDAR9z6zkwjIrbwPCnb5kuXy4lpwPcmmmkva25fI7qlpHtbcuQdoBdCfr/KkKaUCMPyY89LCXgEw\n" + "5KTDj64UywKBgQCNfbO+eZLGzhiHhtNJurresCsIGWlInv322gL8CSfBMYl6eNfUTZvUDdFhPISL\n" + "UljKWzXDrjw0ujFSPR0XhUGtiq89H+HUTuPPYv25gVXO+HTgBFZEPl4PpA+BUsSVZy0NddneyqLk\n" + "42Wey9omY9Q8WsdNQS5cbUvy0uG6WFoX7wKBgQDZ1jpW8pa0x2bZsQsm4vo+3G5CRnZlUp+XlWt2\n" + "dDcp5dC0xD1zbs1dc0NcLeGDOTDv9FSl7hok42iHXXq8AygjEm/QcuwwQ1nC2HxmQP5holAiUs4D\n" + "WHM8PWs3wFYPzE459EBoKTxeaeP/uWAn+he8q7d5uWvSZlEcANs/6e77eQKBgD21Ar0hfFfj7mK8\n" + "9E0FeRZBsqK3omkfnhcYgZC11Xa2SgT1yvs2Va2n0RcdM5kncr3eBZav2GYOhhAdwyBM55XuE/sO\n" + "eokDVutNeuZ6d5fqV96TRaRBpvgfTvvRwxZ9hvKF4Vz+9wfn/JvCwANaKmegF6ejs7pvmF3whq2k\n" + "drZVAoGAX5YxQ5XMTD0QbMAl7/6qp6S58xNoVdfCkmkj1ZLKaHKIjS/benkKGlySVQVPexPfnkZx\n" + "p/Vv9yyphBoudiTBS9Uog66ueLYZqpgxlM/6OhYg86Gm3U2ycvMxYjBM1NFiyze21AqAhI+HX+Ot\n" + "mraV2/guSgDgZAhukRZzeQ2RucI=\n" + "-----END PRIVATE KEY-----"; static { Throwable cause = null; if (SystemPropertyUtil.getBoolean("io.netty.handler.ssl.noOpenSsl", false)) { cause = new UnsupportedOperationException( "OpenSSL was explicit disabled with -Dio.netty.handler.ssl.noOpenSsl=true"); logger.debug( "netty-tcnative explicit disabled; " + OpenSslEngine.class.getSimpleName() + " will be unavailable.", cause); } else { // Test if netty-tcnative is in the classpath first. try { Class.forName("io.netty.internal.tcnative.SSL", false, OpenSsl.class.getClassLoader()); } catch (ClassNotFoundException t) { cause = t; logger.debug( "netty-tcnative not in the classpath; " + OpenSslEngine.class.getSimpleName() + " will be unavailable."); } // If in the classpath, try to load the native library and initialize netty-tcnative. if (cause == null) { try { // The JNI library was not already loaded. Load it now. loadTcNative(); } catch (Throwable t) { cause = t; logger.debug( "Failed to load netty-tcnative; " + OpenSslEngine.class.getSimpleName() + " will be unavailable, unless the " + "application has already loaded the symbols by some other means. " + "See https://netty.io/wiki/forked-tomcat-native.html for more information.", t); } try { String engine = SystemPropertyUtil.get("io.netty.handler.ssl.openssl.engine", null); if (engine == null) { logger.debug("Initialize netty-tcnative using engine: 'default'"); } else { logger.debug("Initialize netty-tcnative using engine: '{}'", engine); } initializeTcNative(engine); // The library was initialized successfully. If loading the library failed above, // reset the cause now since it appears that the library was loaded by some other // means. cause = null; } catch (Throwable t) { if (cause == null) { cause = t; } logger.debug( "Failed to initialize netty-tcnative; " + OpenSslEngine.class.getSimpleName() + " will be unavailable. " + "See https://netty.io/wiki/forked-tomcat-native.html for more information.", t); } } } UNAVAILABILITY_CAUSE = cause; if (cause == null) { logger.debug("netty-tcnative using native library: {}", SSL.versionString()); final List defaultCiphers = new ArrayList<>(); final Set availableOpenSslCipherSuites = new LinkedHashSet<>(128); boolean supportsKeyManagerFactory = false; boolean useKeyManagerFactory = false; boolean tlsv13Supported = false; IS_BORINGSSL = "BoringSSL".equals(versionString()); try { final long sslCtx = SSLContext.make(SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER); long certBio = 0; long keyBio = 0; long cert = 0; long key = 0; try { try { StringBuilder tlsv13Ciphers = new StringBuilder(); for (String cipher: TLSV13_CIPHERS) { String converted = CipherSuiteConverter.toOpenSsl(cipher, IS_BORINGSSL); if (converted != null) { tlsv13Ciphers.append(converted).append(':'); } } if (tlsv13Ciphers.length() == 0) { tlsv13Supported = false; } else { tlsv13Ciphers.setLength(tlsv13Ciphers.length() - 1); SSLContext.setCipherSuite(sslCtx, tlsv13Ciphers.toString() , true); tlsv13Supported = true; } } catch (Exception ignore) { tlsv13Supported = false; } SSLContext.setCipherSuite(sslCtx, "ALL", false); final long ssl = SSL.newSSL(sslCtx, true); try { for (String c: SSL.getCiphers(ssl)) { // Filter out bad input. if (c == null || c.isEmpty() || availableOpenSslCipherSuites.contains(c) || // Filter out TLSv1.3 ciphers if not supported. !tlsv13Supported && isTLSv13Cipher(c)) { continue; } availableOpenSslCipherSuites.add(c); } if (IS_BORINGSSL) { // Currently BoringSSL does not include these when calling SSL.getCiphers() even when these // are supported. Collections.addAll(availableOpenSslCipherSuites, "TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384" , "TLS_CHACHA20_POLY1305_SHA256", "AEAD-AES128-GCM-SHA256", "AEAD-AES256-GCM-SHA384", "AEAD-CHACHA20-POLY1305-SHA256"); } PemEncoded privateKey = PemPrivateKey.valueOf(KEY.getBytes(CharsetUtil.US_ASCII)); try { // Let's check if we can set a callback, which may not work if the used OpenSSL version // is to old. SSLContext.setCertificateCallback(sslCtx, null); X509Certificate certificate = selfSignedCertificate(); certBio = ReferenceCountedOpenSslContext.toBIO(ByteBufAllocator.DEFAULT, certificate); cert = SSL.parseX509Chain(certBio); keyBio = ReferenceCountedOpenSslContext.toBIO( UnpooledByteBufAllocator.DEFAULT, privateKey.retain()); key = SSL.parsePrivateKey(keyBio, null); SSL.setKeyMaterial(ssl, cert, key); supportsKeyManagerFactory = true; } catch (Error ignore) { logger.debug("KeyManagerFactory not supported."); } finally { privateKey.release(); } } finally { SSL.freeSSL(ssl); if (certBio != 0) { SSL.freeBIO(certBio); } if (keyBio != 0) { SSL.freeBIO(keyBio); } if (cert != 0) { SSL.freeX509Chain(cert); } if (key != 0) { SSL.freePrivateKey(key); } } } finally { SSLContext.free(sslCtx); } } catch (Exception e) { logger.warn("Failed to get the list of available OpenSSL cipher suites.", e); } AVAILABLE_OPENSSL_CIPHER_SUITES = Collections.unmodifiableSet(availableOpenSslCipherSuites); final Set availableJavaCipherSuites = new LinkedHashSet<>( AVAILABLE_OPENSSL_CIPHER_SUITES.size() * 2); for (String cipher: AVAILABLE_OPENSSL_CIPHER_SUITES) { // Included converted but also openssl cipher name if (!isTLSv13Cipher(cipher)) { availableJavaCipherSuites.add(CipherSuiteConverter.toJava(cipher, "TLS")); availableJavaCipherSuites.add(CipherSuiteConverter.toJava(cipher, "SSL")); } else { // TLSv1.3 ciphers have the correct format. availableJavaCipherSuites.add(cipher); } } addIfSupported(availableJavaCipherSuites, defaultCiphers, DEFAULT_CIPHER_SUITES); addIfSupported(availableJavaCipherSuites, defaultCiphers, TLSV13_CIPHER_SUITES); useFallbackCiphersIfDefaultIsEmpty(defaultCiphers, availableJavaCipherSuites); DEFAULT_CIPHERS = Collections.unmodifiableList(defaultCiphers); AVAILABLE_JAVA_CIPHER_SUITES = Collections.unmodifiableSet(availableJavaCipherSuites); final Set availableCipherSuites = new LinkedHashSet<>( AVAILABLE_OPENSSL_CIPHER_SUITES.size() + AVAILABLE_JAVA_CIPHER_SUITES.size()); availableCipherSuites.addAll(AVAILABLE_OPENSSL_CIPHER_SUITES); availableCipherSuites.addAll(AVAILABLE_JAVA_CIPHER_SUITES); AVAILABLE_CIPHER_SUITES = availableCipherSuites; SUPPORTS_KEYMANAGER_FACTORY = supportsKeyManagerFactory; Set protocols = new LinkedHashSet<>(6); // Seems like there is no way to explicitly disable SSLv2Hello in openssl so it is always enabled protocols.add(PROTOCOL_SSL_V2_HELLO); if (doesSupportProtocol(SSL.SSL_PROTOCOL_SSLV2, SSL.SSL_OP_NO_SSLv2)) { protocols.add(PROTOCOL_SSL_V2); } if (doesSupportProtocol(SSL.SSL_PROTOCOL_SSLV3, SSL.SSL_OP_NO_SSLv3)) { protocols.add(PROTOCOL_SSL_V3); } if (doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1, SSL.SSL_OP_NO_TLSv1)) { protocols.add(PROTOCOL_TLS_V1); } if (doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1_1, SSL.SSL_OP_NO_TLSv1_1)) { protocols.add(PROTOCOL_TLS_V1_1); } if (doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1_2, SSL.SSL_OP_NO_TLSv1_2)) { protocols.add(PROTOCOL_TLS_V1_2); } // This is only supported by java11 and later. if (tlsv13Supported && doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1_3, SSL.SSL_OP_NO_TLSv1_3)) { protocols.add(PROTOCOL_TLS_V1_3); TLSV13_SUPPORTED = true; } else { TLSV13_SUPPORTED = false; } SUPPORTED_PROTOCOLS_SET = Collections.unmodifiableSet(protocols); SUPPORTS_OCSP = doesSupportOcsp(); if (logger.isDebugEnabled()) { logger.debug("Supported protocols (OpenSSL): {} ", SUPPORTED_PROTOCOLS_SET); logger.debug("Default cipher suites (OpenSSL): {}", DEFAULT_CIPHERS); } } else { DEFAULT_CIPHERS = Collections.emptyList(); AVAILABLE_OPENSSL_CIPHER_SUITES = Collections.emptySet(); AVAILABLE_JAVA_CIPHER_SUITES = Collections.emptySet(); AVAILABLE_CIPHER_SUITES = Collections.emptySet(); SUPPORTS_KEYMANAGER_FACTORY = false; SUPPORTED_PROTOCOLS_SET = Collections.emptySet(); SUPPORTS_OCSP = false; TLSV13_SUPPORTED = false; IS_BORINGSSL = false; } } /** * Returns a self-signed {@link X509Certificate} for {@code netty.io}. */ static X509Certificate selfSignedCertificate() throws CertificateException { return (X509Certificate) SslContext.X509_CERT_FACTORY.generateCertificate( new ByteArrayInputStream(CERT.getBytes(CharsetUtil.US_ASCII)) ); } private static boolean doesSupportOcsp() { boolean supportsOcsp = false; if (version() >= 0x10002000L) { long sslCtx = -1; try { sslCtx = SSLContext.make(SSL.SSL_PROTOCOL_TLSV1_2, SSL.SSL_MODE_SERVER); SSLContext.enableOcsp(sslCtx, false); supportsOcsp = true; } catch (Exception ignore) { // ignore } finally { if (sslCtx != -1) { SSLContext.free(sslCtx); } } } return supportsOcsp; } private static boolean doesSupportProtocol(int protocol, int opt) { if (opt == 0) { // If the opt is 0 the protocol is not supported. This is for example the case with BoringSSL and SSLv2. return false; } long sslCtx = -1; try { sslCtx = SSLContext.make(protocol, SSL.SSL_MODE_COMBINED); return true; } catch (Exception ignore) { return false; } finally { if (sslCtx != -1) { SSLContext.free(sslCtx); } } } /** * Returns {@code true} if and only if * {@code netty-tcnative} and its OpenSSL support * are available. */ public static boolean isAvailable() { return UNAVAILABILITY_CAUSE == null; } /** * Returns {@code true} if the used version of openssl supports * ALPN. */ public static boolean isAlpnSupported() { return version() >= 0x10002000L; } /** * Returns {@code true} if the used version of OpenSSL supports OCSP stapling. */ public static boolean isOcspSupported() { return SUPPORTS_OCSP; } /** * Returns the version of the used available OpenSSL library or {@code -1} if {@link #isAvailable()} * returns {@code false}. */ public static int version() { return isAvailable() ? SSL.version() : -1; } /** * Returns the version string of the used available OpenSSL library or {@code null} if {@link #isAvailable()} * returns {@code false}. */ public static String versionString() { return isAvailable() ? SSL.versionString() : null; } /** * Ensure that {@code netty-tcnative} and * its OpenSSL support are available. * * @throws UnsatisfiedLinkError if unavailable */ public static void ensureAvailability() { if (UNAVAILABILITY_CAUSE != null) { throw (Error) new UnsatisfiedLinkError( "failed to load the required native library").initCause(UNAVAILABILITY_CAUSE); } } /** * Returns the cause of unavailability of * {@code netty-tcnative} and its OpenSSL support. * * @return the cause if unavailable. {@code null} if available. */ public static Throwable unavailabilityCause() { return UNAVAILABILITY_CAUSE; } /** * @deprecated use {@link #availableOpenSslCipherSuites()} */ @Deprecated public static Set availableCipherSuites() { return availableOpenSslCipherSuites(); } /** * Returns all the available OpenSSL cipher suites. * Please note that the returned array may include the cipher suites that are insecure or non-functional. */ public static Set availableOpenSslCipherSuites() { return AVAILABLE_OPENSSL_CIPHER_SUITES; } /** * Returns all the available cipher suites (Java-style). * Please note that the returned array may include the cipher suites that are insecure or non-functional. */ public static Set availableJavaCipherSuites() { return AVAILABLE_JAVA_CIPHER_SUITES; } /** * Returns {@code true} if and only if the specified cipher suite is available in OpenSSL. * Both Java-style cipher suite and OpenSSL-style cipher suite are accepted. */ public static boolean isCipherSuiteAvailable(String cipherSuite) { String converted = CipherSuiteConverter.toOpenSsl(cipherSuite, IS_BORINGSSL); if (converted != null) { cipherSuite = converted; } return AVAILABLE_OPENSSL_CIPHER_SUITES.contains(cipherSuite); } /** * Returns {@code true} if {@link javax.net.ssl.KeyManagerFactory} is supported when using OpenSSL. */ public static boolean supportsKeyManagerFactory() { return SUPPORTS_KEYMANAGER_FACTORY; } /** * Always returns {@code true} if {@link #isAvailable()} returns {@code true}. * * @deprecated Will be removed because hostname validation is always done by a * {@link javax.net.ssl.TrustManager} implementation. */ @Deprecated public static boolean supportsHostnameValidation() { return isAvailable(); } static long memoryAddress(ByteBuf buf) { assert buf.isDirect(); return buf.hasMemoryAddress() ? buf.memoryAddress() : Buffer.address(buf.nioBuffer()); } private OpenSsl() { } private static void loadTcNative() throws Exception { String os = PlatformDependent.normalizedOs(); String arch = PlatformDependent.normalizedArch(); Set libNames = new LinkedHashSet<>(4); String staticLibName = "netty_tcnative"; // First, try loading the platform-specific library. Platform-specific // libraries will be available if using a tcnative uber jar. libNames.add(staticLibName + "_" + os + '_' + arch); if ("linux".equalsIgnoreCase(os)) { // Fedora SSL lib so naming (libssl.so.10 vs libssl.so.1.0.0).. libNames.add(staticLibName + "_" + os + '_' + arch + "_fedora"); } libNames.add(staticLibName + "_" + arch); libNames.add(staticLibName); NativeLibraryLoader.loadFirstAvailable(SSL.class.getClassLoader(), libNames.toArray(new String[0])); } private static boolean initializeTcNative(String engine) throws Exception { return Library.initialize("provided", engine); } static void releaseIfNeeded(ReferenceCounted counted) { if (counted.refCnt() > 0) { ReferenceCountUtil.safeRelease(counted); } } static boolean isTlsv13Supported() { return TLSV13_SUPPORTED; } static boolean isBoringSSL() { return IS_BORINGSSL; } }