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