nl.topicus.jdbc.shaded.io.netty.handler.ssl.OpenSsl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spanner-jdbc Show documentation
Show all versions of spanner-jdbc Show documentation
JDBC Driver for Google Cloud Spanner
/*
* 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 nl.topicus.jdbc.shaded.com.liance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.nl.topicus.jdbc.shaded.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 nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.handler.ssl;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.buffer.ByteBuf;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.handler.ssl.util.SelfSignedCertificate;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.util.ReferenceCountUtil;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.util.ReferenceCounted;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.util.internal.NativeLibraryLoader;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.util.internal.SystemPropertyUtil;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.util.internal.logging.InternalLogger;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.util.internal.logging.InternalLoggerFactory;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.internal.tcnative.Buffer;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.internal.tcnative.Library;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.internal.tcnative.SSL;
import nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.internal.tcnative.SSLContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Set;
/**
* Tells if {@code nl.topicus.jdbc.shaded.net.y-tcnative} and its OpenSSL support
* are available.
*/
public final class OpenSsl {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(OpenSsl.class);
private static final String LINUX = "linux";
private static final String UNKNOWN = "unknown";
private static final Throwable UNAVAILABILITY_CAUSE;
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_HOSTNAME_VALIDATION;
private static final boolean USE_KEYMANAGER_FACTORY;
private static final boolean SUPPORTS_OCSP;
// Protocols
static final String PROTOCOL_SSL_V2_HELLO = "SSLv2Hello";
static final String PROTOCOL_SSL_V2 = "SSLv2";
static final String PROTOCOL_SSL_V3 = "SSLv3";
static final String PROTOCOL_TLS_V1 = "TLSv1";
static final String PROTOCOL_TLS_V1_1 = "TLSv1.1";
static final String PROTOCOL_TLS_V1_2 = "TLSv1.2";
static final Set SUPPORTED_PROTOCOLS_SET;
static {
Throwable cause = null;
// Test if nl.topicus.jdbc.shaded.net.y-tcnative is in the classpath first.
try {
Class.forName("nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.internal.tcnative.SSL", false, OpenSsl.class.getClassLoader());
} catch (ClassNotFoundException t) {
cause = t;
logger.debug(
"nl.topicus.jdbc.shaded.net.y-tcnative not in the classpath; " +
OpenSslEngine.class.getSimpleName() + " will be unavailable.");
}
// If in the classpath, try to load the native library and initialize nl.topicus.jdbc.shaded.net.y-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 nl.topicus.jdbc.shaded.net.y-tcnative; " +
OpenSslEngine.class.getSimpleName() + " will be unavailable, unless the " +
"application has already loaded the symbols by some other means. " +
"See http://nl.topicus.jdbc.shaded.net.y.nl.topicus.jdbc.shaded.io.wiki/forked-tomcat-native.html for more information.", t);
}
try {
initializeTcNative();
// 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 nl.topicus.jdbc.shaded.net.y-tcnative; " +
OpenSslEngine.class.getSimpleName() + " will be unavailable. " +
"See http://nl.topicus.jdbc.shaded.net.y.nl.topicus.jdbc.shaded.io.wiki/forked-tomcat-native.html for more information.", t);
}
}
UNAVAILABILITY_CAUSE = cause;
if (cause == null) {
logger.debug("nl.topicus.jdbc.shaded.net.y-tcnative using native library: {}", SSL.versionString());
final Set availableOpenSslCipherSuites = new LinkedHashSet(128);
boolean supportsKeyManagerFactory = false;
boolean useKeyManagerFactory = false;
boolean supportsHostNameValidation = false;
try {
final long sslCtx = SSLContext.make(SSL.SSL_PROTOCOL_ALL, SSL.SSL_MODE_SERVER);
long certBio = 0;
SelfSignedCertificate cert = null;
try {
SSLContext.setCipherSuite(sslCtx, "ALL");
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)) {
continue;
}
availableOpenSslCipherSuites.add(c);
}
try {
SSL.setHostNameValidation(ssl, 0, "nl.topicus.jdbc.shaded.net.y.nl.topicus.jdbc.shaded.io.);
supportsHostNameValidation = true;
} catch (Throwable ignore) {
logger.debug("Hostname Verification not supported.");
}
try {
cert = new SelfSignedCertificate();
certBio = ReferenceCountedOpenSslContext.toBIO(cert.cert());
SSL.setCertificateChainBio(ssl, certBio, false);
supportsKeyManagerFactory = true;
try {
useKeyManagerFactory = AccessController.doPrivileged(new PrivilegedAction() {
@Override
public Boolean run() {
return SystemPropertyUtil.getBoolean(
"nl.topicus.jdbc.shaded.io.nl.topicus.jdbc.shaded.net.y.handler.ssl.openssl.useKeyManagerFactory", true);
}
});
} catch (Throwable ignore) {
logger.debug("Failed to get useKeyManagerFactory system property.");
}
} catch (Throwable ignore) {
logger.debug("KeyManagerFactory not supported.");
}
} finally {
SSL.freeSSL(ssl);
if (certBio != 0) {
SSL.freeBIO(certBio);
}
if (cert != null) {
cert.delete();
}
}
} 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
availableJavaCipherSuites.add(CipherSuiteConverter.toJava(cipher, "TLS"));
availableJavaCipherSuites.add(CipherSuiteConverter.toJava(cipher, "SSL"));
}
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;
SUPPORTS_HOSTNAME_VALIDATION = supportsHostNameValidation;
USE_KEYMANAGER_FACTORY = useKeyManagerFactory;
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)) {
protocols.add(PROTOCOL_SSL_V2);
}
if (doesSupportProtocol(SSL.SSL_PROTOCOL_SSLV3)) {
protocols.add(PROTOCOL_SSL_V3);
}
if (doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1)) {
protocols.add(PROTOCOL_TLS_V1);
}
if (doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1_1)) {
protocols.add(PROTOCOL_TLS_V1_1);
}
if (doesSupportProtocol(SSL.SSL_PROTOCOL_TLSV1_2)) {
protocols.add(PROTOCOL_TLS_V1_2);
}
SUPPORTED_PROTOCOLS_SET = Collections.unmodifiableSet(protocols);
SUPPORTS_OCSP = doesSupportOcsp();
} else {
AVAILABLE_OPENSSL_CIPHER_SUITES = Collections.emptySet();
AVAILABLE_JAVA_CIPHER_SUITES = Collections.emptySet();
AVAILABLE_CIPHER_SUITES = Collections.emptySet();
SUPPORTS_KEYMANAGER_FACTORY = false;
SUPPORTS_HOSTNAME_VALIDATION = false;
USE_KEYMANAGER_FACTORY = false;
SUPPORTED_PROTOCOLS_SET = Collections.emptySet();
SUPPORTS_OCSP = false;
}
}
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) {
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 nl.topicus.jdbc.shaded.net.y-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 nl.topicus.jdbc.shaded.net.y-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 nl.topicus.jdbc.shaded.net.y-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);
if (converted != null) {
cipherSuite = converted;
}
return AVAILABLE_OPENSSL_CIPHER_SUITES.contains(cipherSuite);
}
/**
* Returns {@code true} if {@link javax.nl.topicus.jdbc.shaded.net.ssl.KeyManagerFactory} is supported when using OpenSSL.
*/
public static boolean supportsKeyManagerFactory() {
return SUPPORTS_KEYMANAGER_FACTORY;
}
/**
* Returns {@code true} if Hostname Validation
* is supported when using OpenSSL.
*/
public static boolean supportsHostnameValidation() {
return SUPPORTS_HOSTNAME_VALIDATION;
}
static boolean useKeyManagerFactory() {
return USE_KEYMANAGER_FACTORY;
}
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 = normalizeOs(SystemPropertyUtil.get("os.name", ""));
String arch = normalizeArch(SystemPropertyUtil.get("os.arch", ""));
Set libNames = new LinkedHashSet(3);
// First, try loading the platform-specific library. Platform-specific
// libraries will be available if using a tcnative uber jar.
libNames.add("nl.topicus.jdbc.shaded.net.y-tcnative-" + os + '-' + arch);
if (LINUX.equalsIgnoreCase(os)) {
// Fedora SSL lib so naming (libssl.so.10 vs libssl.so.1.0.0)..
libNames.add("nl.topicus.jdbc.shaded.net.y-tcnative-" + os + '-' + arch + "-fedora");
}
// finally the default library.
libNames.add("nl.topicus.jdbc.shaded.net.y-tcnative");
NativeLibraryLoader.loadFirstAvailable(SSL.class.getClassLoader(),
libNames.toArray(new String[libNames.size()]));
}
private static boolean initializeTcNative() throws Exception {
return Library.initialize();
}
private static String normalizeOs(String value) {
value = normalize(value);
if (value.startsWith("aix")) {
return "aix";
}
if (value.startsWith("hpux")) {
return "hpux";
}
if (value.startsWith("os400")) {
// Avoid the names such as os4000
if (value.length() <= 5 || !Character.isDigit(value.charAt(5))) {
return "os400";
}
}
if (value.startsWith(LINUX)) {
return LINUX;
}
if (value.startsWith("macosx") || value.startsWith("osx")) {
return "osx";
}
if (value.startsWith("freebsd")) {
return "freebsd";
}
if (value.startsWith("openbsd")) {
return "openbsd";
}
if (value.startsWith("nl.topicus.jdbc.shaded.net.sd")) {
return "nl.topicus.jdbc.shaded.net.sd";
}
if (value.startsWith("solaris") || value.startsWith("sunos")) {
return "sunos";
}
if (value.startsWith("windows")) {
return "windows";
}
return UNKNOWN;
}
private static String normalizeArch(String value) {
value = normalize(value);
if (value.matches("^(x8664|amd64|ia32e|em64t|x64)$")) {
return "x86_64";
}
if (value.matches("^(x8632|x86|i[3-6]86|ia32|x32)$")) {
return "x86_32";
}
if (value.matches("^(ia64|itanium64)$")) {
return "itanium_64";
}
if (value.matches("^(sparc|sparc32)$")) {
return "sparc_32";
}
if (value.matches("^(sparcv9|sparc64)$")) {
return "sparc_64";
}
if (value.matches("^(arm|arm32)$")) {
return "arm_32";
}
if ("aarch64".equals(value)) {
return "aarch_64";
}
if (value.matches("^(ppc|ppc32)$")) {
return "ppc_32";
}
if ("ppc64".equals(value)) {
return "ppc_64";
}
if ("ppc64le".equals(value)) {
return "ppcle_64";
}
if ("s390".equals(value)) {
return "s390_32";
}
if ("s390x".equals(value)) {
return "s390_64";
}
return UNKNOWN;
}
private static String normalize(String value) {
return value.toLowerCase(Locale.US).replaceAll("[^a-z0-9]+", "");
}
static void releaseIfNeeded(ReferenceCounted counted) {
if (counted.refCnt() > 0) {
ReferenceCountUtil.safeRelease(counted);
}
}
}