1 /* 2 * Copyright (C) 2015 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.util; 18 19 import static android.security.keystore.KeyProperties.DIGEST_NONE; 20 import static android.security.keystore.KeyProperties.DIGEST_SHA256; 21 import static android.security.keystore.KeyProperties.DIGEST_SHA512; 22 23 import static org.junit.Assert.assertEquals; 24 import static org.junit.Assert.assertFalse; 25 import static org.junit.Assert.assertNotNull; 26 import static org.junit.Assert.assertTrue; 27 import static org.junit.Assert.fail; 28 import static org.junit.Assume.assumeTrue; 29 30 import android.content.Context; 31 import android.content.pm.FeatureInfo; 32 import android.content.pm.PackageManager; 33 import android.os.Build; 34 import android.os.SystemProperties; 35 import android.security.keystore.KeyGenParameterSpec; 36 import android.security.keystore.KeyInfo; 37 import android.security.keystore.KeyProperties; 38 import android.security.keystore.KeyProtection; 39 import android.test.MoreAsserts; 40 import android.text.TextUtils; 41 42 import androidx.test.platform.app.InstrumentationRegistry; 43 44 import com.android.internal.util.HexDump; 45 46 import org.junit.Assert; 47 48 import java.io.ByteArrayOutputStream; 49 import java.io.File; 50 import java.io.IOException; 51 import java.io.InputStream; 52 import java.math.BigInteger; 53 import java.security.Key; 54 import java.security.KeyFactory; 55 import java.security.KeyPair; 56 import java.security.KeyPairGenerator; 57 import java.security.KeyStore; 58 import java.security.KeyStoreException; 59 import java.security.MessageDigest; 60 import java.security.NoSuchAlgorithmException; 61 import java.security.NoSuchProviderException; 62 import java.security.PrivateKey; 63 import java.security.PublicKey; 64 import java.security.SecureRandom; 65 import java.security.UnrecoverableEntryException; 66 import java.security.UnrecoverableKeyException; 67 import java.security.cert.Certificate; 68 import java.security.cert.CertificateFactory; 69 import java.security.cert.X509Certificate; 70 import java.security.interfaces.ECKey; 71 import java.security.interfaces.ECPrivateKey; 72 import java.security.interfaces.ECPublicKey; 73 import java.security.interfaces.RSAKey; 74 import java.security.interfaces.RSAPrivateKey; 75 import java.security.interfaces.RSAPublicKey; 76 import java.security.spec.ECParameterSpec; 77 import java.security.spec.EllipticCurve; 78 import java.security.spec.InvalidKeySpecException; 79 import java.security.spec.PKCS8EncodedKeySpec; 80 import java.util.ArrayList; 81 import java.util.Arrays; 82 import java.util.Collections; 83 import java.util.HashMap; 84 import java.util.List; 85 import java.util.Locale; 86 import java.util.Map; 87 88 import javax.crypto.SecretKey; 89 import javax.crypto.SecretKeyFactory; 90 import javax.crypto.spec.SecretKeySpec; 91 92 public class TestUtils { 93 94 public static final String EXPECTED_CRYPTO_OP_PROVIDER_NAME = "AndroidKeyStoreBCWorkaround"; 95 public static final String EXPECTED_PROVIDER_NAME = "AndroidKeyStore"; 96 97 public static final long DAY_IN_MILLIS = 1000 * 60 * 60 * 24; 98 TestUtils()99 private TestUtils() {} 100 getContext()101 private static Context getContext() { 102 return InstrumentationRegistry.getInstrumentation().getTargetContext(); 103 } 104 getFilesDir()105 public static File getFilesDir() { 106 Context context = getContext(); 107 final String packageName = context.getPackageName(); 108 return new File(context.getFilesDir(), packageName); 109 } 110 assumeStrongBox()111 static public void assumeStrongBox() { 112 PackageManager packageManager = 113 InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager(); 114 assumeTrue("Can only test if we have StrongBox", 115 packageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)); 116 } 117 118 /** 119 * Returns 0 if not implemented. Otherwise returns the feature version. 120 */ getFeatureVersionKeystore(Context appContext, boolean useStrongbox)121 public static int getFeatureVersionKeystore(Context appContext, boolean useStrongbox) { 122 if (useStrongbox) { 123 return getFeatureVersionKeystoreStrongBox(appContext); 124 } 125 return getFeatureVersionKeystore(appContext); 126 } 127 128 /** 129 * This function returns the valid digest algorithms supported for a Strongbox or default 130 * KeyMint implementation. The isStrongbox parameter specifies the underlying KeyMint 131 * implementation. If true, it indicates Strongbox KeyMint, otherwise TEE/Software KeyMint 132 * is assumed. 133 */ getDigestsForKeyMintImplementation( boolean isStrongbox)134 public static @KeyProperties.DigestEnum String[] getDigestsForKeyMintImplementation( 135 boolean isStrongbox) { 136 if (isStrongbox) { 137 return new String[]{DIGEST_NONE, DIGEST_SHA256}; 138 } 139 return new String[]{DIGEST_NONE, DIGEST_SHA256, DIGEST_SHA512}; 140 } 141 142 // Returns 0 if not implemented. Otherwise returns the feature version. 143 // getFeatureVersionKeystore(Context appContext)144 public static int getFeatureVersionKeystore(Context appContext) { 145 PackageManager pm = appContext.getPackageManager(); 146 147 int featureVersionFromPm = 0; 148 if (pm.hasSystemFeature(PackageManager.FEATURE_HARDWARE_KEYSTORE)) { 149 FeatureInfo info = null; 150 FeatureInfo[] infos = pm.getSystemAvailableFeatures(); 151 for (int n = 0; n < infos.length; n++) { 152 FeatureInfo i = infos[n]; 153 if (i.name.equals(PackageManager.FEATURE_HARDWARE_KEYSTORE)) { 154 info = i; 155 break; 156 } 157 } 158 if (info != null) { 159 featureVersionFromPm = info.version; 160 } 161 } 162 163 return featureVersionFromPm; 164 } 165 166 // Returns 0 if not implemented. Otherwise returns the feature version. 167 // getFeatureVersionKeystoreStrongBox(Context appContext)168 public static int getFeatureVersionKeystoreStrongBox(Context appContext) { 169 PackageManager pm = appContext.getPackageManager(); 170 171 int featureVersionFromPm = 0; 172 if (pm.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)) { 173 FeatureInfo info = null; 174 FeatureInfo[] infos = pm.getSystemAvailableFeatures(); 175 for (int n = 0; n < infos.length; n++) { 176 FeatureInfo i = infos[n]; 177 if (i.name.equals(PackageManager.FEATURE_STRONGBOX_KEYSTORE)) { 178 info = i; 179 break; 180 } 181 } 182 if (info != null) { 183 featureVersionFromPm = info.version; 184 } 185 } 186 187 return featureVersionFromPm; 188 } 189 190 /** 191 * Asserts that the given key is supported by KeyMint after a given (inclusive) version. The 192 * assertion checks that: 193 * 1. The current keystore feature version is less than <code>version</code> and 194 * <code>keyInfo</code> is implemented in software. 195 * OR 196 * 2. The current keystore feature version is greater than or equal to <code>version</code>, 197 * and <code>keyInfo</code> is implemented by KeyMint. 198 */ assertImplementedByKeyMintAfter(KeyInfo keyInfo, int version)199 public static void assertImplementedByKeyMintAfter(KeyInfo keyInfo, int version) 200 throws Exception { 201 // ECDSA keys are always implemented in keymaster since v1, so we can use an ECDSA 202 // to check whether the backend is implemented in HW or is SW-emulated. 203 int ecdsaSecurityLevel; 204 try { 205 KeyPairGenerator kpg = 206 KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore"); 207 kpg.initialize( 208 new KeyGenParameterSpec.Builder("ecdsa-test-key", 209 KeyProperties.PURPOSE_SIGN).build()); 210 KeyPair kp = kpg.generateKeyPair(); 211 KeyFactory factory = KeyFactory.getInstance(kp.getPrivate().getAlgorithm(), 212 "AndroidKeyStore"); 213 ecdsaSecurityLevel = factory.getKeySpec(kp.getPrivate(), 214 KeyInfo.class).getSecurityLevel(); 215 } finally { 216 KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); 217 keyStore.load(null); 218 keyStore.deleteEntry("ecdsa-test-key"); 219 } 220 221 Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); 222 if (getFeatureVersionKeystore(context) >= version) { 223 Assert.assertEquals(keyInfo.getSecurityLevel(), ecdsaSecurityLevel); 224 } else { 225 Assert.assertEquals(keyInfo.getSecurityLevel(), 226 KeyProperties.SECURITY_LEVEL_SOFTWARE); 227 } 228 } 229 230 231 /** 232 * Returns whether 3DES KeyStore tests should run on this device. 3DES support was added in 233 * KeyMaster 4.0 and there should be no software fallback on earlier KeyMaster versions. 234 */ supports3DES()235 public static boolean supports3DES() { 236 return "true".equals(SystemProperties.get("ro.hardware.keystore_desede")); 237 } 238 239 /** 240 * Returns VSR API level. 241 */ getVendorApiLevel()242 public static int getVendorApiLevel() { 243 int vendorApiLevel = SystemProperties.getInt("ro.vendor.api_level", -1); 244 if (vendorApiLevel != -1) { 245 return vendorApiLevel; 246 } 247 248 // Android S and older devices do not define ro.vendor.api_level 249 vendorApiLevel = SystemProperties.getInt("ro.board.api_level", -1); 250 if (vendorApiLevel == -1) { 251 vendorApiLevel = SystemProperties.getInt("ro.board.first_api_level", -1); 252 } 253 254 int productApiLevel = SystemProperties.getInt("ro.product.first_api_level", -1); 255 if (productApiLevel == -1) { 256 productApiLevel = Build.VERSION.SDK_INT; 257 } 258 259 // VSR API level is the minimum of vendorApiLevel and productApiLevel. 260 if (vendorApiLevel == -1 || vendorApiLevel > productApiLevel) { 261 return productApiLevel; 262 } 263 return vendorApiLevel; 264 } 265 266 /** 267 * Returns whether the device has a StrongBox backed KeyStore. 268 */ hasStrongBox(Context context)269 public static boolean hasStrongBox(Context context) { 270 return context.getPackageManager() 271 .hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE); 272 } 273 274 /** 275 * Asserts the the key algorithm and algorithm-specific parameters of the two keys in the 276 * provided pair match. 277 */ assertKeyPairSelfConsistent(KeyPair keyPair)278 public static void assertKeyPairSelfConsistent(KeyPair keyPair) { 279 assertKeyPairSelfConsistent(keyPair.getPublic(), keyPair.getPrivate()); 280 } 281 282 /** 283 * Asserts the the key algorithm and public algorithm-specific parameters of the two provided 284 * keys match. 285 */ assertKeyPairSelfConsistent(PublicKey publicKey, PrivateKey privateKey)286 public static void assertKeyPairSelfConsistent(PublicKey publicKey, PrivateKey privateKey) { 287 assertNotNull(publicKey); 288 assertNotNull(privateKey); 289 assertEquals(publicKey.getAlgorithm(), privateKey.getAlgorithm()); 290 String keyAlgorithm = publicKey.getAlgorithm(); 291 if ("EC".equalsIgnoreCase(keyAlgorithm)) { 292 assertTrue("EC public key must be instanceof ECKey: " 293 + publicKey.getClass().getName(), 294 publicKey instanceof ECKey); 295 assertTrue("EC private key must be instanceof ECKey: " 296 + privateKey.getClass().getName(), 297 privateKey instanceof ECKey); 298 assertECParameterSpecEqualsIgnoreSeedIfNotPresent( 299 "Private key must have the same EC parameters as public key", 300 ((ECKey) publicKey).getParams(), ((ECKey) privateKey).getParams()); 301 } else if ("RSA".equalsIgnoreCase(keyAlgorithm)) { 302 assertTrue("RSA public key must be instance of RSAKey: " 303 + publicKey.getClass().getName(), 304 publicKey instanceof RSAKey); 305 assertTrue("RSA private key must be instance of RSAKey: " 306 + privateKey.getClass().getName(), 307 privateKey instanceof RSAKey); 308 assertEquals("Private and public key must have the same RSA modulus", 309 ((RSAKey) publicKey).getModulus(), ((RSAKey) privateKey).getModulus()); 310 } else if ("XDH".equalsIgnoreCase(keyAlgorithm)) { 311 // TODO This block should verify that public and private keys are instance of 312 // java.security.interfaces.XECKey, And below code should be uncommented once 313 // com.android.org.conscrypt.OpenSSLX25519PublicKey implements XECKey (b/214203951) 314 /*assertTrue("XDH public key must be instance of XECKey: " 315 + publicKey.getClass().getName(), 316 publicKey instanceof XECKey); 317 assertTrue("XDH private key must be instance of XECKey: " 318 + privateKey.getClass().getName(), 319 privateKey instanceof XECKey);*/ 320 assertFalse("XDH public key must not be instance of RSAKey: " 321 + publicKey.getClass().getName(), 322 publicKey instanceof RSAKey); 323 assertFalse("XDH private key must not be instance of RSAKey: " 324 + privateKey.getClass().getName(), 325 privateKey instanceof RSAKey); 326 assertFalse("XDH public key must not be instanceof ECKey: " 327 + publicKey.getClass().getName(), 328 publicKey instanceof ECKey); 329 assertFalse("XDH private key must not be instanceof ECKey: " 330 + privateKey.getClass().getName(), 331 privateKey instanceof ECKey); 332 } else { 333 fail("Unsuported key algorithm: " + keyAlgorithm); 334 } 335 } 336 getKeySizeBits(Key key)337 public static int getKeySizeBits(Key key) { 338 if (key instanceof ECKey) { 339 return ((ECKey) key).getParams().getCurve().getField().getFieldSize(); 340 } else if (key instanceof RSAKey) { 341 return ((RSAKey) key).getModulus().bitLength(); 342 } else { 343 throw new IllegalArgumentException("Unsupported key type: " + key.getClass()); 344 } 345 } 346 assertKeySize(int expectedSizeBits, KeyPair keyPair)347 public static void assertKeySize(int expectedSizeBits, KeyPair keyPair) { 348 assertEquals(expectedSizeBits, getKeySizeBits(keyPair.getPrivate())); 349 assertEquals(expectedSizeBits, getKeySizeBits(keyPair.getPublic())); 350 } 351 352 /** 353 * Asserts that the provided key pair is an Android Keystore key pair stored under the provided 354 * alias. 355 */ assertKeyStoreKeyPair(KeyStore keyStore, String alias, KeyPair keyPair)356 public static void assertKeyStoreKeyPair(KeyStore keyStore, String alias, KeyPair keyPair) { 357 assertKeyMaterialExportable(keyPair.getPublic()); 358 assertKeyMaterialNotExportable(keyPair.getPrivate()); 359 assertTransparentKey(keyPair.getPublic()); 360 assertOpaqueKey(keyPair.getPrivate()); 361 362 KeyStore.Entry entry; 363 Certificate cert; 364 try { 365 entry = keyStore.getEntry(alias, null); 366 cert = keyStore.getCertificate(alias); 367 } catch (KeyStoreException | UnrecoverableEntryException | NoSuchAlgorithmException e) { 368 throw new RuntimeException("Failed to load entry: " + alias, e); 369 } 370 assertNotNull(entry); 371 372 assertTrue(entry instanceof KeyStore.PrivateKeyEntry); 373 KeyStore.PrivateKeyEntry privEntry = (KeyStore.PrivateKeyEntry) entry; 374 assertEquals(cert, privEntry.getCertificate()); 375 assertTrue("Certificate must be an X.509 certificate: " + cert.getClass(), 376 cert instanceof X509Certificate); 377 final X509Certificate x509Cert = (X509Certificate) cert; 378 379 PrivateKey keystorePrivateKey = privEntry.getPrivateKey(); 380 PublicKey keystorePublicKey = cert.getPublicKey(); 381 assertEquals(keyPair.getPrivate(), keystorePrivateKey); 382 assertTrue("Key1:\n" + HexDump.dumpHexString(keyPair.getPublic().getEncoded()) 383 + "\nKey2:\n" + HexDump.dumpHexString(keystorePublicKey.getEncoded()) + "\n", 384 Arrays.equals(keyPair.getPublic().getEncoded(), keystorePublicKey.getEncoded())); 385 386 387 assertEquals( 388 "Public key used to sign certificate should have the same algorithm as in KeyPair", 389 keystorePublicKey.getAlgorithm(), x509Cert.getPublicKey().getAlgorithm()); 390 391 Certificate[] chain = privEntry.getCertificateChain(); 392 if (chain.length == 0) { 393 fail("Empty certificate chain"); 394 return; 395 } 396 assertEquals(cert, chain[0]); 397 } 398 399 assertKeyMaterialExportable(Key key)400 private static void assertKeyMaterialExportable(Key key) { 401 if (key instanceof PublicKey) { 402 assertEquals("X.509", key.getFormat()); 403 } else if (key instanceof PrivateKey) { 404 assertEquals("PKCS#8", key.getFormat()); 405 } else if (key instanceof SecretKey) { 406 assertEquals("RAW", key.getFormat()); 407 } else { 408 fail("Unsupported key type: " + key.getClass().getName()); 409 } 410 byte[] encodedForm = key.getEncoded(); 411 assertNotNull(encodedForm); 412 if (encodedForm.length == 0) { 413 fail("Empty encoded form"); 414 } 415 } 416 assertKeyMaterialNotExportable(Key key)417 private static void assertKeyMaterialNotExportable(Key key) { 418 assertEquals(null, key.getFormat()); 419 assertEquals(null, key.getEncoded()); 420 } 421 assertOpaqueKey(Key key)422 private static void assertOpaqueKey(Key key) { 423 assertFalse(key.getClass().getName() + " is a transparent key", isTransparentKey(key)); 424 } 425 assertTransparentKey(Key key)426 private static void assertTransparentKey(Key key) { 427 assertTrue(key.getClass().getName() + " is not a transparent key", isTransparentKey(key)); 428 } 429 isTransparentKey(Key key)430 private static boolean isTransparentKey(Key key) { 431 if (key instanceof PrivateKey) { 432 return (key instanceof ECPrivateKey) || (key instanceof RSAPrivateKey); 433 } else if (key instanceof PublicKey) { 434 return (key instanceof ECPublicKey) || (key instanceof RSAPublicKey); 435 } else if (key instanceof SecretKey) { 436 return (key instanceof SecretKeySpec); 437 } else { 438 throw new IllegalArgumentException("Unsupported key type: " + key.getClass().getName()); 439 } 440 } 441 assertECParameterSpecEqualsIgnoreSeedIfNotPresent( ECParameterSpec expected, ECParameterSpec actual)442 public static void assertECParameterSpecEqualsIgnoreSeedIfNotPresent( 443 ECParameterSpec expected, ECParameterSpec actual) { 444 assertECParameterSpecEqualsIgnoreSeedIfNotPresent(null, expected, actual); 445 } 446 assertECParameterSpecEqualsIgnoreSeedIfNotPresent(String message, ECParameterSpec expected, ECParameterSpec actual)447 public static void assertECParameterSpecEqualsIgnoreSeedIfNotPresent(String message, 448 ECParameterSpec expected, ECParameterSpec actual) { 449 EllipticCurve expectedCurve = expected.getCurve(); 450 EllipticCurve actualCurve = actual.getCurve(); 451 String msgPrefix = (message != null) ? message + ": " : ""; 452 assertEquals(msgPrefix + "curve field", expectedCurve.getField(), actualCurve.getField()); 453 assertEquals(msgPrefix + "curve A", expectedCurve.getA(), actualCurve.getA()); 454 assertEquals(msgPrefix + "curve B", expectedCurve.getB(), actualCurve.getB()); 455 assertEquals(msgPrefix + "order", expected.getOrder(), actual.getOrder()); 456 assertEquals(msgPrefix + "generator", 457 expected.getGenerator(), actual.getGenerator()); 458 assertEquals(msgPrefix + "cofactor", expected.getCofactor(), actual.getCofactor()); 459 460 // If present, the seed must be the same 461 byte[] expectedSeed = expectedCurve.getSeed(); 462 byte[] actualSeed = expectedCurve.getSeed(); 463 if ((expectedSeed != null) && (actualSeed != null)) { 464 MoreAsserts.assertEquals(expectedSeed, actualSeed); 465 } 466 } 467 getKeyInfo(Key key)468 public static KeyInfo getKeyInfo(Key key) throws InvalidKeySpecException, NoSuchAlgorithmException, 469 NoSuchProviderException { 470 if ((key instanceof PrivateKey) || (key instanceof PublicKey)) { 471 return KeyFactory.getInstance(key.getAlgorithm(), "AndroidKeyStore") 472 .getKeySpec(key, KeyInfo.class); 473 } else if (key instanceof SecretKey) { 474 return (KeyInfo) SecretKeyFactory.getInstance(key.getAlgorithm(), "AndroidKeyStore") 475 .getKeySpec((SecretKey) key, KeyInfo.class); 476 } else { 477 throw new IllegalArgumentException("Unexpected key type: " + key.getClass()); 478 } 479 } 480 assertContentsInAnyOrder(Iterable<T> actual, T... expected)481 public static <T> void assertContentsInAnyOrder(Iterable<T> actual, T... expected) { 482 assertContentsInAnyOrder(null, actual, expected); 483 } 484 assertContentsInAnyOrder(String message, Iterable<T> actual, T... expected)485 public static <T> void assertContentsInAnyOrder(String message, Iterable<T> actual, T... expected) { 486 Map<T, Integer> actualFreq = getFrequencyTable(actual); 487 Map<T, Integer> expectedFreq = getFrequencyTable(expected); 488 if (actualFreq.equals(expectedFreq)) { 489 return; 490 } 491 492 Map<T, Integer> extraneousFreq = new HashMap<T, Integer>(); 493 for (Map.Entry<T, Integer> actualEntry : actualFreq.entrySet()) { 494 int actualCount = actualEntry.getValue(); 495 Integer expectedCount = expectedFreq.get(actualEntry.getKey()); 496 int diff = actualCount - ((expectedCount != null) ? expectedCount : 0); 497 if (diff > 0) { 498 extraneousFreq.put(actualEntry.getKey(), diff); 499 } 500 } 501 502 Map<T, Integer> missingFreq = new HashMap<T, Integer>(); 503 for (Map.Entry<T, Integer> expectedEntry : expectedFreq.entrySet()) { 504 int expectedCount = expectedEntry.getValue(); 505 Integer actualCount = actualFreq.get(expectedEntry.getKey()); 506 int diff = expectedCount - ((actualCount != null) ? actualCount : 0); 507 if (diff > 0) { 508 missingFreq.put(expectedEntry.getKey(), diff); 509 } 510 } 511 512 List<T> extraneous = frequencyTableToValues(extraneousFreq); 513 List<T> missing = frequencyTableToValues(missingFreq); 514 StringBuilder result = new StringBuilder(); 515 String delimiter = ""; 516 if (message != null) { 517 result.append(message).append("."); 518 delimiter = " "; 519 } 520 if (!missing.isEmpty()) { 521 result.append(delimiter).append("missing: " + missing); 522 delimiter = ", "; 523 } 524 if (!extraneous.isEmpty()) { 525 result.append(delimiter).append("extraneous: " + extraneous); 526 } 527 fail(result.toString()); 528 } 529 getFrequencyTable(Iterable<T> values)530 private static <T> Map<T, Integer> getFrequencyTable(Iterable<T> values) { 531 Map<T, Integer> result = new HashMap<T, Integer>(); 532 for (T value : values) { 533 Integer count = result.get(value); 534 if (count == null) { 535 count = 1; 536 } else { 537 count++; 538 } 539 result.put(value, count); 540 } 541 return result; 542 } 543 getFrequencyTable(T... values)544 private static <T> Map<T, Integer> getFrequencyTable(T... values) { 545 Map<T, Integer> result = new HashMap<T, Integer>(); 546 for (T value : values) { 547 Integer count = result.get(value); 548 if (count == null) { 549 count = 1; 550 } else { 551 count++; 552 } 553 result.put(value, count); 554 } 555 return result; 556 } 557 558 @SuppressWarnings("rawtypes") frequencyTableToValues(Map<T, Integer> table)559 private static <T> List<T> frequencyTableToValues(Map<T, Integer> table) { 560 if (table.isEmpty()) { 561 return Collections.emptyList(); 562 } 563 564 List<T> result = new ArrayList<T>(); 565 boolean comparableValues = true; 566 for (Map.Entry<T, Integer> entry : table.entrySet()) { 567 T value = entry.getKey(); 568 if (!(value instanceof Comparable)) { 569 comparableValues = false; 570 } 571 int frequency = entry.getValue(); 572 for (int i = 0; i < frequency; i++) { 573 result.add(value); 574 } 575 } 576 577 if (comparableValues) { 578 sortAssumingComparable(result); 579 } 580 return result; 581 } 582 583 @SuppressWarnings({"rawtypes", "unchecked"}) sortAssumingComparable(List<?> values)584 private static void sortAssumingComparable(List<?> values) { 585 Collections.sort((List<Comparable>)values); 586 } 587 toLowerCase(String... values)588 public static String[] toLowerCase(String... values) { 589 if (values == null) { 590 return null; 591 } 592 String[] result = new String[values.length]; 593 for (int i = 0; i < values.length; i++) { 594 String value = values[i]; 595 result[i] = (value != null) ? value.toLowerCase() : null; 596 } 597 return result; 598 } 599 getRawResPrivateKey(Context context, int resId)600 public static PrivateKey getRawResPrivateKey(Context context, int resId) throws Exception { 601 byte[] pkcs8EncodedForm; 602 try (InputStream in = context.getResources().openRawResource(resId)) { 603 pkcs8EncodedForm = drain(in); 604 } 605 PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(pkcs8EncodedForm); 606 607 String[] algorithms = new String[] {"EC", "RSA", "XDH"}; 608 for (String algo : algorithms) { 609 try { 610 return KeyFactory.getInstance(algo).generatePrivate(privateKeySpec); 611 } catch (InvalidKeySpecException e) { 612 } 613 } 614 throw new InvalidKeySpecException( 615 "The key should be one of " + Arrays.toString(algorithms)); 616 } 617 getRawResX509Certificate(Context context, int resId)618 public static X509Certificate getRawResX509Certificate(Context context, int resId) throws Exception { 619 try (InputStream in = context.getResources().openRawResource(resId)) { 620 return (X509Certificate) CertificateFactory.getInstance("X.509") 621 .generateCertificate(in); 622 } 623 } 624 importIntoAndroidKeyStore( String alias, PrivateKey privateKey, Certificate certificate, KeyProtection keyProtection)625 public static KeyPair importIntoAndroidKeyStore( 626 String alias, 627 PrivateKey privateKey, 628 Certificate certificate, 629 KeyProtection keyProtection) throws Exception { 630 KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); 631 keyStore.load(null); 632 keyStore.setEntry(alias, 633 new KeyStore.PrivateKeyEntry(privateKey, new Certificate[] {certificate}), 634 keyProtection); 635 return new KeyPair( 636 keyStore.getCertificate(alias).getPublicKey(), 637 (PrivateKey) keyStore.getKey(alias, null)); 638 } 639 importIntoAndroidKeyStore( String alias, SecretKey key, KeyProtection keyProtection)640 public static ImportedKey importIntoAndroidKeyStore( 641 String alias, 642 SecretKey key, 643 KeyProtection keyProtection) throws Exception { 644 KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); 645 keyStore.load(null); 646 keyStore.setEntry(alias, 647 new KeyStore.SecretKeyEntry(key), 648 keyProtection); 649 return new ImportedKey(alias, key, (SecretKey) keyStore.getKey(alias, null)); 650 } 651 importIntoAndroidKeyStore( String alias, Context context, int privateResId, int certResId, KeyProtection params)652 public static ImportedKey importIntoAndroidKeyStore( 653 String alias, Context context, int privateResId, int certResId, KeyProtection params) 654 throws Exception { 655 Certificate originalCert = TestUtils.getRawResX509Certificate(context, certResId); 656 PublicKey originalPublicKey = originalCert.getPublicKey(); 657 PrivateKey originalPrivateKey = TestUtils.getRawResPrivateKey(context, privateResId); 658 659 // Check that the domain parameters match between the private key and the public key. This 660 // is to catch accidental errors where a test provides the wrong resource ID as one of the 661 // parameters. 662 if (!originalPublicKey.getAlgorithm().equalsIgnoreCase(originalPrivateKey.getAlgorithm())) { 663 throw new IllegalArgumentException("Key algorithm mismatch." 664 + " Public: " + originalPublicKey.getAlgorithm() 665 + ", private: " + originalPrivateKey.getAlgorithm()); 666 } 667 assertKeyPairSelfConsistent(originalPublicKey, originalPrivateKey); 668 669 KeyPair keystoreBacked = TestUtils.importIntoAndroidKeyStore( 670 alias, originalPrivateKey, originalCert, 671 params); 672 assertKeyPairSelfConsistent(keystoreBacked); 673 assertKeyPairSelfConsistent(keystoreBacked.getPublic(), originalPrivateKey); 674 return new ImportedKey( 675 alias, 676 new KeyPair(originalCert.getPublicKey(), originalPrivateKey), 677 keystoreBacked); 678 } 679 680 /** Returns true if a key with the given alias exists. */ keyExists(String alias)681 public static boolean keyExists(String alias) throws Exception { 682 KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); 683 keyStore.load(null); 684 try { 685 return keyStore.getEntry(alias, null) != null; 686 } catch (UnrecoverableKeyException e) { 687 return false; 688 } 689 } 690 drain(InputStream in)691 public static byte[] drain(InputStream in) throws IOException { 692 ByteArrayOutputStream result = new ByteArrayOutputStream(); 693 byte[] buffer = new byte[16 * 1024]; 694 int chunkSize; 695 while ((chunkSize = in.read(buffer)) != -1) { 696 result.write(buffer, 0, chunkSize); 697 } 698 return result.toByteArray(); 699 } 700 buildUpon(KeyProtection params)701 public static KeyProtection.Builder buildUpon(KeyProtection params) { 702 return buildUponInternal(params, null); 703 } 704 buildUpon(KeyProtection params, int newPurposes)705 public static KeyProtection.Builder buildUpon(KeyProtection params, int newPurposes) { 706 return buildUponInternal(params, newPurposes); 707 } 708 buildUpon( KeyProtection.Builder builder)709 public static KeyProtection.Builder buildUpon( 710 KeyProtection.Builder builder) { 711 return buildUponInternal(builder.build(), null); 712 } 713 buildUpon( KeyProtection.Builder builder, int newPurposes)714 public static KeyProtection.Builder buildUpon( 715 KeyProtection.Builder builder, int newPurposes) { 716 return buildUponInternal(builder.build(), newPurposes); 717 } 718 buildUponInternal( KeyProtection spec, Integer newPurposes)719 private static KeyProtection.Builder buildUponInternal( 720 KeyProtection spec, Integer newPurposes) { 721 int purposes = (newPurposes == null) ? spec.getPurposes() : newPurposes; 722 KeyProtection.Builder result = new KeyProtection.Builder(purposes); 723 result.setBlockModes(spec.getBlockModes()); 724 if (spec.isDigestsSpecified()) { 725 result.setDigests(spec.getDigests()); 726 } 727 result.setEncryptionPaddings(spec.getEncryptionPaddings()); 728 result.setSignaturePaddings(spec.getSignaturePaddings()); 729 result.setKeyValidityStart(spec.getKeyValidityStart()); 730 result.setKeyValidityForOriginationEnd(spec.getKeyValidityForOriginationEnd()); 731 result.setKeyValidityForConsumptionEnd(spec.getKeyValidityForConsumptionEnd()); 732 result.setRandomizedEncryptionRequired(spec.isRandomizedEncryptionRequired()); 733 result.setUserAuthenticationRequired(spec.isUserAuthenticationRequired()); 734 result.setUserAuthenticationValidityDurationSeconds( 735 spec.getUserAuthenticationValidityDurationSeconds()); 736 result.setBoundToSpecificSecureUserId(spec.getBoundToSpecificSecureUserId()); 737 return result; 738 } 739 buildUpon(KeyGenParameterSpec spec)740 public static KeyGenParameterSpec.Builder buildUpon(KeyGenParameterSpec spec) { 741 return buildUponInternal(spec, null); 742 } 743 buildUpon(KeyGenParameterSpec spec, int newPurposes)744 public static KeyGenParameterSpec.Builder buildUpon(KeyGenParameterSpec spec, int newPurposes) { 745 return buildUponInternal(spec, newPurposes); 746 } 747 buildUpon( KeyGenParameterSpec.Builder builder)748 public static KeyGenParameterSpec.Builder buildUpon( 749 KeyGenParameterSpec.Builder builder) { 750 return buildUponInternal(builder.build(), null); 751 } 752 buildUpon( KeyGenParameterSpec.Builder builder, int newPurposes)753 public static KeyGenParameterSpec.Builder buildUpon( 754 KeyGenParameterSpec.Builder builder, int newPurposes) { 755 return buildUponInternal(builder.build(), newPurposes); 756 } 757 buildUponInternal( KeyGenParameterSpec spec, Integer newPurposes)758 private static KeyGenParameterSpec.Builder buildUponInternal( 759 KeyGenParameterSpec spec, Integer newPurposes) { 760 int purposes = (newPurposes == null) ? spec.getPurposes() : newPurposes; 761 KeyGenParameterSpec.Builder result = 762 new KeyGenParameterSpec.Builder(spec.getKeystoreAlias(), purposes); 763 if (spec.getKeySize() >= 0) { 764 result.setKeySize(spec.getKeySize()); 765 } 766 if (spec.getAlgorithmParameterSpec() != null) { 767 result.setAlgorithmParameterSpec(spec.getAlgorithmParameterSpec()); 768 } 769 result.setCertificateNotBefore(spec.getCertificateNotBefore()); 770 result.setCertificateNotAfter(spec.getCertificateNotAfter()); 771 result.setCertificateSerialNumber(spec.getCertificateSerialNumber()); 772 result.setCertificateSubject(spec.getCertificateSubject()); 773 result.setBlockModes(spec.getBlockModes()); 774 if (spec.isDigestsSpecified()) { 775 result.setDigests(spec.getDigests()); 776 } 777 result.setEncryptionPaddings(spec.getEncryptionPaddings()); 778 result.setSignaturePaddings(spec.getSignaturePaddings()); 779 result.setKeyValidityStart(spec.getKeyValidityStart()); 780 result.setKeyValidityForOriginationEnd(spec.getKeyValidityForOriginationEnd()); 781 result.setKeyValidityForConsumptionEnd(spec.getKeyValidityForConsumptionEnd()); 782 result.setRandomizedEncryptionRequired(spec.isRandomizedEncryptionRequired()); 783 result.setUserAuthenticationRequired(spec.isUserAuthenticationRequired()); 784 result.setUserAuthenticationValidityDurationSeconds( 785 spec.getUserAuthenticationValidityDurationSeconds()); 786 return result; 787 } 788 getKeyPairForKeyAlgorithm(String keyAlgorithm, Iterable<KeyPair> keyPairs)789 public static KeyPair getKeyPairForKeyAlgorithm(String keyAlgorithm, Iterable<KeyPair> keyPairs) { 790 for (KeyPair keyPair : keyPairs) { 791 if (keyAlgorithm.equalsIgnoreCase(keyPair.getPublic().getAlgorithm())) { 792 return keyPair; 793 } 794 } 795 throw new IllegalArgumentException("No KeyPair for key algorithm " + keyAlgorithm); 796 } 797 getKeyForKeyAlgorithm(String keyAlgorithm, Iterable<? extends Key> keys)798 public static Key getKeyForKeyAlgorithm(String keyAlgorithm, Iterable<? extends Key> keys) { 799 for (Key key : keys) { 800 if (keyAlgorithm.equalsIgnoreCase(key.getAlgorithm())) { 801 return key; 802 } 803 } 804 throw new IllegalArgumentException("No Key for key algorithm " + keyAlgorithm); 805 } 806 generateLargeKatMsg(byte[] seed, int msgSizeBytes)807 public static byte[] generateLargeKatMsg(byte[] seed, int msgSizeBytes) throws Exception { 808 byte[] result = new byte[msgSizeBytes]; 809 MessageDigest digest = MessageDigest.getInstance("SHA-512"); 810 int resultOffset = 0; 811 int resultRemaining = msgSizeBytes; 812 while (resultRemaining > 0) { 813 seed = digest.digest(seed); 814 int chunkSize = Math.min(seed.length, resultRemaining); 815 System.arraycopy(seed, 0, result, resultOffset, chunkSize); 816 resultOffset += chunkSize; 817 resultRemaining -= chunkSize; 818 } 819 return result; 820 } 821 leftPadWithZeroBytes(byte[] array, int length)822 public static byte[] leftPadWithZeroBytes(byte[] array, int length) { 823 if (array.length >= length) { 824 return array; 825 } 826 byte[] result = new byte[length]; 827 System.arraycopy(array, 0, result, result.length - array.length, array.length); 828 return result; 829 } 830 contains(int[] array, int value)831 public static boolean contains(int[] array, int value) { 832 for (int element : array) { 833 if (element == value) { 834 return true; 835 } 836 } 837 return false; 838 } 839 isHmacAlgorithm(String algorithm)840 public static boolean isHmacAlgorithm(String algorithm) { 841 return algorithm.toUpperCase(Locale.US).startsWith("HMAC"); 842 } 843 getHmacAlgorithmDigest(String algorithm)844 public static String getHmacAlgorithmDigest(String algorithm) { 845 String algorithmUpperCase = algorithm.toUpperCase(Locale.US); 846 if (!algorithmUpperCase.startsWith("HMAC")) { 847 return null; 848 } 849 String result = algorithmUpperCase.substring("HMAC".length()); 850 if (result.startsWith("SHA")) { 851 result = "SHA-" + result.substring("SHA".length()); 852 } 853 return result; 854 } 855 getKeyAlgorithm(String transformation)856 public static String getKeyAlgorithm(String transformation) { 857 try { 858 return getCipherKeyAlgorithm(transformation); 859 } catch (IllegalArgumentException e) { 860 861 } 862 try { 863 return getSignatureAlgorithmKeyAlgorithm(transformation); 864 } catch (IllegalArgumentException e) { 865 866 } 867 String transformationUpperCase = transformation.toUpperCase(Locale.US); 868 if (transformationUpperCase.equals("EC")) { 869 return KeyProperties.KEY_ALGORITHM_EC; 870 } 871 if (transformationUpperCase.equals("RSA")) { 872 return KeyProperties.KEY_ALGORITHM_RSA; 873 } 874 if (transformationUpperCase.equals("DESEDE")) { 875 return KeyProperties.KEY_ALGORITHM_3DES; 876 } 877 if (transformationUpperCase.equals("AES")) { 878 return KeyProperties.KEY_ALGORITHM_AES; 879 } 880 if (transformationUpperCase.startsWith("HMAC")) { 881 if (transformation.endsWith("SHA1")) { 882 return KeyProperties.KEY_ALGORITHM_HMAC_SHA1; 883 } else if (transformation.endsWith("SHA224")) { 884 return KeyProperties.KEY_ALGORITHM_HMAC_SHA224; 885 } else if (transformation.endsWith("SHA256")) { 886 return KeyProperties.KEY_ALGORITHM_HMAC_SHA256; 887 } else if (transformation.endsWith("SHA384")) { 888 return KeyProperties.KEY_ALGORITHM_HMAC_SHA384; 889 } else if (transformation.endsWith("SHA512")) { 890 return KeyProperties.KEY_ALGORITHM_HMAC_SHA512; 891 } 892 } 893 throw new IllegalArgumentException("Unsupported transformation: " + transformation); 894 } 895 getCipherKeyAlgorithm(String transformation)896 public static String getCipherKeyAlgorithm(String transformation) { 897 String transformationUpperCase = transformation.toUpperCase(Locale.US); 898 if (transformationUpperCase.startsWith("AES/")) { 899 return KeyProperties.KEY_ALGORITHM_AES; 900 } else if (transformationUpperCase.startsWith("DESEDE/")) { 901 return KeyProperties.KEY_ALGORITHM_3DES; 902 } else if (transformationUpperCase.startsWith("RSA/")) { 903 return KeyProperties.KEY_ALGORITHM_RSA; 904 } else { 905 throw new IllegalArgumentException("Unsupported transformation: " + transformation); 906 } 907 } 908 isCipherSymmetric(String transformation)909 public static boolean isCipherSymmetric(String transformation) { 910 String transformationUpperCase = transformation.toUpperCase(Locale.US); 911 if (transformationUpperCase.startsWith("AES/") || transformationUpperCase.startsWith( 912 "DESEDE/")) { 913 return true; 914 } else if (transformationUpperCase.startsWith("RSA/")) { 915 return false; 916 } else { 917 throw new IllegalArgumentException("YYZ: Unsupported transformation: " + transformation); 918 } 919 } 920 getCipherDigest(String transformation)921 public static String getCipherDigest(String transformation) { 922 String transformationUpperCase = transformation.toUpperCase(Locale.US); 923 if (transformationUpperCase.contains("/OAEP")) { 924 if (transformationUpperCase.endsWith("/OAEPPADDING")) { 925 return KeyProperties.DIGEST_SHA1; 926 } else if (transformationUpperCase.endsWith( 927 "/OAEPWITHSHA-1ANDMGF1PADDING")) { 928 return KeyProperties.DIGEST_SHA1; 929 } else if (transformationUpperCase.endsWith( 930 "/OAEPWITHSHA-224ANDMGF1PADDING")) { 931 return KeyProperties.DIGEST_SHA224; 932 } else if (transformationUpperCase.endsWith( 933 "/OAEPWITHSHA-256ANDMGF1PADDING")) { 934 return KeyProperties.DIGEST_SHA256; 935 } else if (transformationUpperCase.endsWith( 936 "/OAEPWITHSHA-384ANDMGF1PADDING")) { 937 return KeyProperties.DIGEST_SHA384; 938 } else if (transformationUpperCase.endsWith( 939 "/OAEPWITHSHA-512ANDMGF1PADDING")) { 940 return KeyProperties.DIGEST_SHA512; 941 } else { 942 throw new RuntimeException("Unsupported OAEP padding scheme: " 943 + transformation); 944 } 945 } else { 946 return null; 947 } 948 } 949 getCipherEncryptionPadding(String transformation)950 public static String getCipherEncryptionPadding(String transformation) { 951 String transformationUpperCase = transformation.toUpperCase(Locale.US); 952 if (transformationUpperCase.endsWith("/NOPADDING")) { 953 return KeyProperties.ENCRYPTION_PADDING_NONE; 954 } else if (transformationUpperCase.endsWith("/PKCS7PADDING")) { 955 return KeyProperties.ENCRYPTION_PADDING_PKCS7; 956 } else if (transformationUpperCase.endsWith("/PKCS1PADDING")) { 957 return KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1; 958 } else if (transformationUpperCase.split("/")[2].startsWith("OAEP")) { 959 return KeyProperties.ENCRYPTION_PADDING_RSA_OAEP; 960 } else { 961 throw new IllegalArgumentException("Unsupported transformation: " + transformation); 962 } 963 } 964 getCipherBlockMode(String transformation)965 public static String getCipherBlockMode(String transformation) { 966 return transformation.split("/")[1].toUpperCase(Locale.US); 967 } 968 getSignatureAlgorithmDigest(String algorithm)969 public static String getSignatureAlgorithmDigest(String algorithm) { 970 String algorithmUpperCase = algorithm.toUpperCase(Locale.US); 971 int withIndex = algorithmUpperCase.indexOf("WITH"); 972 if (withIndex == -1) { 973 throw new IllegalArgumentException("Unsupported algorithm: " + algorithm); 974 } 975 String digest = algorithmUpperCase.substring(0, withIndex); 976 if (digest.startsWith("SHA")) { 977 digest = "SHA-" + digest.substring("SHA".length()); 978 } 979 return digest; 980 } 981 getSignatureAlgorithmPadding(String algorithm)982 public static String getSignatureAlgorithmPadding(String algorithm) { 983 String algorithmUpperCase = algorithm.toUpperCase(Locale.US); 984 if (algorithmUpperCase.endsWith("WITHECDSA")) { 985 return null; 986 } else if (algorithmUpperCase.endsWith("WITHRSA")) { 987 return KeyProperties.SIGNATURE_PADDING_RSA_PKCS1; 988 } else if (algorithmUpperCase.endsWith("WITHRSA/PSS")) { 989 return KeyProperties.SIGNATURE_PADDING_RSA_PSS; 990 } else { 991 throw new IllegalArgumentException("Unsupported algorithm: " + algorithm); 992 } 993 } 994 getSignatureAlgorithmKeyAlgorithm(String algorithm)995 public static String getSignatureAlgorithmKeyAlgorithm(String algorithm) { 996 String algorithmUpperCase = algorithm.toUpperCase(Locale.US); 997 if (algorithmUpperCase.endsWith("WITHECDSA")) { 998 return KeyProperties.KEY_ALGORITHM_EC; 999 } else if ((algorithmUpperCase.endsWith("WITHRSA")) 1000 || (algorithmUpperCase.endsWith("WITHRSA/PSS"))) { 1001 return KeyProperties.KEY_ALGORITHM_RSA; 1002 } else { 1003 throw new IllegalArgumentException("Unsupported algorithm: " + algorithm); 1004 } 1005 } 1006 isKeyLongEnoughForSignatureAlgorithm(String algorithm, int keySizeBits)1007 public static boolean isKeyLongEnoughForSignatureAlgorithm(String algorithm, int keySizeBits) { 1008 String keyAlgorithm = getSignatureAlgorithmKeyAlgorithm(algorithm); 1009 if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { 1010 // No length restrictions for ECDSA 1011 return true; 1012 } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { 1013 String digest = getSignatureAlgorithmDigest(algorithm); 1014 int digestOutputSizeBits = getDigestOutputSizeBits(digest); 1015 if (digestOutputSizeBits == -1) { 1016 // No digesting -- assume the key is long enough for the message 1017 return true; 1018 } 1019 String paddingScheme = getSignatureAlgorithmPadding(algorithm); 1020 int paddingOverheadBytes; 1021 if (KeyProperties.SIGNATURE_PADDING_RSA_PKCS1.equalsIgnoreCase(paddingScheme)) { 1022 paddingOverheadBytes = 30; 1023 } else if (KeyProperties.SIGNATURE_PADDING_RSA_PSS.equalsIgnoreCase(paddingScheme)) { 1024 int saltSizeBytes = (digestOutputSizeBits + 7) / 8; 1025 paddingOverheadBytes = saltSizeBytes + 1; 1026 } else { 1027 throw new IllegalArgumentException( 1028 "Unsupported signature padding scheme: " + paddingScheme); 1029 } 1030 int minKeySizeBytes = paddingOverheadBytes + (digestOutputSizeBits + 7) / 8 + 1; 1031 int keySizeBytes = keySizeBits / 8; 1032 return keySizeBytes >= minKeySizeBytes; 1033 } else { 1034 throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm); 1035 } 1036 } 1037 isKeyLongEnoughForSignatureAlgorithm(String algorithm, Key key)1038 public static boolean isKeyLongEnoughForSignatureAlgorithm(String algorithm, Key key) { 1039 return isKeyLongEnoughForSignatureAlgorithm(algorithm, getKeySizeBits(key)); 1040 } 1041 getMaxSupportedPlaintextInputSizeBytes(String transformation, int keySizeBits)1042 public static int getMaxSupportedPlaintextInputSizeBytes(String transformation, int keySizeBits) { 1043 String encryptionPadding = getCipherEncryptionPadding(transformation); 1044 int modulusSizeBytes = (keySizeBits + 7) / 8; 1045 if (KeyProperties.ENCRYPTION_PADDING_NONE.equalsIgnoreCase(encryptionPadding)) { 1046 return modulusSizeBytes - 1; 1047 } else if (KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1.equalsIgnoreCase( 1048 encryptionPadding)) { 1049 return modulusSizeBytes - 11; 1050 } else if (KeyProperties.ENCRYPTION_PADDING_RSA_OAEP.equalsIgnoreCase( 1051 encryptionPadding)) { 1052 String digest = getCipherDigest(transformation); 1053 int digestOutputSizeBytes = (getDigestOutputSizeBits(digest) + 7) / 8; 1054 return modulusSizeBytes - 2 * digestOutputSizeBytes - 2; 1055 } else { 1056 throw new IllegalArgumentException( 1057 "Unsupported encryption padding scheme: " + encryptionPadding); 1058 } 1059 1060 } 1061 getMaxSupportedPlaintextInputSizeBytes(String transformation, Key key)1062 public static int getMaxSupportedPlaintextInputSizeBytes(String transformation, Key key) { 1063 String keyAlgorithm = getCipherKeyAlgorithm(transformation); 1064 if (KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(keyAlgorithm) 1065 || KeyProperties.KEY_ALGORITHM_3DES.equalsIgnoreCase(keyAlgorithm)) { 1066 return Integer.MAX_VALUE; 1067 } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { 1068 return getMaxSupportedPlaintextInputSizeBytes(transformation, getKeySizeBits(key)); 1069 } else { 1070 throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm); 1071 } 1072 } 1073 getDigestOutputSizeBits(String digest)1074 public static int getDigestOutputSizeBits(String digest) { 1075 if (KeyProperties.DIGEST_NONE.equals(digest)) { 1076 return -1; 1077 } else if (KeyProperties.DIGEST_MD5.equals(digest)) { 1078 return 128; 1079 } else if (KeyProperties.DIGEST_SHA1.equals(digest)) { 1080 return 160; 1081 } else if (KeyProperties.DIGEST_SHA224.equals(digest)) { 1082 return 224; 1083 } else if (KeyProperties.DIGEST_SHA256.equals(digest)) { 1084 return 256; 1085 } else if (KeyProperties.DIGEST_SHA384.equals(digest)) { 1086 return 384; 1087 } else if (KeyProperties.DIGEST_SHA512.equals(digest)) { 1088 return 512; 1089 } else { 1090 throw new IllegalArgumentException("Unsupported digest: " + digest); 1091 } 1092 } 1093 concat(byte[] arr1, byte[] arr2)1094 public static byte[] concat(byte[] arr1, byte[] arr2) { 1095 return concat(arr1, 0, (arr1 != null) ? arr1.length : 0, 1096 arr2, 0, (arr2 != null) ? arr2.length : 0); 1097 } 1098 concat(byte[] arr1, int offset1, int len1, byte[] arr2, int offset2, int len2)1099 public static byte[] concat(byte[] arr1, int offset1, int len1, 1100 byte[] arr2, int offset2, int len2) { 1101 if (len1 == 0) { 1102 return subarray(arr2, offset2, len2); 1103 } else if (len2 == 0) { 1104 return subarray(arr1, offset1, len1); 1105 } 1106 byte[] result = new byte[len1 + len2]; 1107 if (len1 > 0) { 1108 System.arraycopy(arr1, offset1, result, 0, len1); 1109 } 1110 if (len2 > 0) { 1111 System.arraycopy(arr2, offset2, result, len1, len2); 1112 } 1113 return result; 1114 } 1115 subarray(byte[] arr, int offset, int len)1116 public static byte[] subarray(byte[] arr, int offset, int len) { 1117 if (len == 0) { 1118 return EmptyArray.BYTE; 1119 } 1120 if ((offset == 0) && (arr.length == len)) { 1121 return arr; 1122 } 1123 byte[] result = new byte[len]; 1124 System.arraycopy(arr, offset, result, 0, len); 1125 return result; 1126 } 1127 getMinimalWorkingImportParametersForSigningingWith( String signatureAlgorithm)1128 public static KeyProtection getMinimalWorkingImportParametersForSigningingWith( 1129 String signatureAlgorithm) { 1130 String keyAlgorithm = getSignatureAlgorithmKeyAlgorithm(signatureAlgorithm); 1131 String digest = getSignatureAlgorithmDigest(signatureAlgorithm); 1132 if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { 1133 return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN) 1134 .setDigests(digest) 1135 .build(); 1136 } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { 1137 String padding = getSignatureAlgorithmPadding(signatureAlgorithm); 1138 return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN) 1139 .setDigests(digest) 1140 .setSignaturePaddings(padding) 1141 .build(); 1142 } else { 1143 throw new IllegalArgumentException( 1144 "Unsupported signature algorithm: " + signatureAlgorithm); 1145 } 1146 } 1147 getMinimalWorkingImportParametersWithLimitedUsageForSigningingWith( String signatureAlgorithm, int maxUsageCount)1148 public static KeyProtection getMinimalWorkingImportParametersWithLimitedUsageForSigningingWith( 1149 String signatureAlgorithm, int maxUsageCount) { 1150 String keyAlgorithm = getSignatureAlgorithmKeyAlgorithm(signatureAlgorithm); 1151 String digest = getSignatureAlgorithmDigest(signatureAlgorithm); 1152 if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { 1153 return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN) 1154 .setDigests(digest) 1155 .setMaxUsageCount(maxUsageCount) 1156 .build(); 1157 } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { 1158 String padding = getSignatureAlgorithmPadding(signatureAlgorithm); 1159 return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN) 1160 .setDigests(digest) 1161 .setSignaturePaddings(padding) 1162 .setMaxUsageCount(maxUsageCount) 1163 .build(); 1164 } else { 1165 throw new IllegalArgumentException( 1166 "Unsupported signature algorithm: " + signatureAlgorithm); 1167 } 1168 } 1169 getMinimalWorkingImportParametersForCipheringWith( String transformation, int purposes)1170 public static KeyProtection getMinimalWorkingImportParametersForCipheringWith( 1171 String transformation, int purposes) { 1172 return getMinimalWorkingImportParametersForCipheringWith(transformation, purposes, false); 1173 } 1174 getMinimalWorkingImportParametersForCipheringWith( String transformation, int purposes, boolean ivProvidedWhenEncrypting)1175 public static KeyProtection getMinimalWorkingImportParametersForCipheringWith( 1176 String transformation, int purposes, boolean ivProvidedWhenEncrypting) { 1177 return getMinimalWorkingImportParametersForCipheringWith(transformation, purposes, 1178 ivProvidedWhenEncrypting, false, false); 1179 } 1180 getMinimalWorkingImportParametersForCipheringWith( String transformation, int purposes, boolean ivProvidedWhenEncrypting, boolean isUnlockedDeviceRequired, boolean isUserAuthRequired)1181 public static KeyProtection getMinimalWorkingImportParametersForCipheringWith( 1182 String transformation, int purposes, boolean ivProvidedWhenEncrypting, 1183 boolean isUnlockedDeviceRequired, boolean isUserAuthRequired) { 1184 String keyAlgorithm = TestUtils.getCipherKeyAlgorithm(transformation); 1185 if (KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(keyAlgorithm) 1186 || KeyProperties.KEY_ALGORITHM_3DES.equalsIgnoreCase(keyAlgorithm)) { 1187 String encryptionPadding = TestUtils.getCipherEncryptionPadding(transformation); 1188 String blockMode = TestUtils.getCipherBlockMode(transformation); 1189 boolean randomizedEncryptionRequired = true; 1190 if (KeyProperties.BLOCK_MODE_ECB.equalsIgnoreCase(blockMode)) { 1191 randomizedEncryptionRequired = false; 1192 } else if ((ivProvidedWhenEncrypting) 1193 && ((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0)) { 1194 randomizedEncryptionRequired = false; 1195 } 1196 return new KeyProtection.Builder( 1197 purposes) 1198 .setBlockModes(blockMode) 1199 .setEncryptionPaddings(encryptionPadding) 1200 .setRandomizedEncryptionRequired(randomizedEncryptionRequired) 1201 .setUnlockedDeviceRequired(isUnlockedDeviceRequired) 1202 .setUserAuthenticationRequired(isUserAuthRequired) 1203 .setUserAuthenticationValidityDurationSeconds(3600) 1204 .build(); 1205 } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { 1206 String digest = TestUtils.getCipherDigest(transformation); 1207 String encryptionPadding = TestUtils.getCipherEncryptionPadding(transformation); 1208 boolean randomizedEncryptionRequired = 1209 !KeyProperties.ENCRYPTION_PADDING_NONE.equalsIgnoreCase(encryptionPadding); 1210 // For algorithm transformation such as "RSA/ECB/OAEPWithSHA-224AndMGF1Padding", 1211 // Some providers uses same digest for OAEP main digest(SHA-224) and 1212 // MGF1 digest(SHA-224), and some providers uses main digest as (SHA-224) and 1213 // MGF1 digest as (SHA-1) hence adding both digest for MGF1 digest. 1214 String[] mgf1DigestList = null; 1215 if (digest != null) { 1216 mgf1DigestList = digest.equalsIgnoreCase("SHA-1") 1217 ? new String[] {digest} : new String[] {digest, "SHA-1"}; 1218 } 1219 KeyProtection.Builder keyProtectionBuilder = new KeyProtection.Builder( 1220 purposes) 1221 .setDigests((digest != null) ? new String[] {digest} : EmptyArray.STRING) 1222 .setEncryptionPaddings(encryptionPadding) 1223 .setRandomizedEncryptionRequired(randomizedEncryptionRequired) 1224 .setUserAuthenticationRequired(isUserAuthRequired) 1225 .setUserAuthenticationValidityDurationSeconds(3600) 1226 .setUnlockedDeviceRequired(isUnlockedDeviceRequired); 1227 if (mgf1DigestList != null && android.security.Flags.mgf1DigestSetterV2()) { 1228 keyProtectionBuilder.setMgf1Digests(mgf1DigestList); 1229 } 1230 return keyProtectionBuilder.build(); 1231 } else { 1232 throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm); 1233 } 1234 } 1235 getBigIntegerMagnitudeBytes(BigInteger value)1236 public static byte[] getBigIntegerMagnitudeBytes(BigInteger value) { 1237 return removeLeadingZeroByteIfPresent(value.toByteArray()); 1238 } 1239 removeLeadingZeroByteIfPresent(byte[] value)1240 private static byte[] removeLeadingZeroByteIfPresent(byte[] value) { 1241 if ((value.length < 1) || (value[0] != 0)) { 1242 return value; 1243 } 1244 return TestUtils.subarray(value, 1, value.length - 1); 1245 } 1246 generateRandomMessage(int messageSize)1247 public static byte[] generateRandomMessage(int messageSize) { 1248 byte[] message = new byte[messageSize]; 1249 new SecureRandom().nextBytes(message); 1250 return message; 1251 } 1252 isAttestationSupported()1253 public static boolean isAttestationSupported() { 1254 return Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.O; 1255 } 1256 isPropertyEmptyOrUnknown(String property)1257 public static boolean isPropertyEmptyOrUnknown(String property) { 1258 return TextUtils.isEmpty(property) || property.equals(Build.UNKNOWN); 1259 } 1260 hasSecureLockScreen(Context context)1261 public static boolean hasSecureLockScreen(Context context) { 1262 PackageManager pm = context.getPackageManager(); 1263 return (pm != null && pm.hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)); 1264 } 1265 1266 /** 1267 * Determines whether running build is GSI or not. 1268 * @return true if running build is GSI, false otherwise. 1269 */ isGsiImage()1270 public static boolean isGsiImage() { 1271 final File initGsiRc = new File("/system/system_ext/etc/init/init.gsi.rc"); 1272 return initGsiRc.exists(); 1273 } 1274 1275 /** 1276 * Ed25519 algorithm name is added in Android V. So CTS using this algorithm 1277 * name should check SDK_INT for Android V/preview. 1278 * @return true if current SDK_INT is 34 and PREVIEW_SDK_INT >= 1 or SDK_INT >= 35 1279 */ isEd25519AlgorithmExpectedToSupport()1280 public static boolean isEd25519AlgorithmExpectedToSupport() { 1281 return ((Build.VERSION.SDK_INT == 34 && Build.VERSION.PREVIEW_SDK_INT >= 1) 1282 || Build.VERSION.SDK_INT >= 35); 1283 } 1284 1285 /** 1286 * Returns whether the device has a StrongBox backed KeyStore or Hardware based Keystore 1287 * with provided version and newer. 1288 */ hasKeystoreVersion(boolean isStrongBoxBased, int version)1289 public static boolean hasKeystoreVersion(boolean isStrongBoxBased, int version) { 1290 Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); 1291 if (isStrongBoxBased) { 1292 return context.getPackageManager() 1293 .hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE, version); 1294 } 1295 return context.getPackageManager() 1296 .hasSystemFeature(PackageManager.FEATURE_HARDWARE_KEYSTORE, version); 1297 } 1298 } 1299