blob: 5d9f7f4ef183ba34c0ee2b34fb30e7d2c9250202 [file] [log] [blame]
/*
* Copyright 2014 The Android Open Source Project
*
* Licensed 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.
*/
/*
* Copyright 2013 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 org.conscrypt;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketImpl;
import java.nio.channels.SocketChannel;
import java.security.AccessController;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PrivilegedAction;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.ECParameterSpec;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.spec.GCMParameterSpec;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;
import sun.security.x509.AlgorithmId;
/**
* Platform-specific methods for OpenJDK.
*
* Uses reflection to implement Java 8 SSL features for backwards compatibility.
*/
final class Platform {
private static final Logger logger = Logger.getLogger(NativeLibraryLoader.class.getName());
private static final int JAVA_VERSION = javaVersion0();
private static final String TEMP_DIR_PROPERTY_NAME = "org.conscrypt.tmpdir";
private static final Method GET_CURVE_NAME_METHOD;
static final OperatingSystem OS;
static final Architecture ARCH;
static {
OS = getOperatingSystem(System.getProperty("os.name", ""));
ARCH = getArchitecture(System.getProperty("os.arch", ""));
Method getCurveNameMethod = null;
try {
getCurveNameMethod = ECParameterSpec.class.getDeclaredMethod("getCurveName");
getCurveNameMethod.setAccessible(true);
} catch (Exception ignored) {
}
GET_CURVE_NAME_METHOD = getCurveNameMethod;
}
/**
* Enumeration of operating systems.
*/
enum OperatingSystem {
AIX,
HPUX,
OS400,
LINUX,
OSX,
FREEBSD,
OPENBSD,
NETBSD,
SUNOS,
WINDOWS,
UNKNOWN
}
/**
* Enumeration of architectures.
*/
enum Architecture {
X86_64,
X86_32,
ITANIUM_64,
SPARC_32,
SPARC_64,
ARM_32,
AARCH_64,
PPC_32,
PPC_64,
PPCLE_64,
S390_32,
S390_64,
UNKNOWN
}
private Platform() {}
static void setup() {}
static boolean isWindows() {
return Platform.OS == OperatingSystem.WINDOWS;
}
static boolean isOSX() {
return Platform.OS == OperatingSystem.OSX;
}
static File getTempDir() {
File f;
try {
// First, see if the application specified a temp dir for conscrypt.
f = toDirectory(System.getProperty(TEMP_DIR_PROPERTY_NAME));
if (f != null) {
return f;
}
// Use the Java system property if available.
f = toDirectory(System.getProperty("java.io.tmpdir"));
if (f != null) {
return f;
}
// This shouldn't happen, but just in case ..
if (isWindows()) {
f = toDirectory(System.getenv("TEMP"));
if (f != null) {
return f;
}
String userprofile = System.getenv("USERPROFILE");
if (userprofile != null) {
f = toDirectory(userprofile + "\\AppData\\Local\\Temp");
if (f != null) {
return f;
}
f = toDirectory(userprofile + "\\Local Settings\\Temp");
if (f != null) {
return f;
}
}
} else {
f = toDirectory(System.getenv("TMPDIR"));
if (f != null) {
return f;
}
}
} catch (Exception ignored) {
// Environment variable inaccessible
}
// Last resort.
if (isWindows()) {
f = new File("C:\\Windows\\Temp");
} else {
f = new File("/tmp");
}
logger.log(Level.WARNING,
"Failed to get the temporary directory; falling back to: {0}", f);
return f;
}
/**
* Approximates the behavior of File.createTempFile without depending on SecureRandom.
*/
static File createTempFile(String prefix, String suffix, File directory)
throws IOException {
if (directory == null) {
throw new NullPointerException();
}
long time = System.currentTimeMillis();
prefix = new File(prefix).getName();
IOException suppressed = null;
for (int i = 0; i < 10000; i++) {
String tempName = String.format(Locale.US, "%s%d%04d%s", prefix, time, i, suffix);
File tempFile = new File(directory, tempName);
if (!tempName.equals(tempFile.getName())) {
// The given prefix or suffix contains path separators.
throw new IOException("Unable to create temporary file: " + tempFile);
}
try {
if (tempFile.createNewFile()) {
return tempFile.getCanonicalFile();
}
} catch (IOException e) {
// This may just be a transient error; store it just in case.
suppressed = e;
}
}
if (suppressed != null) {
throw suppressed;
} else {
throw new IOException("Unable to create temporary file");
}
}
@SuppressWarnings("ResultOfMethodCallIgnored")
private static File toDirectory(String path) {
if (path == null) {
return null;
}
File f = new File(path);
f.mkdirs();
if (!f.isDirectory()) {
return null;
}
try {
return f.getAbsoluteFile();
} catch (Exception ignored) {
return f;
}
}
static boolean canExecuteExecutable(File file) throws IOException {
if (JAVA_VERSION >= 7) {
return Java7PlatformUtil.canExecuteExecutable(file);
}
return true;
}
static void addSuppressed(Throwable t, Throwable suppressed) {
if (JAVA_VERSION >= 7) {
Java7PlatformUtil.addSuppressed(t, suppressed);
}
}
static FileDescriptor getFileDescriptor(Socket s) {
try {
SocketChannel channel = s.getChannel();
if (channel != null) {
Field f_fd = channel.getClass().getDeclaredField("fd");
f_fd.setAccessible(true);
return (FileDescriptor) f_fd.get(channel);
}
} catch (Exception e) {
// Try socket class below...
}
try {
Field f_impl = Socket.class.getDeclaredField("impl");
f_impl.setAccessible(true);
Object socketImpl = f_impl.get(s);
Field f_fd = SocketImpl.class.getDeclaredField("fd");
f_fd.setAccessible(true);
return (FileDescriptor) f_fd.get(socketImpl);
} catch (Exception e) {
throw new RuntimeException("Can't get FileDescriptor from socket", e);
}
}
@SuppressWarnings("unused")
static FileDescriptor getFileDescriptorFromSSLSocket(AbstractConscryptSocket socket) {
return getFileDescriptor(socket);
}
@SuppressWarnings("unused")
static String getCurveName(ECParameterSpec spec) {
if (GET_CURVE_NAME_METHOD != null) {
try {
return (String) GET_CURVE_NAME_METHOD.invoke(spec);
} catch (Exception ignored) {
// Ignored
}
}
return null;
}
@SuppressWarnings("unused")
static void setCurveName(@SuppressWarnings("unused") ECParameterSpec spec,
@SuppressWarnings("unused") String curveName) {
// This doesn't appear to be needed.
}
/*
* Call Os.setsockoptTimeval via reflection.
*/
@SuppressWarnings("unused")
static void setSocketWriteTimeout(@SuppressWarnings("unused") Socket s,
@SuppressWarnings("unused") long timeoutMillis) throws SocketException {
// TODO: figure this out on the RI
}
static void setSSLParameters(
SSLParameters params, SSLParametersImpl impl, AbstractConscryptSocket socket) {
if (JAVA_VERSION >= 8) {
Java8PlatformUtil.setSSLParameters(params, impl, socket);
} else if (JAVA_VERSION >= 7) {
Java7PlatformUtil.setSSLParameters(params, impl);
}
}
static void getSSLParameters(
SSLParameters params, SSLParametersImpl impl, AbstractConscryptSocket socket) {
if (JAVA_VERSION >= 8) {
Java8PlatformUtil.getSSLParameters(params, impl, socket);
} else if (JAVA_VERSION >= 7) {
Java7PlatformUtil.getSSLParameters(params, impl);
}
}
static void setSSLParameters(
SSLParameters params, SSLParametersImpl impl, ConscryptEngine engine) {
if (JAVA_VERSION >= 8) {
Java8PlatformUtil.setSSLParameters(params, impl, engine);
} else if (JAVA_VERSION >= 7) {
Java7PlatformUtil.setSSLParameters(params, impl);
}
}
static void getSSLParameters(
SSLParameters params, SSLParametersImpl impl, ConscryptEngine engine) {
if (JAVA_VERSION >= 8) {
Java8PlatformUtil.getSSLParameters(params, impl, engine);
} else if (JAVA_VERSION >= 7) {
Java7PlatformUtil.getSSLParameters(params, impl);
}
}
@SuppressWarnings("unused")
static void setEndpointIdentificationAlgorithm(
SSLParameters params, String endpointIdentificationAlgorithm) {
params.setEndpointIdentificationAlgorithm(endpointIdentificationAlgorithm);
}
@SuppressWarnings("unused")
static String getEndpointIdentificationAlgorithm(SSLParameters params) {
return params.getEndpointIdentificationAlgorithm();
}
@SuppressWarnings("unused")
static void checkClientTrusted(X509TrustManager tm, X509Certificate[] chain, String authType,
AbstractConscryptSocket socket) throws CertificateException {
if (JAVA_VERSION >= 7) {
Java7PlatformUtil.checkClientTrusted(tm, chain, authType, socket);
} else {
tm.checkClientTrusted(chain, authType);
}
}
@SuppressWarnings("unused")
static void checkServerTrusted(X509TrustManager tm, X509Certificate[] chain, String authType,
AbstractConscryptSocket socket) throws CertificateException {
if (JAVA_VERSION >= 7) {
Java7PlatformUtil.checkServerTrusted(tm, chain, authType, socket);
} else {
tm.checkServerTrusted(chain, authType);
}
}
@SuppressWarnings("unused")
static void checkClientTrusted(X509TrustManager tm, X509Certificate[] chain, String authType,
ConscryptEngine engine) throws CertificateException {
if (JAVA_VERSION >= 7) {
Java7PlatformUtil.checkClientTrusted(tm, chain, authType, engine);
} else {
tm.checkClientTrusted(chain, authType);
}
}
@SuppressWarnings("unused")
static void checkServerTrusted(X509TrustManager tm, X509Certificate[] chain, String authType,
ConscryptEngine engine) throws CertificateException {
if (JAVA_VERSION >= 7) {
Java7PlatformUtil.checkServerTrusted(tm, chain, authType, engine);
} else {
tm.checkServerTrusted(chain, authType);
}
}
/**
* Wraps an old AndroidOpenSSL key instance. This is not needed on RI.
*/
@SuppressWarnings("unused")
static OpenSSLKey wrapRsaKey(@SuppressWarnings("unused") PrivateKey javaKey) {
return null;
}
/**
* Logs to the system EventLog system.
*/
@SuppressWarnings("unused")
static void logEvent(@SuppressWarnings("unused") String message) {}
/**
* Returns true if the supplied hostname is an literal IP address.
*/
@SuppressWarnings("unused")
static boolean isLiteralIpAddress(String hostname) {
// TODO: any RI API to make this better?
return AddressUtils.isLiteralIpAddress(hostname);
}
/**
* For unbundled versions, SNI is always enabled by default.
*/
@SuppressWarnings("unused")
static boolean isSniEnabledByDefault() {
return true;
}
/**
* Currently we don't wrap anything from the RI.
*/
@SuppressWarnings("unused")
static SSLSocketFactory wrapSocketFactoryIfNeeded(OpenSSLSocketFactoryImpl factory) {
return factory;
}
/**
* Convert from platform's GCMParameterSpec to our internal version.
*/
@SuppressWarnings("unused")
static GCMParameters fromGCMParameterSpec(AlgorithmParameterSpec params) {
if (params instanceof GCMParameterSpec) {
GCMParameterSpec gcmParams = (GCMParameterSpec) params;
return new GCMParameters(gcmParams.getTLen(), gcmParams.getIV());
}
return null;
}
/**
* Creates a platform version of {@code GCMParameterSpec}.
*/
@SuppressWarnings("unused")
static AlgorithmParameterSpec toGCMParameterSpec(int tagLenInBits, byte[] iv) {
return new GCMParameterSpec(tagLenInBits, iv);
}
/*
* CloseGuard functions.
*/
@SuppressWarnings("unused")
static Object closeGuardGet() {
return null;
}
@SuppressWarnings("unused")
static void closeGuardOpen(@SuppressWarnings("unused") Object guardObj,
@SuppressWarnings("unused") String message) {}
@SuppressWarnings("unused")
static void closeGuardClose(@SuppressWarnings("unused") Object guardObj) {}
@SuppressWarnings("unused")
static void closeGuardWarnIfOpen(@SuppressWarnings("unused") Object guardObj) {}
/*
* BlockGuard functions.
*/
@SuppressWarnings("unused")
static void blockGuardOnNetwork() {}
/**
* OID to Algorithm Name mapping.
*/
@SuppressWarnings("unused")
static String oidToAlgorithmName(String oid) {
try {
return AlgorithmId.get(oid).getName();
} catch (NoSuchAlgorithmException e) {
return oid;
}
}
/*
* Pre-Java-8 backward compatibility.
*/
@SuppressWarnings("unused")
static SSLSession wrapSSLSession(ActiveSession sslSession) {
return ExtendedSessionAdapter.wrap(sslSession);
}
@SuppressWarnings("unused")
static SSLSession unwrapSSLSession(SSLSession sslSession) {
return ExtendedSessionAdapter.getDelegate(sslSession);
}
public static String getOriginalHostNameFromInetAddress(InetAddress addr) {
try {
Method getHolder = InetAddress.class.getDeclaredMethod("holder");
getHolder.setAccessible(true);
Method getOriginalHostName = Class.forName("java.net.InetAddress$InetAddressHolder")
.getDeclaredMethod("getOriginalHostName");
getOriginalHostName.setAccessible(true);
String originalHostName = (String) getOriginalHostName.invoke(getHolder.invoke(addr));
if (originalHostName == null) {
return addr.getHostAddress();
}
return originalHostName;
} catch (InvocationTargetException e) {
throw new RuntimeException("Failed to get originalHostName", e);
} catch (ReflectiveOperationException ignore) {
// passthrough and return addr.getHostAddress()
}
return addr.getHostAddress();
}
/*
* Pre-Java-7 backward compatibility.
*/
@SuppressWarnings("unused")
static String getHostStringFromInetSocketAddress(InetSocketAddress addr) {
if (JAVA_VERSION >= 7) {
return Java7PlatformUtil.getHostStringFromInetSocketAddress(addr);
}
return null;
}
/**
* Check if SCT verification is required for a given hostname.
*
* SCT Verification is enabled using {@code Security} properties.
* The "conscrypt.ct.enable" property must be true, as well as a per domain property.
* The reverse notation of the domain name, prefixed with "conscrypt.ct.enforce."
* is used as the property name.
* Basic globbing is also supported.
*
* For example, for the domain foo.bar.com, the following properties will be
* looked up, in order of precedence.
* - conscrypt.ct.enforce.com.bar.foo
* - conscrypt.ct.enforce.com.bar.*
* - conscrypt.ct.enforce.com.*
* - conscrypt.ct.enforce.*
*/
static boolean isCTVerificationRequired(String hostname) {
if (hostname == null) {
return false;
}
String property = Security.getProperty("conscrypt.ct.enable");
if (property == null || !Boolean.valueOf(property.toLowerCase())) {
return false;
}
List<String> parts = Arrays.asList(hostname.split("\\."));
Collections.reverse(parts);
boolean enable = false;
StringBuilder propertyName = new StringBuilder("conscrypt.ct.enforce");
// The loop keeps going on even once we've found a match
// This allows for finer grained settings on subdomains
for (String part : parts) {
property = Security.getProperty(propertyName + ".*");
if (property != null) {
enable = Boolean.valueOf(property.toLowerCase());
}
propertyName.append(".").append(part);
}
property = Security.getProperty(propertyName.toString());
if (property != null) {
enable = Boolean.valueOf(property.toLowerCase());
}
return enable;
}
private static boolean isAndroid() {
boolean android;
try {
Class.forName("android.app.Application", false, getSystemClassLoader());
android = true;
} catch (Throwable ignored) {
// Failed to load the class uniquely available in Android.
android = false;
}
return android;
}
static int javaVersion() {
return JAVA_VERSION;
}
private static int javaVersion0() {
final int majorVersion;
if (isAndroid()) {
majorVersion = 6;
} else {
majorVersion = majorVersionFromJavaSpecificationVersion();
}
return majorVersion;
}
private static int majorVersionFromJavaSpecificationVersion() {
return majorVersion(System.getProperty("java.specification.version", "1.6"));
}
private static int majorVersion(final String javaSpecVersion) {
final String[] components = javaSpecVersion.split("\\.");
final int[] version = new int[components.length];
for (int i = 0; i < components.length; i++) {
version[i] = Integer.parseInt(components[i]);
}
if (version[0] == 1) {
assert version[1] >= 6;
return version[1];
} else {
return version[0];
}
}
private static ClassLoader getSystemClassLoader() {
if (System.getSecurityManager() == null) {
return ClassLoader.getSystemClassLoader();
} else {
return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
@Override
public ClassLoader run() {
return ClassLoader.getSystemClassLoader();
}
});
}
}
/**
* Normalizes the os.name value into the value used by the Maven os plugin
* (https://github.com/trustin/os-maven-plugin). This plugin is used to generate
* platform-specific
* classifiers for artifacts.
*/
private static OperatingSystem getOperatingSystem(String value) {
value = normalize(value);
if (value.startsWith("aix")) {
return OperatingSystem.AIX;
}
if (value.startsWith("hpux")) {
return OperatingSystem.HPUX;
}
if (value.startsWith("os400")) {
// Avoid the names such as os4000
if (value.length() <= 5 || !Character.isDigit(value.charAt(5))) {
return OperatingSystem.OS400;
}
}
if (value.startsWith("linux")) {
return OperatingSystem.LINUX;
}
if (value.startsWith("macosx") || value.startsWith("osx")) {
return OperatingSystem.OSX;
}
if (value.startsWith("freebsd")) {
return OperatingSystem.FREEBSD;
}
if (value.startsWith("openbsd")) {
return OperatingSystem.OPENBSD;
}
if (value.startsWith("netbsd")) {
return OperatingSystem.NETBSD;
}
if (value.startsWith("solaris") || value.startsWith("sunos")) {
return OperatingSystem.SUNOS;
}
if (value.startsWith("windows")) {
return OperatingSystem.WINDOWS;
}
return OperatingSystem.UNKNOWN;
}
/**
* Normalizes the os.arch value into the value used by the Maven os plugin
* (https://github.com/trustin/os-maven-plugin). This plugin is used to generate
* platform-specific
* classifiers for artifacts.
*/
private static Architecture getArchitecture(String value) {
value = normalize(value);
if (value.matches("^(x8664|amd64|ia32e|em64t|x64)$")) {
return Architecture.X86_64;
}
if (value.matches("^(x8632|x86|i[3-6]86|ia32|x32)$")) {
return Architecture.X86_32;
}
if (value.matches("^(ia64|itanium64)$")) {
return Architecture.ITANIUM_64;
}
if (value.matches("^(sparc|sparc32)$")) {
return Architecture.SPARC_32;
}
if (value.matches("^(sparcv9|sparc64)$")) {
return Architecture.SPARC_64;
}
if (value.matches("^(arm|arm32)$")) {
return Architecture.ARM_32;
}
if ("aarch64".equals(value)) {
return Architecture.AARCH_64;
}
if (value.matches("^(ppc|ppc32)$")) {
return Architecture.PPC_32;
}
if ("ppc64".equals(value)) {
return Architecture.PPC_64;
}
if ("ppc64le".equals(value)) {
return Architecture.PPCLE_64;
}
if ("s390".equals(value)) {
return Architecture.S390_32;
}
if ("s390x".equals(value)) {
return Architecture.S390_64;
}
return Architecture.UNKNOWN;
}
private static String normalize(String value) {
return value.toLowerCase(Locale.US).replaceAll("[^a-z0-9]+", "");
}
}