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 Linux returns x86_64. 35 // 32bit Linux returns i686. 36 // Other host architectures can potentially return x86 or i386. 37 X86("^x86.*|i386|i686"), 38 // There is no 32bit riscv Android. 39 RISCV("^riscv64$"); 40 41 private final String machineRegex; 42 Arch(String machineRegex)43 Arch(String machineRegex) { 44 this.machineRegex = machineRegex; 45 } 46 47 /** 48 * Returns the architecture of this machine by matching against output from uname() 49 * against the regex for each known family. 50 */ currentArch()51 public static Arch currentArch() { 52 String machine = Os.uname().machine; 53 for (Arch type : values()) { 54 if (machine.matches(type.machineRegex)) { 55 return type; 56 } 57 } 58 throw new IllegalStateException("Unknown machine value: " + machine); 59 } 60 } 61 62 private enum InstructionSet { 63 ARM_32(Arch.ARM, "arm"), 64 ARM_64(Arch.ARM, "arm64"), 65 X86_32(Arch.X86, "x86"), 66 X86_64(Arch.X86, "x86_64"), 67 RISCV_64(Arch.RISCV, "riscv64"); 68 69 private final Arch arch; 70 private final String name; 71 InstructionSet(Arch arch, String name)72 InstructionSet(Arch arch, String name) { 73 this.arch = arch; 74 this.name = name; 75 } 76 architecture()77 public Arch architecture() { 78 return arch; 79 } 80 81 /** 82 * Returns the current InstructionSet set by matching against the name fields above. 83 */ currentInstructionSet()84 public static InstructionSet currentInstructionSet() { 85 // Always returns one of the values from VMRuntime.ABI_TO_INSTRUCTION_SET_MAP. 86 String instructionSet = VMRuntime.getCurrentInstructionSet(); 87 for (InstructionSet set : values()) { 88 if (instructionSet.equals(set.name)) { 89 return set; 90 } 91 } 92 throw new IllegalStateException("Unknown instruction set: " + instructionSet); 93 } 94 } 95 CpuFeatures()96 private CpuFeatures() { 97 } 98 99 /** 100 * Returns true if this device has hardware AES support as determined by BoringSSL. 101 */ isAesHardwareAccelerated()102 public static boolean isAesHardwareAccelerated() { 103 try { 104 Class<?> nativeCrypto = Class.forName("com.android.org.conscrypt.NativeCrypto"); 105 Method EVP_has_aes_hardware = nativeCrypto.getDeclaredMethod("EVP_has_aes_hardware"); 106 EVP_has_aes_hardware.setAccessible(true); 107 return ((Integer) EVP_has_aes_hardware.invoke(null)) == 1; 108 } catch (ClassNotFoundException | NoSuchMethodException | SecurityException 109 | IllegalAccessException | IllegalArgumentException ignored) { 110 } catch (InvocationTargetException e) { 111 throw new IllegalArgumentException(e); 112 } 113 114 return false; 115 } 116 117 /** 118 * Returns true if this device should have hardware AES support based on CPU information. 119 * 120 * A return value of false means that acceleration isn't expected, but it may still be available 121 * e.g. via bridging to a native library in an emulated environment. 122 */ isKnownToSupportHardwareAes()123 public static boolean isKnownToSupportHardwareAes() { 124 Arch architecture = Arch.currentArch(); 125 InstructionSet instructionSet = InstructionSet.currentInstructionSet(); 126 127 if (!instructionSet.architecture().equals(architecture)) { 128 // Different architectures imply an emulated environment, so unable to determine if 129 // hardware acceleration is expected. Assume not. 130 return false; 131 } 132 133 if (architecture.equals(Arch.ARM)) { 134 // All ARM CPUs (32 and 64 bit) with the "aes" feature should have hardware AES. 135 return cpuFieldContainsAes("Features"); 136 } else if (instructionSet.equals(InstructionSet.X86_64)) { 137 // x86 CPUs with the "aes" flag and running in 64bit mode should have hardware AES. 138 // Hardware AES is not *expected* in 32bit mode, but may be available. 139 return cpuFieldContainsAes("flags"); 140 } 141 return false; 142 } 143 144 145 /** 146 * Returns true if any line in the output from /proc/cpuinfo matches the provided 147 * field name and contains the word "aes" in its list of values. 148 * 149 * Example line from /proc/cpuinfo: Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 150 */ cpuFieldContainsAes(String fieldName)151 private static boolean cpuFieldContainsAes(String fieldName) { 152 try (BufferedReader br = new BufferedReader(new FileReader("/proc/cpuinfo"))) { 153 String regex = "^" + fieldName + "\\s*:.*\\baes\\b.*"; 154 String line; 155 while ((line = br.readLine()) != null) { 156 if (line.matches(regex)) { 157 return true; 158 } 159 } 160 } catch (IOException ignored) { 161 } 162 return false; 163 } 164 } 165