1 /* 2 * Copyright 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package libcore.java.security; 18 19 import android.system.Os; 20 import java.io.BufferedReader; 21 import java.io.FileReader; 22 import java.io.IOException; 23 import java.lang.reflect.InvocationTargetException; 24 import java.lang.reflect.Method; 25 26 import dalvik.system.VMRuntime; 27 28 public class CpuFeatures { 29 /** Machine architecture, determined from the "machine" value returned by uname() */ 30 private enum Arch { 31 // 64bit ARM can return armv8 or aarch64. 32 // 32bit ARM should return armv7 or armv7a 33 ARM("^aarch.*|^arm.*"), 34 // 64bit Android and Linux generally return x86_64. 35 // 32bit Android and Linux generally return i686 36 // Other host architectures can potentially return x86 or i386. 37 X86("^x86.*|i386|i686"); 38 39 private final String machineRegex; 40 Arch(String machineRegex)41 Arch(String machineRegex) { 42 this.machineRegex = machineRegex; 43 } 44 45 /** 46 * Returns the architecture of this machine by matching against output from uname() 47 * against the regex for each known family. 48 */ currentArch()49 public static Arch currentArch() { 50 String machine = Os.uname().machine; 51 for (Arch type : values()) { 52 if (machine.matches(type.machineRegex)) { 53 return type; 54 } 55 } 56 throw new IllegalStateException("Unknown machine value: " + machine); 57 } 58 } 59 60 private enum InstructionSet { 61 ARM_32(Arch.ARM, "arm"), 62 ARM_64(Arch.ARM, "arm64"), 63 X86_32(Arch.X86, "x86"), 64 X86_64(Arch.X86, "x86_64"); 65 66 private final Arch arch; 67 private final String name; 68 InstructionSet(Arch arch, String name)69 InstructionSet(Arch arch, String name) { 70 this.arch = arch; 71 this.name = name; 72 } 73 architecture()74 public Arch architecture() { 75 return arch; 76 } 77 78 /** 79 * Returns the current InstructionSet set by matching against the name fields above. 80 */ currentInstructionSet()81 public static InstructionSet currentInstructionSet() { 82 // Always returns one of the values from VMRuntime.ABI_TO_INSTRUCTION_SET_MAP. 83 String instructionSet = VMRuntime.getCurrentInstructionSet(); 84 for (InstructionSet set : values()) { 85 if (instructionSet.equals(set.name)) { 86 return set; 87 } 88 } 89 throw new IllegalStateException("Unknown instruction set: " + instructionSet); 90 } 91 } 92 CpuFeatures()93 private CpuFeatures() { 94 } 95 96 /** 97 * Returns true if this device has hardware AES support as determined by BoringSSL. 98 */ isAesHardwareAccelerated()99 public static boolean isAesHardwareAccelerated() { 100 try { 101 Class<?> nativeCrypto = Class.forName("com.android.org.conscrypt.NativeCrypto"); 102 Method EVP_has_aes_hardware = nativeCrypto.getDeclaredMethod("EVP_has_aes_hardware"); 103 EVP_has_aes_hardware.setAccessible(true); 104 return ((Integer) EVP_has_aes_hardware.invoke(null)) == 1; 105 } catch (ClassNotFoundException | NoSuchMethodException | SecurityException 106 | IllegalAccessException | IllegalArgumentException ignored) { 107 } catch (InvocationTargetException e) { 108 throw new IllegalArgumentException(e); 109 } 110 111 return false; 112 } 113 114 /** 115 * Returns true if this device should have hardware AES support based on CPU information. 116 * 117 * A return value of false means that acceleration isn't expected, but it may still be available 118 * e.g. via bridging to a native library in an emulated environment. 119 */ isKnownToSupportHardwareAes()120 public static boolean isKnownToSupportHardwareAes() { 121 Arch architecture = Arch.currentArch(); 122 InstructionSet instructionSet = InstructionSet.currentInstructionSet(); 123 124 if (!instructionSet.architecture().equals(architecture)) { 125 // Different architectures imply an emulated environment, so unable to determine if 126 // hardware acceleration is expected. Assume not. 127 return false; 128 } 129 130 if (architecture.equals(Arch.ARM)) { 131 // All ARM CPUs (32 and 64 bit) with the "aes" feature should have hardware AES. 132 return cpuFieldContainsAes("Features"); 133 } else if (instructionSet.equals(InstructionSet.X86_64)) { 134 // x86 CPUs with the "aes" flag and running in 64bit mode should have hardware AES. 135 // Hardware AES is not *expected* in 32bit mode, but may be available. 136 return cpuFieldContainsAes("flags"); 137 } 138 return false; 139 } 140 141 142 /** 143 * Returns true if any line in the output from /proc/cpuinfo matches the provided 144 * field name and contains the word "aes" in its list of values. 145 * 146 * Example line from /proc/cpuinfo: Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 147 */ cpuFieldContainsAes(String fieldName)148 private static boolean cpuFieldContainsAes(String fieldName) { 149 try (BufferedReader br = new BufferedReader(new FileReader("/proc/cpuinfo"))) { 150 String regex = "^" + fieldName + "\\s*:.*\\baes\\b.*"; 151 String line; 152 while ((line = br.readLine()) != null) { 153 if (line.matches(regex)) { 154 return true; 155 } 156 } 157 } catch (IOException ignored) { 158 } 159 return false; 160 } 161 } 162