1 /*
2  * Copyright (C) 2019 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 android.keystore.cts;
18 
19 import android.os.SystemClock;
20 import android.security.keystore.KeyGenParameterSpec;
21 import android.test.AndroidTestCase;
22 
23 import androidx.test.platform.app.InstrumentationRegistry;
24 
25 import com.android.compatibility.common.util.DeviceReportLog;
26 import com.android.compatibility.common.util.ResultType;
27 import com.android.compatibility.common.util.ResultUnit;
28 
29 import java.security.KeyPair;
30 import java.security.KeyPairGenerator;
31 import java.security.KeyStore;
32 import java.security.cert.Certificate;
33 import java.util.ArrayList;
34 
35 import javax.crypto.KeyGenerator;
36 import javax.crypto.SecretKey;
37 
38 import android.os.Build;
39 
40 public class PerformanceTestBase extends AndroidTestCase {
41 
42     public static final long MS_PER_NS = 1000000L;
43     protected static final String TAG = "KeystorePerformanceTest";
44     private static final String REPORT_LOG_NAME = "CtsKeystoreTestCases";
45     /**
46      * Number of milliseconds to spend repeating a single test.
47      *
48      * <p>For each algorithm we run the test repeatedly up to a maximum of this time limit (or up to
49      * {@link #TEST_ITERATION_LIMIT} whichever is reached first), then report back the number of
50      * repetitions. We don't abort a test at the time limit but let it run to completion, so we're
51      * guaranteed to always get at least one repetition, even if it takes longer than the limit.
52      */
53     private static int TEST_TIME_LIMIT = 20000;
54 
55     /** Maximum number of iterations to run a single test. */
56     static int TEST_ITERATION_LIMIT = 20;
57 
measure(Measurable... measurables)58     protected void measure(Measurable... measurables) throws Exception {
59         ArrayList<PerformanceTestResult> results = new ArrayList<>();
60         results.ensureCapacity(measurables.length);
61         for (Measurable measurable : measurables) {
62             DeviceReportLog reportLog = new DeviceReportLog(REPORT_LOG_NAME, "performance_test");
63             PerformanceTestResult result = measure(measurable);
64 
65             reportLog.addValue(
66                     "test_environment",
67                     measurable.getEnvironment(),
68                     ResultType.NEUTRAL,
69                     ResultUnit.NONE);
70             reportLog.addValue(
71                     "test_name", measurable.getName(), ResultType.NEUTRAL, ResultUnit.NONE);
72             reportLog.addValue(
73                     "sample_count", result.getSampleCount(), ResultType.NEUTRAL, ResultUnit.COUNT);
74             reportLog.addValue(
75                     "setup_time", result.getSetupTime(), ResultType.LOWER_BETTER, ResultUnit.MS);
76             reportLog.addValue(
77                     "mean_time", result.getMean(), ResultType.LOWER_BETTER, ResultUnit.MS);
78             reportLog.addValue(
79                     "sample_std_dev",
80                     result.getSampleStdDev(),
81                     ResultType.LOWER_BETTER,
82                     ResultUnit.MS);
83             reportLog.addValue(
84                     "median_time", result.getMedian(), ResultType.LOWER_BETTER, ResultUnit.MS);
85             reportLog.addValue(
86                     "percentile_90_time",
87                     result.getPercentile(0.9),
88                     ResultType.LOWER_BETTER,
89                     ResultUnit.MS);
90             reportLog.addValue(
91                     "teardown_time",
92                     result.getTearDownTime(),
93                     ResultType.LOWER_BETTER,
94                     ResultUnit.MS);
95             reportLog.addValue(
96                     "teardown_time",
97                     result.getTearDownTime(),
98                     ResultType.LOWER_BETTER,
99                     ResultUnit.MS);
100             reportLog.submit(InstrumentationRegistry.getInstrumentation());
101         }
102     }
103 
measure(Measurable measurable)104     private PerformanceTestResult measure(Measurable measurable) throws Exception {
105         // One un-measured time through everything, to warm caches, etc.
106 
107         PerformanceTestResult result = new PerformanceTestResult();
108 
109         measurable.initialSetUp();
110         measurable.setUp();
111         measurable.measure();
112         measurable.tearDown();
113 
114         long runLimit = now() + TEST_TIME_LIMIT * MS_PER_NS;
115         while (now() < runLimit && result.getSampleCount() < TEST_ITERATION_LIMIT) {
116             long setupBegin = now();
117             measurable.setUp();
118             result.addSetupTime(now() - setupBegin);
119 
120             long runBegin = now();
121             measurable.measure();
122             result.addMeasurement(now() - runBegin);
123 
124             long tearDownBegin = now();
125             measurable.tearDown();
126             result.addTeardownTime(now() - tearDownBegin);
127         }
128         measurable.finalTearDown();
129 
130         return result;
131     }
132 
now()133     protected long now() {
134         return SystemClock.elapsedRealtimeNanos();
135     }
136 
137     public abstract class Measurable {
138 
Measurable()139         private Measurable() {}
140         ;
141 
getEnvironment()142         public abstract String getEnvironment();
143 
getName()144         public abstract String getName();
145 
initialSetUp()146         public void initialSetUp() throws Exception {}
147 
setUp()148         public void setUp() throws Exception {}
149 
measure()150         public abstract void measure() throws Exception;
151 
tearDown()152         public void tearDown() throws Exception {}
153 
finalTearDown()154         public void finalTearDown() throws Exception {}
155     }
156 
157     /** Base class for measuring Keystore operations. */
158     abstract class KeystoreMeasurable extends Measurable {
159         private final String mName;
160         private final byte[] mMessage;
161         private final KeystoreKeyGenerator mGenerator;
162 
KeystoreMeasurable( KeystoreKeyGenerator generator, String operation, int keySize, int messageSize)163         KeystoreMeasurable(
164                 KeystoreKeyGenerator generator, String operation, int keySize, int messageSize)
165                 throws Exception {
166             super();
167             mGenerator = generator;
168             if (messageSize < 0) {
169                 mName = (operation
170                                 + "/" + getAlgorithm()
171                                 + "/" + keySize);
172                 mMessage = null;
173             } else {
174                 mName = (operation
175                                 + "/" + getAlgorithm()
176                                 + "/" + keySize
177                                 + "/" + messageSize);
178                 mMessage = TestUtils.generateRandomMessage(messageSize);
179             }
180         }
181 
KeystoreMeasurable(KeystoreKeyGenerator generator, String operation, int keySize)182         KeystoreMeasurable(KeystoreKeyGenerator generator, String operation, int keySize)
183                 throws Exception {
184             this(generator, operation, keySize, -1);
185         }
186 
187         @Override
getEnvironment()188         public String getEnvironment() {
189             return mGenerator.getProvider() + "/" + Build.CPU_ABI;
190         }
191 
192         @Override
getName()193         public String getName() {
194             return mName;
195         }
196 
getMessage()197         byte[] getMessage() {
198             return mMessage;
199         }
200 
getAlgorithm()201         String getAlgorithm() {
202             return mGenerator.getAlgorithm();
203         }
204 
205         @Override
finalTearDown()206         public void finalTearDown() throws Exception {
207             deleteKey();
208         }
209 
deleteKey()210         public void deleteKey() throws Exception {
211             mGenerator.deleteKey();
212         }
213 
generateSecretKey()214         SecretKey generateSecretKey() throws Exception {
215             return mGenerator.getSecretKeyGenerator().generateKey();
216         }
217 
generateKeyPair()218         KeyPair generateKeyPair() throws Exception {
219             return mGenerator.getKeyPairGenerator().generateKeyPair();
220         }
221     }
222 
223     /**
224      * Measurable for generating key pairs.
225      *
226      * <p>This class is ignostic to the Keystore provider or key algorithm.
227      */
228     class KeystoreKeyPairGenMeasurable extends KeystoreMeasurable {
229 
KeystoreKeyPairGenMeasurable(KeystoreKeyGenerator keyGenerator, int keySize)230         KeystoreKeyPairGenMeasurable(KeystoreKeyGenerator keyGenerator, int keySize)
231                 throws Exception {
232             super(keyGenerator, "keygen", keySize);
233         }
234 
235         @Override
measure()236         public void measure() throws Exception {
237             generateKeyPair();
238         }
239 
240         @Override
tearDown()241         public void tearDown() throws Exception {
242             deleteKey();
243         }
244     }
245 
246     /**
247      * Measurable for generating a secret key.
248      *
249      * <p>This class is ignostic to the Keystore provider or key algorithm.
250      */
251     class KeystoreSecretKeyGenMeasurable extends KeystoreMeasurable {
252 
KeystoreSecretKeyGenMeasurable(KeystoreKeyGenerator keyGenerator, int keySize)253         KeystoreSecretKeyGenMeasurable(KeystoreKeyGenerator keyGenerator, int keySize)
254                 throws Exception {
255             super(keyGenerator, "keygen", keySize);
256         }
257 
258         @Override
measure()259         public void measure() throws Exception {
260             generateSecretKey();
261         }
262 
263         @Override
tearDown()264         public void tearDown() throws Exception {
265             deleteKey();
266         }
267     }
268 
269     /**
270      * Wrapper for generating Keystore keys.
271      *
272      * <p>Abstracts the provider and initilization of the generator.
273      */
274     abstract class KeystoreKeyGenerator {
275         private final String mAlgorithm;
276         private final String mProvider;
277         private KeyGenerator mSecretKeyGenerator = null;
278         private KeyPairGenerator mKeyPairGenerator = null;
279 
KeystoreKeyGenerator(String algorithm, String provider)280         KeystoreKeyGenerator(String algorithm, String provider) throws Exception {
281             mAlgorithm = algorithm;
282             mProvider = provider;
283         }
284 
KeystoreKeyGenerator(String algorithm)285         KeystoreKeyGenerator(String algorithm) throws Exception {
286             // This is a hack to get the default provider.
287             this(algorithm, KeyGenerator.getInstance("AES").getProvider().getName());
288         }
289 
getAlgorithm()290         String getAlgorithm() {
291             return mAlgorithm;
292         }
293 
getProvider()294         String getProvider() {
295             return mProvider;
296         }
297 
298         /** By default, deleteKey is a nop */
deleteKey()299         void deleteKey() throws Exception {}
300 
getSecretKeyGenerator()301         KeyGenerator getSecretKeyGenerator() throws Exception {
302             if (mSecretKeyGenerator == null) {
303                 mSecretKeyGenerator =
304                         KeyGenerator.getInstance(TestUtils.getKeyAlgorithm(mAlgorithm), mProvider);
305             }
306             return mSecretKeyGenerator;
307         }
308 
getKeyPairGenerator()309         KeyPairGenerator getKeyPairGenerator() throws Exception {
310             if (mKeyPairGenerator == null) {
311                 mKeyPairGenerator =
312                         KeyPairGenerator.getInstance(
313                                 TestUtils.getKeyAlgorithm(mAlgorithm), mProvider);
314             }
315             return mKeyPairGenerator;
316         }
317     }
318 
319     /**
320      * Wrapper for generating Android Keystore keys.
321      *
322      * <p>Provides Android Keystore specific functionality, like deleting the key.
323      */
324     abstract class AndroidKeystoreKeyGenerator extends KeystoreKeyGenerator {
325         private final KeyStore mKeyStore;
326         private final String KEY_ALIAS = "perf_key";
327 
AndroidKeystoreKeyGenerator(String algorithm)328         AndroidKeystoreKeyGenerator(String algorithm) throws Exception {
329             super(algorithm, TestUtils.EXPECTED_PROVIDER_NAME);
330             mKeyStore = KeyStore.getInstance(getProvider());
331             mKeyStore.load(null);
332         }
333 
334         @Override
deleteKey()335         void deleteKey() throws Exception {
336             mKeyStore.deleteEntry(KEY_ALIAS);
337         }
338 
getKeyGenParameterSpecBuilder(int purpose)339         KeyGenParameterSpec.Builder getKeyGenParameterSpecBuilder(int purpose) {
340             return new KeyGenParameterSpec.Builder(KEY_ALIAS, purpose);
341         }
342 
getCertificateChain()343         Certificate[] getCertificateChain() throws Exception {
344             return mKeyStore.getCertificateChain(KEY_ALIAS);
345         }
346     }
347 
348     /** Basic generator for KeyPairs that uses the default provider. */
349     class DefaultKeystoreKeyPairGenerator extends KeystoreKeyGenerator {
DefaultKeystoreKeyPairGenerator(String algorithm, int keySize)350         DefaultKeystoreKeyPairGenerator(String algorithm, int keySize) throws Exception {
351             super(algorithm);
352             getKeyPairGenerator().initialize(keySize);
353         }
354     }
355 
356     /** Basic generator for SecretKeys that uses the default provider. */
357     class DefaultKeystoreSecretKeyGenerator extends KeystoreKeyGenerator {
DefaultKeystoreSecretKeyGenerator(String algorithm, int keySize)358         DefaultKeystoreSecretKeyGenerator(String algorithm, int keySize) throws Exception {
359             super(algorithm);
360             getSecretKeyGenerator().init(keySize);
361         }
362     }
363 }
364