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