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