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