diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-02-01 11:05:09 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-02-01 11:05:09 +0000 |
commit | e6f6c55b64d61e176809e6df121a5aaaeb26160f (patch) | |
tree | ac677ca5b94f464f7a98786ab625d1fe6e6d489b | |
parent | ce01dc9a6d7932f5e3778ad053566ae9a31b65be (diff) | |
parent | e93c839d9f7988b54a3f5e1e33809e69f14c0163 (diff) | |
download | wycheproof-busytown-mac-infra-release.tar.gz |
Snap for 9550355 from e93c839d9f7988b54a3f5e1e33809e69f14c0163 to sdk-releaseplatform-tools-34.0.1platform-tools-34.0.0platform-tools-33.0.4busytown-mac-infra-release
Change-Id: I126e2be26376683809ee836241dcd5a63f00e3bb
17 files changed, 832 insertions, 224 deletions
@@ -76,6 +76,7 @@ java_import { name: "wycheproof-gson", visibility: [ "//cts/tests/tests/keystore", + "//external/android-key-attestation", ], jars: ["keystore-cts/libs/gson-2.9.0.jar"], } diff --git a/java/com/google/security/wycheproof/testcases/EcdhTest.java b/java/com/google/security/wycheproof/testcases/EcdhTest.java index c1a8b82..07c0b1e 100644 --- a/java/com/google/security/wycheproof/testcases/EcdhTest.java +++ b/java/com/google/security/wycheproof/testcases/EcdhTest.java @@ -990,7 +990,7 @@ public class EcdhTest extends TestCase { testModifiedPublicSpec("ECDHC"); } - @SuppressWarnings("InsecureCryptoUsage") + @SuppressWarnings({"InsecureCryptoUsage", "JUnit3TestNotRun"}) public void testDistinctCurves(String algorithm, ECPrivateKey priv, ECPublicKey pub) throws Exception { KeyAgreement kaA; diff --git a/keystore-cts/java/android/keystore/cts/util/KeyStoreUtil.java b/keystore-cts/java/android/keystore/cts/util/KeyStoreUtil.java index 8bb774e..d174cbf 100644 --- a/keystore-cts/java/android/keystore/cts/util/KeyStoreUtil.java +++ b/keystore-cts/java/android/keystore/cts/util/KeyStoreUtil.java @@ -13,7 +13,10 @@ */ package android.keystore.cts.util; +import android.content.Context; import android.security.keystore.KeyProtection; +import android.keystore.cts.util.TestUtils; +import androidx.test.core.app.ApplicationProvider; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.cert.X509CertificateHolder; @@ -84,6 +87,28 @@ public class KeyStoreUtil { } } + public static int getFeatureVersionKeystore() { + return TestUtils.getFeatureVersionKeystore(ApplicationProvider.getApplicationContext()); + } + + public static boolean hasStrongBox() { + Context context = ApplicationProvider.getApplicationContext(); + return TestUtils.hasStrongBox(context); + } + + public static void assumeStrongBox() { + TestUtils.assumeStrongBox(); + } + + public static boolean isStrongBoxSupportDigest(String digest) { + return digest.equalsIgnoreCase("sha-1") + || digest.equalsIgnoreCase("sha-256"); + } + + public static boolean isStrongBoxSupportKeySize(int keySize) { + return keySize == 2048; + } + public static X509Certificate createCertificate( KeyPair keyPair, X500Principal subject, X500Principal issuer) throws OperatorCreationException, CertificateException, IOException { diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/AesGcmTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/AesGcmTest.java index 8a574f6..725dff1 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/AesGcmTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/AesGcmTest.java @@ -24,11 +24,16 @@ import java.security.AlgorithmParameters; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.KeyStore; +import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; import java.util.ArrayList; import java.util.Arrays; import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.ShortBufferException; import javax.crypto.spec.GCMParameterSpec; @@ -41,9 +46,6 @@ import org.junit.After; import android.keystore.cts.util.KeyStoreUtil; import android.security.keystore.KeyProtection; import android.security.keystore.KeyProperties; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.UnrecoverableKeyException; // TODO(bleichen): // - For EAX I was able to derive some special cases by inverting OMAC. @@ -70,8 +72,12 @@ public class AesGcmTest { public void setup() throws Exception { keyStore = KeyStore.getInstance("AndroidKeyStore"); keyStore.load(null); + boolean hasStrongBox = KeyStoreUtil.hasStrongBox(); for (GcmTestVector test : GCM_TEST_VECTORS) { - setKeystoreEntry(test.alias, test.key); + setKeystoreEntry(test.alias, test.key, false); + if (hasStrongBox) { + setKeystoreEntry(test.alias_sb, test.key, true); + } } } @@ -80,7 +86,7 @@ public class AesGcmTest { KeyStoreUtil.cleanUpKeyStore(); } - private SecretKey setKeystoreEntry(String alias, SecretKeySpec key) + private SecretKey setKeystoreEntry(String alias, SecretKeySpec key, boolean isStrongBox) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { keyStore.setEntry( alias, @@ -89,15 +95,26 @@ public class AesGcmTest { .setBlockModes(KeyProperties.BLOCK_MODE_ECB, KeyProperties.BLOCK_MODE_GCM) .setRandomizedEncryptionRequired(false) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .setIsStrongBoxBacked(isStrongBox) .build()); // Key imported, obtain a reference to it. return (SecretKey) keyStore.getKey(alias, null); } - + private SecretKey getKey(String alias) throws Exception { return (SecretKey) keyStore.getKey(alias, null); } + private Cipher getInitializedCipherInstance(String algorithm, int operationMode, + GcmTestVector testVector, boolean isStrongBox) + throws Exception { + Cipher cipher = Cipher.getInstance(algorithm, + EXPECTED_PROVIDER_NAME); + cipher.init(operationMode, getKey(isStrongBox ? testVector.alias_sb : testVector.alias), + testVector.parameters); + return cipher; + } + /** Test vectors */ public static class GcmTestVector { final byte[] pt; @@ -110,6 +127,7 @@ public class AesGcmTest { final int nonceLengthInBits; final int tagLengthInBits; final String alias; + final String alias_sb; public GcmTestVector( String message, @@ -129,6 +147,7 @@ public class AesGcmTest { this.parameters = new GCMParameterSpec(tagLengthInBits, TestUtil.hexToBytes(nonce)); this.key = new SecretKeySpec(TestUtil.hexToBytes(keyMaterial), "AES"); this.alias = alias; + this.alias_sb = alias + "_sb"; } }; @@ -215,15 +234,15 @@ public class AesGcmTest { * <p>The only assumption we make here is that all test vectors with 128 bit tags and nonces with * at least 96 bits are supported. */ - private Iterable<GcmTestVector> getTestVectors() throws Exception { + private Iterable<GcmTestVector> getTestVectors(boolean isStrongBox) throws Exception { ArrayList<GcmTestVector> supported = new ArrayList<GcmTestVector>(); for (GcmTestVector test : GCM_TEST_VECTORS) { if (test.nonceLengthInBits != 96 || test.tagLengthInBits != 128) { try { // Checks whether the parameter size is supported. // It would be nice if there was a way to check this without trying to encrypt. - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME); - cipher.init(Cipher.ENCRYPT_MODE, getKey(test.alias), test.parameters); + getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.ENCRYPT_MODE, + test, isStrongBox); } catch (InvalidKeyException | InvalidAlgorithmParameterException ex) { // Not supported continue; @@ -236,9 +255,17 @@ public class AesGcmTest { @Test public void testVectors() throws Exception { - for (GcmTestVector test : getTestVectors()) { - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME); - cipher.init(Cipher.ENCRYPT_MODE, getKey(test.alias), test.parameters); + testVectors(false); + } + @Test + public void testVectors_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testVectors(true); + } + private void testVectors(boolean isStrongBox) throws Exception { + for (GcmTestVector test : getTestVectors(isStrongBox)) { + Cipher cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.ENCRYPT_MODE, + test, isStrongBox); cipher.updateAAD(test.aad); byte[] ct = cipher.doFinal(test.pt); assertEquals(test.ctHex, TestUtil.bytesToHex(ct)); @@ -248,11 +275,19 @@ public class AesGcmTest { /** Test encryption when update and doFinal are done with empty byte arrays. */ @Test public void testEncryptWithEmptyArrays() throws Exception { - for (GcmTestVector test : getTestVectors()) { + testEncryptWithEmptyArrays(false); + } + @Test + public void testEncryptWithEmptyArrays_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testEncryptWithEmptyArrays(true); + } + private void testEncryptWithEmptyArrays(boolean isStrongBox) throws Exception { + for (GcmTestVector test : getTestVectors(isStrongBox)) { // Encryption byte[] empty = new byte[0]; - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME); - cipher.init(Cipher.ENCRYPT_MODE, getKey(test.alias), test.parameters); + Cipher cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.ENCRYPT_MODE, + test, isStrongBox); int outputSize = cipher.getOutputSize(test.pt.length); ByteBuffer ctBuffer = ByteBuffer.allocate(outputSize); cipher.updateAAD(empty); @@ -275,10 +310,18 @@ public class AesGcmTest { @Test public void testDecryptWithEmptyArrays() throws Exception { - for (GcmTestVector test : getTestVectors()) { + testDecryptWithEmptyArrays(false); + } + @Test + public void testDecryptWithEmptyArrays_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testDecryptWithEmptyArrays(true); + } + private void testDecryptWithEmptyArrays(boolean isStrongBox) throws Exception { + for (GcmTestVector test : getTestVectors(isStrongBox)) { byte[] empty = new byte[0]; - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME); - cipher.init(Cipher.DECRYPT_MODE, getKey(test.alias), test.parameters); + Cipher cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.DECRYPT_MODE, + test, isStrongBox); int outputSize = cipher.getOutputSize(test.ct.length); ByteBuffer ptBuffer = ByteBuffer.allocate(outputSize); cipher.updateAAD(empty); @@ -299,7 +342,8 @@ public class AesGcmTest { // Simple test that a modified ciphertext fails. ptBuffer.clear(); - cipher.init(Cipher.DECRYPT_MODE, getKey(test.alias), test.parameters); + cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.DECRYPT_MODE, test, + isStrongBox); cipher.updateAAD(empty); cipher.updateAAD(test.aad); cipher.updateAAD(new byte[1]); @@ -333,9 +377,17 @@ public class AesGcmTest { */ @Test public void testLateUpdateAAD() throws Exception { - for (GcmTestVector test : getTestVectors()) { - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME); - cipher.init(Cipher.ENCRYPT_MODE, getKey(test.alias), test.parameters); + testLateUpdateAAD(false); + } + @Test + public void testLateUpdateAAD_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testLateUpdateAAD(true); + } + private void testLateUpdateAAD(boolean isStrongBox) throws Exception { + for (GcmTestVector test : getTestVectors(isStrongBox)) { + Cipher cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.ENCRYPT_MODE, test, + isStrongBox); byte[] c0 = cipher.update(test.pt); try { cipher.updateAAD(test.aad); @@ -366,9 +418,17 @@ public class AesGcmTest { */ @Test public void testIvReuse() throws Exception { - for (GcmTestVector test : getTestVectors()) { - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME); - cipher.init(Cipher.ENCRYPT_MODE, getKey(test.alias), test.parameters); + testIvReuse(false); + } + @Test + public void testIvReuse_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testIvReuse(true); + } + private void testIvReuse(boolean isStrongBox) throws Exception { + for (GcmTestVector test : getTestVectors(isStrongBox)) { + Cipher cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.ENCRYPT_MODE, test, + isStrongBox); cipher.updateAAD(test.aad); byte[] ct1 = cipher.doFinal(test.pt); try { @@ -392,14 +452,22 @@ public class AesGcmTest { */ @Test public void testByteBufferSize() throws Exception { - for (GcmTestVector test : getTestVectors()) { - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME); - // Encryption - cipher.init(Cipher.ENCRYPT_MODE, getKey(test.alias), test.parameters); + testByteBufferSize(false); + } + @Test + public void testByteBufferSize_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testByteBufferSize(true); + } + private void testByteBufferSize(boolean isStrongBox) throws Exception { + for (GcmTestVector test : getTestVectors(isStrongBox)) { + Cipher cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.ENCRYPT_MODE, + test, isStrongBox); int outputSize = cipher.getOutputSize(test.pt.length); assertEquals("plaintext size:" + test.pt.length, test.ct.length, outputSize); // Decryption - cipher.init(Cipher.DECRYPT_MODE, getKey(test.alias), test.parameters); + cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.DECRYPT_MODE, + test, isStrongBox); outputSize = cipher.getOutputSize(test.ct.length); assertEquals("ciphertext size:" + test.ct.length, test.pt.length, outputSize); } @@ -408,11 +476,19 @@ public class AesGcmTest { /** Encryption with ByteBuffers. */ @Test public void testByteBuffer() throws Exception { - for (GcmTestVector test : getTestVectors()) { + testByteBuffer(false); + } + @Test + public void testByteBuffer_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testByteBuffer(true); + } + private void testByteBuffer(boolean isStrongBox) throws Exception { + for (GcmTestVector test : getTestVectors(isStrongBox)) { // Encryption - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME); ByteBuffer ptBuffer = ByteBuffer.wrap(test.pt); - cipher.init(Cipher.ENCRYPT_MODE, getKey(test.alias), test.parameters); + Cipher cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.ENCRYPT_MODE, + test, isStrongBox); int outputSize = cipher.getOutputSize(test.pt.length); ByteBuffer ctBuffer = ByteBuffer.allocate(outputSize); cipher.updateAAD(test.aad); @@ -421,7 +497,8 @@ public class AesGcmTest { // Decryption ctBuffer.flip(); - cipher.init(Cipher.DECRYPT_MODE, getKey(test.alias), test.parameters); + cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.DECRYPT_MODE, + test, isStrongBox); outputSize = cipher.getOutputSize(test.ct.length); ByteBuffer decrypted = ByteBuffer.allocate(outputSize); cipher.updateAAD(test.aad); @@ -433,10 +510,18 @@ public class AesGcmTest { /** Encryption with ByteBuffers should be copy-safe. */ @Test public void testByteBufferAlias() throws Exception { - for (GcmTestVector test : getTestVectors()) { + testByteBufferAlias(false); + } + @Test + public void testByteBufferAlias_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testByteBufferAlias(true); + } + private void testByteBufferAlias(boolean isStrongBox) throws Exception { + for (GcmTestVector test : getTestVectors(isStrongBox)) { // Encryption - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME); - cipher.init(Cipher.ENCRYPT_MODE, getKey(test.alias), test.parameters); + Cipher cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.ENCRYPT_MODE, + test, isStrongBox); int outputSize = cipher.getOutputSize(test.pt.length); byte[] backingArray = new byte[outputSize]; ByteBuffer ptBuffer = ByteBuffer.wrap(backingArray); @@ -450,7 +535,8 @@ public class AesGcmTest { // Decryption ByteBuffer decrypted = ByteBuffer.wrap(backingArray); ctBuffer.flip(); - cipher.init(Cipher.DECRYPT_MODE, getKey(test.alias), test.parameters); + cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.DECRYPT_MODE, + test, isStrongBox); cipher.updateAAD(test.aad); cipher.doFinal(ctBuffer, decrypted); assertEquals(test.ptHex, TestUtil.byteBufferToHex(decrypted)); @@ -460,6 +546,14 @@ public class AesGcmTest { /** Encryption and decryption with large arrays should be copy-safe. */ @Test public void testLargeArrayAlias() throws Exception { + testLargeArrayAlias(false); + } + @Test + public void testLargeArrayAlias_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testLargeArrayAlias(true); + } + private void testLargeArrayAlias(boolean isStrongBox) throws Exception { byte[] ptVector = new byte[8192]; // this offset is relative to the start of the input, not the start of the buffer. @@ -470,7 +564,7 @@ public class AesGcmTest { try { String alias = "TestKey" + 1; SecretKeySpec keySpec = new SecretKeySpec(new byte[16], "AES"); - secretKey = setKeystoreEntry(alias, keySpec); + secretKey = setKeystoreEntry(alias, keySpec, isStrongBox); } catch (Exception e) { fail("Failed to set secret key entry in KeyStore."); } @@ -542,6 +636,14 @@ public class AesGcmTest { */ @Test public void testByteBufferShiftedAlias() throws Exception { + testByteBufferShiftedAlias(false); + } + @Test + public void testByteBufferShiftedAlias_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testByteBufferShiftedAlias(true); + } + private void testByteBufferShiftedAlias(boolean isStrongBox) throws Exception { byte[] ptVector = new byte[8192]; for (int i = 0; i < 3; i++) { @@ -552,7 +654,7 @@ public class AesGcmTest { try { String alias = "TestKey" + 1; SecretKeySpec keySpec = new SecretKeySpec(new byte[16], "AES"); - secretKey = setKeystoreEntry(alias, keySpec); + secretKey = setKeystoreEntry(alias, keySpec, isStrongBox); } catch (Exception e) { fail("Failed to set secret key entry in KeyStore."); } @@ -656,11 +758,19 @@ public class AesGcmTest { @Test public void testReadOnlyByteBuffer() throws Exception { - for (GcmTestVector test : getTestVectors()) { + testReadOnlyByteBuffer(false); + } + @Test + public void testReadOnlyByteBuffer_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testReadOnlyByteBuffer(true); + } + private void testReadOnlyByteBuffer(boolean isStrongBox) throws Exception { + for (GcmTestVector test : getTestVectors(isStrongBox)) { // Encryption - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME); ByteBuffer ptBuffer = ByteBuffer.wrap(test.pt).asReadOnlyBuffer(); - cipher.init(Cipher.ENCRYPT_MODE, getKey(test.alias), test.parameters); + Cipher cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.ENCRYPT_MODE, + test, isStrongBox); int outputSize = cipher.getOutputSize(test.pt.length); ByteBuffer ctBuffer = ByteBuffer.allocate(outputSize); cipher.updateAAD(test.aad); @@ -670,7 +780,8 @@ public class AesGcmTest { // Decryption ctBuffer.flip(); ctBuffer = ctBuffer.asReadOnlyBuffer(); - cipher.init(Cipher.DECRYPT_MODE, getKey(test.alias), test.parameters); + cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.DECRYPT_MODE, + test, isStrongBox); outputSize = cipher.getOutputSize(test.ct.length); ByteBuffer decrypted = ByteBuffer.allocate(outputSize); cipher.updateAAD(test.aad); @@ -686,9 +797,18 @@ public class AesGcmTest { */ @Test public void testByteBufferWithOffset() throws Exception { - for (GcmTestVector test : getTestVectors()) { + testByteBufferWithOffset(false); + } + @Test + public void testByteBufferWithOffset_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testByteBufferWithOffset(true); + } + private void testByteBufferWithOffset(boolean isStrongBox) throws Exception { + for (GcmTestVector test : getTestVectors(isStrongBox)) { // Encryption - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME); + Cipher cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.ENCRYPT_MODE, + test, isStrongBox); ByteBuffer ptBuffer = ByteBuffer.wrap(new byte[test.pt.length + 50]); ptBuffer.position(5); ptBuffer = ptBuffer.slice(); @@ -698,7 +818,6 @@ public class AesGcmTest { ByteBuffer ctBuffer = ByteBuffer.wrap(new byte[test.ct.length + 50]); ctBuffer.position(8); ctBuffer = ctBuffer.slice(); - cipher.init(Cipher.ENCRYPT_MODE, getKey(test.alias), test.parameters); cipher.updateAAD(test.aad); cipher.doFinal(ptBuffer, ctBuffer); assertEquals(test.ctHex, TestUtil.byteBufferToHex(ctBuffer)); @@ -708,7 +827,8 @@ public class AesGcmTest { ByteBuffer decBuffer = ByteBuffer.wrap(new byte[test.pt.length + 50]); decBuffer.position(6); decBuffer = decBuffer.slice(); - cipher.init(Cipher.DECRYPT_MODE, getKey(test.alias), test.parameters); + cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.DECRYPT_MODE, + test, isStrongBox); cipher.updateAAD(test.aad); cipher.doFinal(ctBuffer, decBuffer); assertEquals(test.ptHex, TestUtil.byteBufferToHex(decBuffer)); @@ -717,12 +837,20 @@ public class AesGcmTest { @Test public void testByteBufferTooShort() throws Exception { - for (GcmTestVector test : getTestVectors()) { + testByteBufferTooShort(false); + } + @Test + public void testByteBufferTooShort_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testByteBufferTooShort(true); + } + private void testByteBufferTooShort(boolean isStrongBox) throws Exception { + for (GcmTestVector test : getTestVectors(isStrongBox)) { // Encryption - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME); + Cipher cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.ENCRYPT_MODE, + test, isStrongBox); ByteBuffer ptBuffer = ByteBuffer.wrap(test.pt); ByteBuffer ctBuffer = ByteBuffer.allocate(test.ct.length - 1); - cipher.init(Cipher.ENCRYPT_MODE, getKey(test.alias), test.parameters); cipher.updateAAD(test.aad); try { cipher.doFinal(ptBuffer, ctBuffer); @@ -734,7 +862,8 @@ public class AesGcmTest { // Decryption ctBuffer = ByteBuffer.wrap(test.ct); ByteBuffer decrypted = ByteBuffer.allocate(test.pt.length - 1); - cipher.init(Cipher.DECRYPT_MODE, getKey(test.alias), test.parameters); + cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.DECRYPT_MODE, + test, isStrongBox); cipher.updateAAD(test.aad); try { cipher.doFinal(ctBuffer, decrypted); @@ -751,12 +880,20 @@ public class AesGcmTest { */ @Test public void testEncryptWithEmptyByteBuffer() throws Exception { - for (GcmTestVector test : getTestVectors()) { + testEncryptWithEmptyByteBuffer(false); + } + @Test + public void testEncryptWithEmptyByteBuffer_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testEncryptWithEmptyByteBuffer(true); + } + private void testEncryptWithEmptyByteBuffer(boolean isStrongBox) throws Exception { + for (GcmTestVector test : getTestVectors(isStrongBox)) { // Encryption - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME); + Cipher cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.ENCRYPT_MODE, + test, isStrongBox); ByteBuffer empty = ByteBuffer.allocate(0); ByteBuffer ptBuffer = ByteBuffer.wrap(test.pt); - cipher.init(Cipher.ENCRYPT_MODE, getKey(test.alias), test.parameters); int outputSize = cipher.getOutputSize(test.pt.length); ByteBuffer ctBuffer = ByteBuffer.allocate(outputSize); cipher.updateAAD(empty); @@ -770,11 +907,19 @@ public class AesGcmTest { @Test public void testDecryptWithEmptyBuffer() throws Exception { - for (GcmTestVector test : getTestVectors()) { - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME); + testDecryptWithEmptyBuffer(false); + } + @Test + public void testDecryptWithEmptyBuffer_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testDecryptWithEmptyBuffer(true); + } + private void testDecryptWithEmptyBuffer(boolean isStrongBox) throws Exception { + for (GcmTestVector test : getTestVectors(isStrongBox)) { + Cipher cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.DECRYPT_MODE, + test, isStrongBox); ByteBuffer empty = ByteBuffer.allocate(0); ByteBuffer ctBuffer = ByteBuffer.wrap(test.ct); - cipher.init(Cipher.DECRYPT_MODE, getKey(test.alias), test.parameters); int outputSize = cipher.getOutputSize(test.ct.length); ByteBuffer ptBuffer = ByteBuffer.allocate(outputSize); cipher.updateAAD(empty); @@ -787,7 +932,8 @@ public class AesGcmTest { // Simple test that a modified ciphertext fails. ctBuffer.flip(); ptBuffer.clear(); - cipher.init(Cipher.DECRYPT_MODE, getKey(test.alias), test.parameters); + cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.DECRYPT_MODE, + test, isStrongBox); cipher.updateAAD(empty); cipher.updateAAD(test.aad); cipher.updateAAD(new byte[1]); @@ -822,7 +968,7 @@ public class AesGcmTest { try { String alias = "TestKey" + 1; SecretKeySpec keySpec = new SecretKeySpec(new byte[16], "AES"); - secretKey = setKeystoreEntry(alias, keySpec); + secretKey = setKeystoreEntry(alias, keySpec, /*isStrongBox*/ false); } catch (Exception e) { fail("Failed to set secret key entry in KeyStore."); } @@ -851,7 +997,7 @@ public class AesGcmTest { try { String alias = "TestKey" + 1; SecretKeySpec keySpec = new SecretKeySpec(new byte[16], "AES"); - secretKey = setKeystoreEntry(alias, keySpec); + secretKey = setKeystoreEntry(alias, keySpec, /*isStrongBox*/ false); } catch (Exception e) { fail("Failed to set secret key entry in KeyStore."); } @@ -907,7 +1053,7 @@ public class AesGcmTest { try { String alias = "TestKey" + 1; SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); - secretKey = setKeystoreEntry(alias, keySpec); + secretKey = setKeystoreEntry(alias, keySpec, /*isStrongBox*/ false); } catch (Exception e) { fail("Failed to set secret key entry in KeyStore."); } @@ -939,6 +1085,15 @@ public class AesGcmTest { */ @Test public void testEncryptEmptyPlaintextWithEmptyIv() throws Exception { + testEncryptEmptyPlaintextWithEmptyIv(false); + } + @Test + public void testEncryptEmptyPlaintextWithEmptyIv_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testEncryptEmptyPlaintextWithEmptyIv(true); + } + + private void testEncryptEmptyPlaintextWithEmptyIv(boolean isStrongBox) throws Exception { byte[] emptyIv = new byte[0]; byte[] input = new byte[0]; byte[] key = TestUtil.hexToBytes("56aae7bd5cbefc71d31c4338e6ddd6c5"); @@ -946,7 +1101,7 @@ public class AesGcmTest { try { String alias = "TestKey" + 1; SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); - secretKey = setKeystoreEntry(alias, keySpec); + secretKey = setKeystoreEntry(alias, keySpec, isStrongBox); } catch (Exception e) { fail("Failed to set secret key entry in KeyStore."); } @@ -967,13 +1122,22 @@ public class AesGcmTest { @Test public void testDecryptWithEmptyIv() throws Exception { + testDecryptWithEmptyIv(false); + } + @Test + public void testDecryptWithEmptyIv_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testDecryptWithEmptyIv(true); + } + + private void testDecryptWithEmptyIv(boolean isStrongBox) throws Exception { byte[] emptyIv = new byte[0]; byte[] key = TestUtil.hexToBytes("56aae7bd5cbefc71d31c4338e6ddd6c5"); SecretKey secretKey = null; try { String alias = "TestKey" + 1; SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); - secretKey = setKeystoreEntry(alias, keySpec); + secretKey = setKeystoreEntry(alias, keySpec, isStrongBox); } catch (Exception e) { fail("Failed to set secret key entry in KeyStore."); } diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/CipherInputStreamTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/CipherInputStreamTest.java index d0f85d4..85d84d7 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/CipherInputStreamTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/CipherInputStreamTest.java @@ -71,13 +71,15 @@ public class CipherInputStreamTest { return bytes; } - static SecretKey randomKey(String algorithm, String alias, int keySizeInBytes) throws Exception { + static SecretKey randomKey(String algorithm, String alias, int keySizeInBytes, + boolean isStrongBox) throws Exception { SecretKeySpec keySpec = new SecretKeySpec(randomBytes(keySizeInBytes), "AES"); KeyStore keyStore = KeyStoreUtil.saveSecretKeyToKeystore(alias, keySpec, new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setRandomizedEncryptionRequired(false) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .setIsStrongBoxBacked(isStrongBox) .build()); // Key imported, obtain a reference to it. return (SecretKey) keyStore.getKey(alias, null); @@ -103,9 +105,9 @@ public class CipherInputStreamTest { @SuppressWarnings("InsecureCryptoUsage") public TestVector( String algorithm, String alias, int keySize, - int ivSize, int tagSize, int ptSize, int aadSize) throws Exception { + int ivSize, int tagSize, int ptSize, int aadSize, boolean isStrongBox) throws Exception { this.algorithm = algorithm; - this.key = randomKey(algorithm, alias, keySize); + this.key = randomKey(algorithm, alias, keySize, isStrongBox); this.params = randomParameters(algorithm, ivSize, tagSize); this.pt = randomBytes(ptSize); this.aad = randomBytes(aadSize); @@ -122,7 +124,8 @@ public class CipherInputStreamTest { int[] ivSizes, int[] tagSizes, int[] ptSizes, - int[] aadSizes) + int[] aadSizes, + boolean isStrongBox) throws Exception { ArrayList<TestVector> result = new ArrayList<TestVector>(); for (int keySize : keySizes) { @@ -133,7 +136,7 @@ public class CipherInputStreamTest { String keyAlias = "Key-" + keySize + "-" + ivSize + "-" + tagSize + "-" + ptSize + "-" + aadSize; result.add(new TestVector(algorithm, keyAlias, keySize, - ivSize, tagSize, ptSize, aadSize)); + ivSize, tagSize, ptSize, aadSize, isStrongBox)); } } } @@ -240,26 +243,44 @@ public class CipherInputStreamTest { @Test public void testAesGcm() throws Exception { + testAesGcm(false); + } + @Test + public void testAesGcm_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testAesGcm(true); + } + private void testAesGcm(boolean isStrongBox) throws Exception { final int[] keySizes = {16, 32}; final int[] ivSizes = {12}; final int[] tagSizes = {12, 16}; final int[] ptSizes = {0, 8, 16, 65, 8100}; final int[] aadSizes = {0, 8, 24}; Iterable<TestVector> v = - getTestVectors("AES/GCM/NoPadding", keySizes, ivSizes, tagSizes, ptSizes, aadSizes); + getTestVectors("AES/GCM/NoPadding", keySizes, ivSizes, + tagSizes, ptSizes, aadSizes, isStrongBox); testEncrypt(v); testDecrypt(v); } @Test public void testCorruptAesGcm() throws Exception { + testCorruptAesGcm(false); + } + @Test + public void testCorruptAesGcm_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testCorruptAesGcm(true); + } + private void testCorruptAesGcm(boolean isStrongBox) throws Exception { final int[] keySizes = {16, 32}; final int[] ivSizes = {12}; final int[] tagSizes = {12, 16}; final int[] ptSizes = {8, 16, 65, 8100}; final int[] aadSizes = {0, 8, 24}; Iterable<TestVector> v = - getTestVectors("AES/GCM/NoPadding", keySizes, ivSizes, tagSizes, ptSizes, aadSizes); + getTestVectors("AES/GCM/NoPadding", keySizes, + ivSizes, tagSizes, ptSizes, aadSizes, isStrongBox); boolean acceptEmptyPlaintext = true; testCorruptDecrypt(v, acceptEmptyPlaintext); } @@ -271,13 +292,22 @@ public class CipherInputStreamTest { */ @Test public void testEmptyPlaintext() throws Exception { + testEmptyPlaintext(false); + } + @Test + public void testEmptyPlaintext_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testEmptyPlaintext(true); + } + private void testEmptyPlaintext(boolean isStrongBox) throws Exception { final int[] keySizes = {16, 32}; final int[] ivSizes = {12}; final int[] tagSizes = {12, 16}; final int[] ptSizes = {0}; final int[] aadSizes = {0, 8, 24}; Iterable<TestVector> v = - getTestVectors("AES/GCM/NoPadding", keySizes, ivSizes, tagSizes, ptSizes, aadSizes); + getTestVectors("AES/GCM/NoPadding", keySizes, + ivSizes, tagSizes, ptSizes, aadSizes, isStrongBox); boolean acceptEmptyPlaintext = false; testCorruptDecrypt(v, acceptEmptyPlaintext); } @@ -293,7 +323,8 @@ public class CipherInputStreamTest { final int[] ptSizes = {0, 8, 16, 65, 8100}; final int[] aadSizes = {0, 8, 24}; Iterable<TestVector> v = - getTestVectors(algorithm, keySizes, ivSizes, tagSizes, ptSizes, aadSizes); + getTestVectors(algorithm, keySizes, ivSizes, tagSizes, ptSizes, + aadSizes, /*isStrongBox*/ false); testEncrypt(v); testDecrypt(v); boolean acceptEmptyPlaintext = true; diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/CipherOutputStreamTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/CipherOutputStreamTest.java index bf863fe..39a6044 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/CipherOutputStreamTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/CipherOutputStreamTest.java @@ -71,13 +71,15 @@ public class CipherOutputStreamTest { return bytes; } - static SecretKey randomKey(String algorithm, String alias, int keySizeInBytes) throws Exception{ + static SecretKey randomKey(String algorithm, String alias, int keySizeInBytes, + boolean isStrongBox) throws Exception{ SecretKeySpec keySpec = new SecretKeySpec(randomBytes(keySizeInBytes), "AES"); KeyStore keyStore = KeyStoreUtil.saveSecretKeyToKeystore(alias, keySpec, new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setRandomizedEncryptionRequired(false) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .setIsStrongBoxBacked(isStrongBox) .build()); // Key imported, obtain a reference to it. return (SecretKey) keyStore.getKey(alias, null); @@ -103,9 +105,9 @@ public class CipherOutputStreamTest { public TestVector( String algorithm, String alias, int keySize, - int ivSize, int tagSize, int ptSize, int aadSize) throws Exception { + int ivSize, int tagSize, int ptSize, int aadSize, boolean isStrongBox) throws Exception { this.algorithm = algorithm; - this.key = randomKey(algorithm, alias, keySize); + this.key = randomKey(algorithm, alias, keySize, isStrongBox); this.params = randomParameters(algorithm, ivSize, tagSize); this.pt = randomBytes(ptSize); this.aad = randomBytes(aadSize); @@ -122,7 +124,8 @@ public class CipherOutputStreamTest { int[] ivSizes, int[] tagSizes, int[] ptSizes, - int[] aadSizes) + int[] aadSizes, + boolean isStrongBox) throws Exception { int counter = 0; ArrayList<TestVector> result = new ArrayList<TestVector>(); @@ -133,7 +136,7 @@ public class CipherOutputStreamTest { for (int aadSize : aadSizes) { String keyAlias = "Key" + counter++; result.add(new TestVector(algorithm, keyAlias, keySize, - ivSize, tagSize, ptSize, aadSize)); + ivSize, tagSize, ptSize, aadSize, isStrongBox)); } } } @@ -213,13 +216,22 @@ public class CipherOutputStreamTest { @Test public void testAesGcm() throws Exception { + testAesGcm(false); + } + @Test + public void testAesGcm_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testAesGcm(true); + } + private void testAesGcm(boolean isStrongBox) throws Exception { final int[] keySizes = {16, 32}; final int[] ivSizes = {12}; final int[] tagSizes = {12, 16}; final int[] ptSizes = {8, 16, 65, 8100}; final int[] aadSizes = {0, 8, 24}; Iterable<TestVector> v = - getTestVectors("AES/GCM/NoPadding", keySizes, ivSizes, tagSizes, ptSizes, aadSizes); + getTestVectors("AES/GCM/NoPadding", keySizes, + ivSizes, tagSizes, ptSizes, aadSizes, isStrongBox); testEncrypt(v); testDecrypt(v); boolean acceptEmptyPlaintext = true; @@ -233,13 +245,22 @@ public class CipherOutputStreamTest { */ @Test public void testEmptyPlaintext() throws Exception { + testEmptyPlaintext(false); + } + @Test + public void testEmptyPlaintext_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testEmptyPlaintext(true); + } + private void testEmptyPlaintext(boolean isStrongBox) throws Exception { final int[] keySizes = {16, 32}; final int[] ivSizes = {12}; final int[] tagSizes = {12, 16}; final int[] ptSizes = {0}; final int[] aadSizes = {0, 8, 24}; Iterable<TestVector> v = - getTestVectors("AES/GCM/NoPadding", keySizes, ivSizes, tagSizes, ptSizes, aadSizes); + getTestVectors("AES/GCM/NoPadding", keySizes, + ivSizes, tagSizes, ptSizes, aadSizes, isStrongBox); testEncrypt(v); testDecrypt(v); boolean acceptEmptyPlaintext = false; @@ -258,7 +279,8 @@ public class CipherOutputStreamTest { final int[] ptSizes = {8, 16, 65, 8100}; final int[] aadSizes = {0, 8, 24}; Iterable<TestVector> v = - getTestVectors(algorithm, keySizes, ivSizes, tagSizes, ptSizes, aadSizes); + getTestVectors(algorithm, keySizes, ivSizes, tagSizes, ptSizes, + aadSizes, /*isStrongBox*/ false); testEncrypt(v); testDecrypt(v); boolean acceptEmptyPlaintext = true; diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/EcdhTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/EcdhTest.java index dae5bde..f57ad29 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/EcdhTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/EcdhTest.java @@ -48,7 +48,6 @@ import android.security.keystore.KeyProtection; import android.security.keystore.KeyProperties; import android.security.keystore.KeyGenParameterSpec; import android.keystore.cts.util.KeyStoreUtil; -import android.keystore.cts.util.TestUtils; import androidx.test.InstrumentationRegistry; @@ -104,21 +103,25 @@ public class EcdhTest { KeyStoreUtil.cleanUpKeyStore(); } - private static PrivateKey getKeystorePrivateKey(PublicKey pubKey, PrivateKey privKey) + private static PrivateKey getKeystorePrivateKey(PublicKey pubKey, PrivateKey privKey, + boolean isStrongBox) throws Exception { return (PrivateKey) KeyStoreUtil.saveKeysToKeystore( KEY_ALIAS_1, pubKey, privKey, new KeyProtection.Builder(KeyProperties.PURPOSE_AGREE_KEY) + .setIsStrongBoxBacked(isStrongBox) .build()) .getKey(KEY_ALIAS_1, null); } - private KeyPair generateECKeyPair(String alias, ECGenParameterSpec ecSpec) throws Exception { + private KeyPair generateECKeyPair(String alias, ECGenParameterSpec ecSpec, boolean isStrongBox) + throws Exception { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC", EXPECTED_PROVIDER_NAME); KeyGenParameterSpec ecKeySpec = new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_AGREE_KEY) - .setAlgorithmParameterSpec(ecSpec) - .build(); + .setAlgorithmParameterSpec(ecSpec) + .setIsStrongBoxBacked(isStrongBox) + .build(); keyGen.initialize(ecKeySpec); return keyGen.generateKeyPair(); @@ -540,8 +543,18 @@ public static final EcPublicKeyTestVector EC_VALID_PUBLIC_KEY = /** Checks that key agreement using ECDH works. */ @Test public void testBasic() throws Exception { - KeyPair keyPairA = generateECKeyPair(KEY_ALIAS_2, new ECGenParameterSpec("secp256r1")); - KeyPair keyPairB = generateECKeyPair(KEY_ALIAS_3, new ECGenParameterSpec("secp256r1")); + testBasic(false); + } + @Test + public void testBasic_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testBasic(true); + } + private void testBasic(boolean isStrongBox) throws Exception { + KeyPair keyPairA = generateECKeyPair(KEY_ALIAS_2, new ECGenParameterSpec("secp256r1"), + isStrongBox); + KeyPair keyPairB = generateECKeyPair(KEY_ALIAS_3, new ECGenParameterSpec("secp256r1"), + isStrongBox); KeyAgreement kaA = KeyAgreement.getInstance("ECDH", EXPECTED_PROVIDER_NAME); KeyAgreement kaB = KeyAgreement.getInstance("ECDH", EXPECTED_PROVIDER_NAME); @@ -583,8 +596,13 @@ public static final EcPublicKeyTestVector EC_VALID_PUBLIC_KEY = */ @SuppressWarnings("InsecureCryptoUsage") public void testModifiedPublic(String algorithm) throws Exception { + testModifiedPublic(algorithm, false); + } + @SuppressWarnings("InsecureCryptoUsage") + public void testModifiedPublic(String algorithm, boolean isStrongBox) throws Exception { KeyAgreement ka = KeyAgreement.getInstance(algorithm, EXPECTED_PROVIDER_NAME); - KeyPair pair = generateECKeyPair(KEY_ALIAS_1, new ECGenParameterSpec("secp256r1")); + KeyPair pair = generateECKeyPair(KEY_ALIAS_1, new ECGenParameterSpec("secp256r1"), + isStrongBox); KeyFactory kf = KeyFactory.getInstance("EC"); ECPublicKey validKey = (ECPublicKey) kf.generatePublic(EC_VALID_PUBLIC_KEY.getSpec()); ka.init(pair.getPrivate()); @@ -619,8 +637,13 @@ public static final EcPublicKeyTestVector EC_VALID_PUBLIC_KEY = */ @SuppressWarnings("InsecureCryptoUsage") public void testModifiedPublicSpec(String algorithm) throws Exception { + testModifiedPublicSpec(algorithm, false); + } + @SuppressWarnings("InsecureCryptoUsage") + public void testModifiedPublicSpec(String algorithm, boolean isStrongBox) throws Exception { KeyAgreement ka = KeyAgreement.getInstance(algorithm, EXPECTED_PROVIDER_NAME); - KeyPair pair = generateECKeyPair(KEY_ALIAS_1, new ECGenParameterSpec("secp256r1")); + KeyPair pair = generateECKeyPair(KEY_ALIAS_1, new ECGenParameterSpec("secp256r1"), + isStrongBox); KeyFactory kf = KeyFactory.getInstance("EC"); ECPublicKey validKey = (ECPublicKey) kf.generatePublic(EC_VALID_PUBLIC_KEY.getSpec()); ka.init(pair.getPrivate()); @@ -658,6 +681,11 @@ public static final EcPublicKeyTestVector EC_VALID_PUBLIC_KEY = public void testEcdhModifiedPublic() throws Exception { testModifiedPublic("ECDH"); } + @Test + public void testEcdhModifiedPublic_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testModifiedPublic("ECDH", true); + } @Test @Ignore // ECDHC algorithm is not supported in AndroidKeyStore @@ -669,6 +697,11 @@ public static final EcPublicKeyTestVector EC_VALID_PUBLIC_KEY = public void testEcdhModifiedPublicSpec() throws Exception { testModifiedPublicSpec("ECDH"); } + @Test + public void testEcdhModifiedPublicSpec_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testModifiedPublicSpec("ECDH", true); + } @Test @Ignore // ECDHC algorithm is not supported in AndroidKeyStore @@ -684,12 +717,20 @@ public static final EcPublicKeyTestVector EC_VALID_PUBLIC_KEY = */ // TODO(bleichen): This can be merged with testModifiedPublic once this is fixed. @SuppressWarnings("InsecureCryptoUsage") - public void testWrongOrder(String algorithm, ECParameterSpec spec) throws Exception { + public void testWrongOrder(String algorithm, ECParameterSpec spec) + throws Exception { + testWrongOrder(algorithm, spec, false); + } + @SuppressWarnings("InsecureCryptoUsage") + public void testWrongOrder(String algorithm, ECParameterSpec spec, boolean isStrongBox) + throws Exception { KeyAgreement ka = KeyAgreement.getInstance(algorithm, EXPECTED_PROVIDER_NAME); PrivateKey priv = generateECKeyPair(KEY_ALIAS_2, - new ECGenParameterSpec("secp256r1")).getPrivate(); + new ECGenParameterSpec("secp256r1"), isStrongBox) + .getPrivate(); ECPublicKey pub = (ECPublicKey) generateECKeyPair(KEY_ALIAS_3, - new ECGenParameterSpec("secp256r1")).getPublic(); + new ECGenParameterSpec("secp256r1"), isStrongBox) + .getPublic(); // Get the shared secret for the unmodified keys. ka.init(priv); ka.doPhase(pub, true); @@ -728,6 +769,11 @@ public static final EcPublicKeyTestVector EC_VALID_PUBLIC_KEY = public void testWrongOrderEcdhNist() throws Exception { testWrongOrder("ECDH", EcUtil.getNistP256Params()); } + @Test + public void testWrongOrderEcdhNist_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testWrongOrder("ECDH", EcUtil.getNistP256Params(), true); + } @Test @Ignore // Brainpool curves are not supported in AndroidKeyStore. @@ -750,6 +796,9 @@ public static final EcPublicKeyTestVector EC_VALID_PUBLIC_KEY = * occur for example when the private key is close to the order of the curve. */ private void testLargePrivateKey(ECParameterSpec spec) throws Exception { + testLargePrivateKey(spec, false); + } + private void testLargePrivateKey(ECParameterSpec spec, boolean isStrongBox) throws Exception { BigInteger order = spec.getOrder(); KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); keyGen.initialize(spec); @@ -764,11 +813,11 @@ public static final EcPublicKeyTestVector EC_VALID_PUBLIC_KEY = // This Public key is not pair of priv1, but it is required to create KeyPair to import into // AndroidKeyStore, So using dummy public key. PublicKey pub1 = kf.generatePublic(EC_VALID_PUBLIC_KEY.getX509EncodedKeySpec()); - ka.init(getKeystorePrivateKey(pub1, priv1)); + ka.init(getKeystorePrivateKey(pub1, priv1, isStrongBox)); ka.doPhase(pub, true); byte[] shared1 = ka.generateSecret(); PrivateKey priv2 = kf.generatePrivate(spec2); - ka.init(getKeystorePrivateKey(pub1, priv2)); + ka.init(getKeystorePrivateKey(pub1, priv2, isStrongBox)); ka.doPhase(pub, true); byte[] shared2 = ka.generateSecret(); // The private keys p1 and p2 are equivalent, since only the x-coordinate of the @@ -779,21 +828,17 @@ public static final EcPublicKeyTestVector EC_VALID_PUBLIC_KEY = @Test public void testNistCurveLargePrivateKey() throws Exception { - Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); - /** - * Software emulation of ECDH / AGREE_KEY function is performed by Keystore when the underlying - * device is a Keymaster implementation rather than KeyMint. However, this emulated support does - * not (yet) support imported ECDH keys, so skip the test if this is the case (b/216434270). - */ - assumeTrue("This test can only test with keymint version 1 and above", - TestUtils.getFeatureVersionKeystore(context) >= KeyStoreUtil.KM_VERSION_KEYMINT_1); - testLargePrivateKey(EcUtil.getNistP224Params()); testLargePrivateKey(EcUtil.getNistP256Params()); testLargePrivateKey(EcUtil.getNistP384Params()); // This test failed before CVE-2017-10176 was fixed. testLargePrivateKey(EcUtil.getNistP521Params()); } + @Test + public void testNistCurveLargePrivateKey_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testLargePrivateKey(EcUtil.getNistP256Params(), true); + } @Test @Ignore // Brainpool curves are not supported in AndroidKeyStore. diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/EcdsaTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/EcdsaTest.java index 3da6d83..3a758f3 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/EcdsaTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/EcdsaTest.java @@ -17,6 +17,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.math.BigInteger; +import java.nio.ByteBuffer; import java.security.InvalidAlgorithmParameterException; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -31,6 +32,7 @@ import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; import java.security.spec.ECParameterSpec; import java.util.Arrays; +import java.util.HashSet; import org.junit.After; import org.junit.Test; import org.junit.Ignore; @@ -49,21 +51,24 @@ import android.keystore.cts.util.KeyStoreUtil; public class EcdsaTest { private static final String EXPECTED_PROVIDER_NAME = TestUtil.EXPECTED_CRYPTO_OP_PROVIDER_NAME; private static final String KEY_ALIAS_1 = "TestKey"; + private static final String TAG = "EcdsaTest"; @After public void tearDown() throws Exception { KeyStoreUtil.cleanUpKeyStore(); } - private static PrivateKey getKeystorePrivateKey(PublicKey pubKey, PrivateKey privKey) - throws Exception { + private static PrivateKey getKeystorePrivateKey(PublicKey pubKey, PrivateKey privKey, + boolean isStrongBox) throws Exception { KeyProtection keyProtection = new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN) .setDigests(KeyProperties.DIGEST_SHA224, KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA384, KeyProperties.DIGEST_SHA512) + .setIsStrongBoxBacked(isStrongBox) .build(); - KeyStore keyStore = KeyStoreUtil.saveKeysToKeystore(KEY_ALIAS_1, pubKey, privKey, keyProtection); + KeyStore keyStore = KeyStoreUtil.saveKeysToKeystore(KEY_ALIAS_1, pubKey, privKey, + keyProtection); return (PrivateKey) keyStore.getKey(KEY_ALIAS_1, null); } @@ -86,6 +91,43 @@ public class EcdsaTest { } /** + * Returns true if the signature scheme is deterministic. Even though a non-deterministic + * signature scheme can in principle return the same signature twice this should never happen in + * practice. + */ + private boolean isDeterministic(Signature signer, PrivateKey priv) throws Exception { + byte[][] signature = new byte[2][]; + byte[] message = new byte[1]; + for (int i = 0; i < 2; i++) { + signer.initSign(priv); + signer.update(message); + signature[i] = signer.sign(); + } + return Arrays.equals(signature[0], signature[1]); + } + + /** + * Returns number of count messages to sign. If the signature scheme is deterministic then the + * messages are all different. If the signature scheme is randomized then the messages are all + * the same. If the messages signed are all the same then it may be easier to detect a bias. + */ + private byte[][] getMessagesToSign(int count, Signature signer, PrivateKey priv) + throws Exception { + byte[][] messages = new byte[count][]; + if (isDeterministic(signer, priv)) { + for (int i = 0; i < count; i++) { + messages[i] = ByteBuffer.allocate(4).putInt(i).array(); + } + } else { + byte[] msg = new byte[4]; + for (int i = 0; i < count; i++) { + messages[i] = msg; + } + } + return messages; + } + + /** * Extract the integer r from an ECDSA signature. This method implicitely assumes that the ECDSA * signature is DER encoded. and that the order of the curve is smaller than 2^1024. */ @@ -116,14 +158,13 @@ public class EcdsaTest { /** * Computes the bias of samples as * - * abs(sum(e^(2 pi i s m / modulus) for s in samples) / sqrt(samples.length). + * <p>abs(sum(e^(2 pi i s m / modulus) for s in samples) / sqrt(samples.length). * - * If the samples are taken from a uniform distribution in the range 0 .. modulus - 1 - * and the number of samples is significantly larger than L^2 - * then the probability that the result is larger than L is approximately e^(-L^2). - * The approximation can be derived from the assumption that samples taken from - * a uniform distribution give a result that approximates a standard complex normal - * distribution Z. I.e. Z has a density f_Z(z) = exp(-abs(z)^2) / pi. + * <p>If the samples are taken from a uniform distribution in the range 0 .. modulus - 1 and the + * number of samples is significantly larger than L^2 then the probability that the result is + * larger than L is approximately e^(-L^2). The approximation can be derived from the assumption + * that samples taken from a uniform distribution give a result that approximates a standard + * complex normal distribution Z. I.e. Z has a density f_Z(z) = exp(-abs(z)^2) / pi. * https://en.wikipedia.org/wiki/Complex_normal_distribution */ double bias(BigInteger[] samples, BigInteger modulus, BigInteger m) { @@ -134,7 +175,7 @@ public class EcdsaTest { // multiplier = 2 * pi / 2^52 double multiplier = 1.3951473992034527e-15; // computes the quotent 2 * pi * r / modulus - double quot = r.shiftLeft(52).divide(modulus).doubleValue() * multiplier; + double quot = r.shiftLeft(52).divide(modulus).doubleValue() * multiplier; sumReal += Math.cos(quot); sumImag += Math.sin(quot); } @@ -151,13 +192,18 @@ public class EcdsaTest { * @throws Exception if an unexpected error occurred. */ boolean testParameters(String algorithm, String curve) throws Exception { + return testParameters(algorithm, curve, false); + } + boolean testParameters(String algorithm, String curve, boolean isStrongBox) throws Exception { + if (isStrongBox) { + KeyStoreUtil.assumeStrongBox(); + } String message = "123400"; KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); - ECGenParameterSpec ecSpec = new ECGenParameterSpec(curve); KeyPair keyPair; try { - keyGen.initialize(ecSpec); + keyGen.initialize(new ECGenParameterSpec(curve)); keyPair = keyGen.generateKeyPair(); } catch (InvalidAlgorithmParameterException ex) { // The curve is not supported. @@ -181,7 +227,7 @@ public class EcdsaTest { // Both algorithm and curve are supported. // Hence, we expect that signing and verifying properly works. byte[] messageBytes = message.getBytes("UTF-8"); - signer.initSign(getKeystorePrivateKey(pub, priv)); + signer.initSign(getKeystorePrivateKey(pub, priv, isStrongBox)); signer.update(messageBytes); byte[] signature = signer.sign(); verifier.initVerify(pub); @@ -200,13 +246,30 @@ public class EcdsaTest { String curve = "secp256r1"; assertTrue(testParameters(algorithm, curve)); } + @Test + public void testBasic_StrongBox() throws Exception { + String algorithm = "SHA256WithECDSA"; + String curve = "secp256r1"; + assertTrue(testParameters(algorithm, curve, true)); + } /** Checks whether the one time key k in ECDSA is biased. */ - public void testBias(String algorithm, String curve, ECParameterSpec ecParams) throws Exception { + public void testBias(String algorithm, String curve) throws Exception { + testBias(algorithm, curve, false); + } + public void testBias(String algorithm, String curve, + boolean isStrongBox) throws Exception { + if (isStrongBox) { + KeyStoreUtil.assumeStrongBox(); + } + Signature signer = Signature.getInstance(algorithm, EXPECTED_PROVIDER_NAME); KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); - keyGen.initialize(ecParams); + keyGen.initialize(new ECGenParameterSpec(curve)); KeyPair keyPair = keyGen.generateKeyPair(); - ECPrivateKey priv = (ECPrivateKey) keyPair.getPrivate(); + + ECPrivateKey priv = (ECPrivateKey)keyPair.getPrivate(); + PrivateKey keystorePrivateKey = getKeystorePrivateKey(keyPair.getPublic(), + keyPair.getPrivate(), isStrongBox); // If we throw a fair coin tests times then the probability that // either heads or tails appears less than mincount is less than 2^{-32}. // Therefore the test below is not expected to fail unless the generation @@ -214,23 +277,17 @@ public class EcdsaTest { final int tests = 1024; final int mincount = 410; - String hashAlgorithm = getHashAlgorithm(algorithm); - String message = "Hello"; - byte[] messageBytes = message.getBytes("UTF-8"); - byte[] digest = MessageDigest.getInstance(hashAlgorithm).digest(messageBytes); - - // TODO(bleichen): Truncate the digest if the digest size is larger than the - // curve size. - BigInteger h = new BigInteger(1, digest); - BigInteger q = priv.getParams().getOrder(); - BigInteger qHalf = q.shiftRight(1); - - Signature signer = Signature.getInstance(algorithm, EXPECTED_PROVIDER_NAME); - signer.initSign(getKeystorePrivateKey(keyPair.getPublic(), keyPair.getPrivate())); BigInteger[] kList = new BigInteger[tests]; + byte[][] message = getMessagesToSign(tests, signer, keystorePrivateKey); + signer.initSign(keystorePrivateKey); + String hashAlgorithm = getHashAlgorithm(algorithm); for (int i = 0; i < tests; i++) { - signer.update(messageBytes); + signer.update(message[i]); byte[] signature = signer.sign(); + byte[] digest = MessageDigest.getInstance(hashAlgorithm).digest(message[i]); + // TODO(bleichen): Truncate the digest if the digest size is larger than the + // curve size. + BigInteger h = new BigInteger(1, digest); kList[i] = extractK(signature, h, priv); } @@ -238,6 +295,8 @@ public class EcdsaTest { // of the value k are unbiased. int countMsb = 0; // count the number of k's with lsb set int countLsb = 0; // count the number of k's with msb set + BigInteger q = priv.getParams().getOrder(); + BigInteger qHalf = q.shiftRight(1); for (BigInteger k : kList) { if (k.testBit(0)) { countLsb++; @@ -270,7 +329,7 @@ public class EcdsaTest { fail("Bias for k detected. bias1 = " + bias1); } // Same as above but shifing by one bit. - double bias2 = bias(kList, q, BigInteger.valueOf(2)); + double bias2 = bias(kList, q, BigInteger.valueOf(2)); if (bias2 > threshold) { fail("Bias for k detected. bias2 = " + bias2); } @@ -289,16 +348,116 @@ public class EcdsaTest { } @Test - public void testBiasAll() throws Exception { - testBias("SHA256WithECDSA", "secp256r1", EcUtil.getNistP256Params()); - testBias("SHA224WithECDSA", "secp224r1", EcUtil.getNistP224Params()); - testBias("SHA384WithECDSA", "secp384r1", EcUtil.getNistP384Params()); - testBias("SHA512WithECDSA", "secp521r1", EcUtil.getNistP521Params()); + public void testBiasSecp224r1() throws Exception { + testBias("SHA224WithECDSA", "secp224r1"); + } + + @Test + public void testBiasSecp256r1() throws Exception { + testBias("SHA256WithECDSA", "secp256r1"); + } + + @Test + public void testBiasSecp384r1() throws Exception { + testBias("SHA384WithECDSA", "secp384r1"); + } + + @Test + public void testBiasSecp521r1() throws Exception { + testBias("SHA512WithECDSA", "secp521r1"); + } + + @Test + public void testBiasSecp521r1_StrongBox() throws Exception { + testBias("SHA256WithECDSA", "secp256r1", true); } @Test @Ignore // Brainpool curve are not supported in AndroidKeyStore - public void testBiasBrainpoolCurve() throws Exception { - testBias("SHA256WithECDSA", "brainpoolP256r1", EcUtil.getBrainpoolP256r1Params()); + public void testBiasBrainpoolP256r1() throws Exception { + testBias("SHA512WithECDSA", "brainpoolP256r1"); + } + + /** + * This test uses the deterministic ECDSA implementation from BouncyCastle (if BouncyCastle is + * being tested.) + */ + @Test + @Ignore // Algorithm SHA256WithECDDSA is not supported in AndroidKeyStore. + public void testBiasSecp256r1ECDDSA() throws Exception { + testBias("SHA256WithECDDSA", "secp256r1"); + } + + /** + * Tests initSign with a null value for SecureRandom. The expected behaviour is that a default + * instance of SecureRandom is used and that this instance is properly seeded. I.e., the expected + * behaviour is that Signature.initSign(ECPrivateKey, null) behaves like + * Signature.initSign(ECPrivateKey). If the signature scheme normally is randomized then + * Signature.initSign(ECprivateKey, null) should still be a randomized signature scheme. If the + * implementation is deterministic then we simply want this to work. + * + * <p>In principle, the correct behaviour is not really defined. However, if a provider would + * throw a null pointer exception then this can lead to unnecessary breakages. + */ + public void testNullRandom(String algorithm, String curve) throws Exception { + testNullRandom(algorithm, curve, false); + } + public void testNullRandom(String algorithm, String curve, boolean isStrongBox) + throws Exception { + if (isStrongBox) { + KeyStoreUtil.assumeStrongBox(); + } + int samples = 8; + Signature signer = Signature.getInstance(algorithm); + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); + keyGen.initialize(new ECGenParameterSpec(curve)); + KeyPair keyPair = keyGen.generateKeyPair(); + PrivateKey priv = getKeystorePrivateKey(keyPair.getPublic(), keyPair.getPrivate(), + isStrongBox); + byte[][] message = getMessagesToSign(samples, signer, priv); + HashSet<BigInteger> rSet = new HashSet<>(); + for (int i = 0; i < samples; i++) { + // This is the function call that is tested by this test. + signer.initSign(priv, null); + signer.update(message[i]); + byte[] signature = signer.sign(); + BigInteger r = extractR(signature); + assertTrue("Same r computed twice", rSet.add(r)); + } + } + + @Test + public void testNullRandomSecp224r1() throws Exception { + testNullRandom("SHA224WithECDSA", "secp224r1"); + } + + @Test + public void testNullRandomSecp256r1() throws Exception { + testNullRandom("SHA256WithECDSA", "secp256r1"); + } + + @Test + public void testNullRandomSecp256r1_StrongBox() throws Exception { + testNullRandom("SHA256WithECDSA", "secp256r1", true); + } + + @Test + public void testNullRandomSecp384r1() throws Exception { + testNullRandom("SHA384WithECDSA", "secp384r1"); + } + + @Test + public void testNullRandomSecp521r1() throws Exception { + testNullRandom("SHA512WithECDSA", "secp521r1"); + } + + /** + * This test uses the deterministic ECDSA implementation from BouncyCastle (if BouncyCastle is + * being tested.) + */ + @Test + @Ignore // Algorithm SHA256WithECdDSA is not supported in AndroidKeyStore. + public void testNullRandomSecp256r1ECDDSA() throws Exception { + testNullRandom("SHA256WithECdDSA", "secp256r1"); } } diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/JsonAeadTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonAeadTest.java index 91a0d2c..c4a6b63 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/JsonAeadTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonAeadTest.java @@ -89,7 +89,7 @@ public class JsonAeadTest { * @throws Exception if the initialization failed. */ protected static Cipher getInitializedCipher( - String algorithm, int opmode, byte[] key, byte[] iv, int tagSize) + String algorithm, int opmode, byte[] key, byte[] iv, int tagSize, boolean isStrongBox) throws Exception { Cipher cipher = Cipher.getInstance(algorithm, EXPECTED_CRYPTO_PROVIDER_NAME); if (algorithm.equalsIgnoreCase("AES/GCM/NoPadding")) { @@ -99,6 +99,7 @@ public class JsonAeadTest { .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setRandomizedEncryptionRequired(false) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .setIsStrongBoxBacked(isStrongBox) .build()); // Key imported, obtain a reference to it. SecretKey keyStoreKey = (SecretKey) keyStore.getKey(KEY_ALIAS_1, null); @@ -152,6 +153,9 @@ public class JsonAeadTest { // This is a false positive, since errorprone cannot track values passed into a method. @SuppressWarnings("InsecureCryptoUsage") public void testAead(String filename, String algorithm) throws Exception { + testAead(filename, algorithm, false); + } + public void testAead(String filename, String algorithm, boolean isStrongBox) throws Exception { // Version number have the format major.minor[.subversion]. // Versions before 1.0 are experimental and use formats that are expected to change. // Versions after 1.0 change the major number if the format changes and change @@ -203,7 +207,8 @@ public class JsonAeadTest { // Test encryption Cipher cipher; try { - cipher = getInitializedCipher(algorithm, Cipher.ENCRYPT_MODE, key, iv, tagSize); + cipher = getInitializedCipher(algorithm, Cipher.ENCRYPT_MODE, key, iv, tagSize, + isStrongBox); } catch (GeneralSecurityException ex) { // Some libraries restrict key size, iv size and tag size. // Because of the initialization of the cipher might fail. @@ -234,7 +239,8 @@ public class JsonAeadTest { // Test decryption Cipher decCipher; try { - decCipher = getInitializedCipher(algorithm, Cipher.DECRYPT_MODE, key, iv, tagSize); + decCipher = getInitializedCipher(algorithm, Cipher.DECRYPT_MODE, key, iv, tagSize, + isStrongBox); } catch (GeneralSecurityException ex) { errors++; continue; @@ -265,6 +271,11 @@ public class JsonAeadTest { public void testAesGcm() throws Exception { testAead("aes_gcm_test.json", "AES/GCM/NoPadding"); } + @Test + public void testAesGcm_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testAead("aes_gcm_test.json", "AES/GCM/NoPadding", true); + } @Test @Ignore // Ignored due to AES/EAX algorithm not supported in AndroidKeyStore. diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/JsonCipherTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonCipherTest.java index e0c5b27..d6dae08 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/JsonCipherTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonCipherTest.java @@ -74,9 +74,10 @@ public class JsonCipherTest { * @param opmode either Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE * @param key raw key bytes * @param iv the initialisation vector + * @param isStrongBox whether key should store in StrongBox or not */ - protected static void initCipher( - Cipher cipher, String algorithm, int opmode, byte[] key, byte[] iv) throws Exception { + protected static void initCipher(Cipher cipher, String algorithm, int opmode, + byte[] key, byte[] iv, boolean isStrongBox) throws Exception { SecretKeySpec keySpec = null; if (algorithm.startsWith("AES/")) { keySpec = new SecretKeySpec(key, "AES"); @@ -89,6 +90,7 @@ public class JsonCipherTest { .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) .setRandomizedEncryptionRequired(false) + .setIsStrongBoxBacked(isStrongBox) .build()); // Key imported, obtain a reference to it. SecretKey keyStoreKey = (SecretKey) keyStore.getKey(KEY_ALIAS_1, null); @@ -124,6 +126,10 @@ public class JsonCipherTest { // This is a false positive, since errorprone cannot track values passed into a method. @SuppressWarnings("InsecureCryptoUsage") public void testCipher(String filename, String algorithm) throws Exception { + testCipher(filename, algorithm, false); + } + @SuppressWarnings("InsecureCryptoUsage") + public void testCipher(String filename, String algorithm, boolean isStrongBox) throws Exception { // Testing with old test vectors may a reason for a test failure. // Version number have the format major.minor[status]. // Versions before 1.0 are experimental and use formats that are expected to change. @@ -164,7 +170,7 @@ public class JsonCipherTest { // Test encryption try { - initCipher(cipher, algorithm, Cipher.ENCRYPT_MODE, key, iv); + initCipher(cipher, algorithm, Cipher.ENCRYPT_MODE, key, iv, isStrongBox); } catch (GeneralSecurityException ex) { // Some libraries restrict key size, iv size and tag size. // Because of the initialization of the cipher might fail. @@ -195,7 +201,7 @@ public class JsonCipherTest { // However, all the test vectors in Wycheproof are constructed such that they have // invalid padding. If this changes then the test below is too strict. try { - initCipher(cipher, algorithm, Cipher.DECRYPT_MODE, key, iv); + initCipher(cipher, algorithm, Cipher.DECRYPT_MODE, key, iv, isStrongBox); } catch (GeneralSecurityException ex) { errors++; continue; @@ -238,9 +244,16 @@ public class JsonCipherTest { } @Test - public void testAesCbcPkcs5() throws Exception { + public void testAesCbcPkcs7() throws Exception { // AndroidKeyStore only suuport AES/CBC/PKCS7Padding algorithm, // so it is used instead of PKCS5Padding testCipher("aes_cbc_pkcs5_test.json", "AES/CBC/PKCS7Padding"); } + @Test + public void testAesCbcPkcs7_StrongBox() throws Exception { + // AndroidKeyStore only suuport AES/CBC/PKCS7Padding algorithm, + // so it is used instead of PKCS5Padding + KeyStoreUtil.assumeStrongBox(); + testCipher("aes_cbc_pkcs5_test.json", "AES/CBC/PKCS7Padding", true); + } } diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/JsonEcdhTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonEcdhTest.java index f5b97f8..779dc42 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/JsonEcdhTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonEcdhTest.java @@ -88,6 +88,9 @@ public class JsonEcdhTest { * ... **/ public void testEcdhComp(String filename) throws Exception { + testEcdhComp(filename, false); + } + public void testEcdhComp(String filename, boolean isStrongBox) throws Exception { JsonObject test = JsonUtil.getTestVectors(this.getClass(), filename); // This test expects test vectors as defined in wycheproof/schemas/ecdh_test_schema.json. @@ -122,6 +125,7 @@ public class JsonEcdhTest { KeyStore keyStore = KeyStoreUtil.saveKeysToKeystore(KEY_ALIAS_1, pubKey, privKey, new KeyProtection.Builder(KeyProperties.PURPOSE_AGREE_KEY) + .setIsStrongBoxBacked(isStrongBox) .build()); KeyAgreement ka = KeyAgreement.getInstance("ECDH", EXPECTED_PROVIDER_NAME); PrivateKey keyStorePrivateKey = (PrivateKey) keyStore.getKey(KEY_ALIAS_1, null); @@ -156,6 +160,8 @@ public class JsonEcdhTest { passedTests++; } } catch (InvalidKeySpecException | InvalidKeyException | NoSuchAlgorithmException ex) { + Log.e(TAG, + "Test vector with tcId:" + tcid + " comment:" + comment + " throws:" + ex.toString()); // These are the exception that we expect to see when a curve is not implemented // or when a key is not valid. if (result.equals("valid")) { @@ -172,29 +178,31 @@ public class JsonEcdhTest { } } assertEquals(0, errors); - assertEquals(numTests, passedTests); + assertEquals(numTests, passedTests + rejectedTests); } @Test - @Ignore //TODO Reverify after bug b/215175472 is fixed. public void testSecp224r1() throws Exception { testEcdhComp("ecdh_secp224r1_test.json"); } @Test - @Ignore //TODO Reverify after bug b/215175472 is fixed. public void testSecp256r1() throws Exception { testEcdhComp("ecdh_secp256r1_test.json"); } - @Test @Ignore //TODO Reverify after bug b/215175472 is fixed. + public void testSecp256r1_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testEcdhComp("ecdh_secp256r1_test.json", true); + } + + @Test public void testSecp384r1() throws Exception { testEcdhComp("ecdh_secp384r1_test.json"); } @Test - @Ignore //TODO Reverify after bug b/215175472 is fixed. public void testSecp521r1() throws Exception { testEcdhComp("ecdh_secp521r1_test.json"); } diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/JsonMacTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonMacTest.java index c1dddc5..d813d02 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/JsonMacTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonMacTest.java @@ -72,8 +72,8 @@ public class JsonMacTest { * if the initialization failed. For example one case are GMACs with a tag size othe than 128 * bits, since the JCE interface does not seem to support such a specification. */ - protected static byte[] computeMac(String algorithm, byte[] key, byte[] msg, int tagSize) - throws Exception { + protected static byte[] computeMac(String algorithm, byte[] key, byte[] msg, int tagSize, + boolean isStrongBox) throws Exception { Mac mac = Mac.getInstance(algorithm, EXPECTED_PROVIDER_NAME); algorithm = algorithm.toUpperCase(Locale.ENGLISH); if (algorithm.startsWith("HMAC")) { @@ -91,7 +91,9 @@ public class JsonMacTest { // full length tag and truncates it. The drawback of having to truncate tags is that // the caller has to compare truncated tags during verification. KeyStore keyStore = KeyStoreUtil.saveSecretKeyToKeystore(KEY_ALIAS_1, keySpec, - new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN).build()); + new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN) + .setIsStrongBoxBacked(isStrongBox) + .build()); // Key imported, obtain a reference to it. Key keyStoreKey = keyStore.getKey(KEY_ALIAS_1, null); mac.init(keyStoreKey); @@ -110,6 +112,9 @@ public class JsonMacTest { * @param filename the JSON file with the test vectors. */ public void testMac(String filename) throws Exception { + testMac(filename, false); + } + public void testMac(String filename, boolean isStrongBox) throws Exception { // Checking preconditions. JsonObject test = JsonUtil.getTestVectors(this.getClass(), filename); String algorithm = test.get("algorithm").getAsString(); @@ -130,14 +135,17 @@ public class JsonMacTest { byte[] key = getBytes(testcase, "key"); byte[] msg = getBytes(testcase, "msg"); byte[] expectedTag = getBytes(testcase, "tag"); + // Strongbox only supports key size from 8 to 32 bytes. + if (isStrongBox && (key.length < 8 || key.length > 32)) { + continue; + } // Result is one of "valid", "invalid", "acceptable". // "valid" are test vectors with matching plaintext, ciphertext and tag. // "invalid" are test vectors with invalid parameters or invalid ciphertext and tag. // "acceptable" are test vectors with weak parameters or legacy formats. String result = testcase.get("result").getAsString(); - byte[] computedTag = null; - computedTag = computeMac(algorithm, key, msg, tagSize); + computedTag = computeMac(algorithm, key, msg, tagSize, isStrongBox); boolean eq = arrayEquals(expectedTag, computedTag); if (result.equals("invalid")) { @@ -264,6 +272,11 @@ public class JsonMacTest { public void testHmacSha256() throws Exception { testMac("hmac_sha256_test.json"); } + @Test + public void testHmacSha256_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testMac("hmac_sha256_test.json", true); + } @Test public void testHmacSha384() throws Exception { diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/JsonSignatureTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonSignatureTest.java index 58da530..bbea28a 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/JsonSignatureTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonSignatureTest.java @@ -54,13 +54,12 @@ public class JsonSignatureTest { KeyStoreUtil.cleanUpKeyStore(); } - private static PrivateKey getKeystorePrivateKey(PublicKey pubKey, PrivateKey privKey) - throws Exception { + private static PrivateKey getKeystorePrivateKey(PublicKey pubKey, PrivateKey privKey, String digest, + boolean isStrongBox) throws Exception { KeyProtection keyProtection = new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN) - .setDigests(KeyProperties.DIGEST_SHA1, KeyProperties.DIGEST_SHA224, - KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA384, - KeyProperties.DIGEST_SHA512) + .setDigests(digest) .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1) + .setIsStrongBoxBacked(isStrongBox) .build(); KeyStore keyStore = KeyStoreUtil.saveKeysToKeystore(KEY_ALIAS_1, pubKey, privKey, keyProtection); @@ -263,16 +262,18 @@ public class JsonSignatureTest { */ // This is a false positive, since errorprone cannot track values passed into a method. @SuppressWarnings("InsecureCryptoUsage") - protected static PrivateKey getPrivateKey(JsonObject object, String algorithm) throws Exception { + protected static PrivateKey getPrivateKey(JsonObject object, String algorithm, + boolean isStrongBox) throws Exception { if (algorithm.equals("RSA")) { KeyFactory kf = KeyFactory.getInstance(algorithm); byte[] encoded = TestUtil.hexToBytes(getString(object, "privateKeyPkcs8")); byte[] pubEncoded = TestUtil.hexToBytes(getString(object, "keyDer")); + String digest = getString(object, "sha"); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); PrivateKey intermediateKey = kf.generatePrivate(keySpec); X509EncodedKeySpec x509keySpec = new X509EncodedKeySpec(pubEncoded); PublicKey pubKey = kf.generatePublic(x509keySpec); - return getKeystorePrivateKey(pubKey, intermediateKey); + return getKeystorePrivateKey(pubKey, intermediateKey, digest, isStrongBox); } else { throw new NoSuchAlgorithmException("Algorithm " + algorithm + " is not supported"); } @@ -432,8 +433,13 @@ public class JsonSignatureTest { * @param allowSkippingKeys if true then keys that cannot be constructed will not fail the test. */ public void testSigning( + String filename, String signatureAlgorithm, Format signatureFormat, + boolean allowSkippingKeys) throws Exception { + testSigning(filename, signatureAlgorithm, signatureFormat, allowSkippingKeys, false); + } + public void testSigning( String filename, String signatureAlgorithm, Format signatureFormat, - boolean allowSkippingKeys) throws Exception { + boolean allowSkippingKeys, boolean isStrongBox) throws Exception { JsonObject test = JsonUtil.getTestVectors(this.getClass(), filename); int cntTests = 0; int errors = 0; @@ -442,7 +448,7 @@ public class JsonSignatureTest { JsonObject group = g.getAsJsonObject(); PrivateKey key; try { - key = getPrivateKey(group, signatureAlgorithm); + key = getPrivateKey(group, signatureAlgorithm, isStrongBox); } catch (GeneralSecurityException ex) { skippedKeys++; continue; @@ -466,13 +472,17 @@ public class JsonSignatureTest { signer.update(message); String sig = TestUtil.bytesToHex(signer.sign()); if (!sig.equals(expectedSig)) { + android.util.Log.e("JsonSignatureTest", "Signature mismatch error for test id " + tcid); errors++; } else { cntTests++; } } catch (InvalidKeyException | SignatureException ex) { if (result.equals("valid")) { - errors++; + android.util.Log.e("JsonSignatureTest", "Unexpected exception for test id " + tcid, ex); + if (!isStrongBox) { + errors++; + } } } } @@ -725,9 +735,14 @@ public class JsonSignatureTest { // Testing RSA PKCS#1 v1.5 signatures. @Test - public void testRsaSigning() throws Exception { + public void testRsaSigning() throws Exception { testSigning("rsa_sig_gen_misc_test.json", "RSA", Format.RAW, true); } + @Test + public void testRsaSigning_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testSigning("rsa_sig_gen_misc_test.json", "RSA", Format.RAW, true, true); + } @Test public void testRsaSignatures() throws Exception { diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/MacTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/MacTest.java index 171af58..3a42761 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/MacTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/MacTest.java @@ -46,10 +46,12 @@ public class MacTest { KeyStoreUtil.cleanUpKeyStore(); } - private static Key getKeyStoreSecretKey(byte[] keyMaterial, String algorithm) throws Exception { + private static Key getKeyStoreSecretKey(byte[] keyMaterial, String algorithm, boolean isStrongBox) + throws Exception { KeyStore keyStore = KeyStoreUtil.saveSecretKeyToKeystore(KEY_ALIAS_1, - new SecretKeySpec(keyMaterial, algorithm), - new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN).build()); + new SecretKeySpec(keyMaterial, algorithm), + new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN) + .setIsStrongBoxBacked(isStrongBox).build()); return keyStore.getKey(KEY_ALIAS_1, null); } @@ -192,6 +194,9 @@ public class MacTest { } public void testMac(String algorithm, int keySize) throws Exception { + testMac(algorithm, keySize, false); + } + public void testMac(String algorithm, int keySize, boolean isStrongBox) throws Exception { try { Mac.getInstance(algorithm, EXPECTED_PROVIDER_NAME); } catch (NoSuchAlgorithmException ex) { @@ -200,7 +205,7 @@ public class MacTest { byte[] key = new byte[keySize]; SecureRandom rand = new SecureRandom(); rand.nextBytes(key); - testUpdate(algorithm, getKeyStoreSecretKey(key, algorithm)); + testUpdate(algorithm, getKeyStoreSecretKey(key, algorithm, isStrongBox)); } @Test @@ -219,6 +224,13 @@ public class MacTest { } @Test + @Ignore // StrongBox takes very long time to complete this test and CTS timed out (b/242028608), hence ignoring it. + public void testHmacSha256_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testMac("HMACSHA256", 32, true); + } + + @Test public void testHmacSha384() throws Exception { testMac("HMACSHA384", 48); } @@ -298,10 +310,15 @@ public class MacTest { * IMPLEMENTATION RETURNS INCORRECT HASH FOR LARGE SETS OF DATA */ private void testLongMac( - String algorithm, String keyhex, String message, long repetitions, String expected) - throws Exception { + String algorithm, String keyhex, String message, long repetitions, String expected) + throws Exception { + testLongMac(algorithm, keyhex, message, repetitions, expected, false); + } + private void testLongMac( + String algorithm, String keyhex, String message, long repetitions, String expected, + boolean isStrongBox) throws Exception { - Key key = getKeyStoreSecretKey(TestUtil.hexToBytes(keyhex), algorithm); + Key key = getKeyStoreSecretKey(TestUtil.hexToBytes(keyhex), algorithm, isStrongBox); byte[] bytes = message.getBytes(UTF_8); byte[] mac = null; try { @@ -331,18 +348,29 @@ public class MacTest { @Test public void testLongMacSha256() throws Exception { + testLongMacSha256(false); + } + @Test + @Ignore // StrongBox takes very long time to complete this test and CTS timed out (b/242028608), hence ignoring it. + public void testLongMacSha256_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testLongMacSha256(true); + } + private void testLongMacSha256(boolean isStrongBox) throws Exception { testLongMac( "HMACSHA256", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", "a", 2147483647L, - "84f213c9bb5b329d547bc31dabed41939754b1af7482365ec74380c45f6ea0a7"); + "84f213c9bb5b329d547bc31dabed41939754b1af7482365ec74380c45f6ea0a7", + isStrongBox); testLongMac( "HMACSHA256", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", "a", 5000000000L, - "59a75754df7093fa4339aa618b64b104f153a5b42cc85394fdb8735b13ea684a"); + "59a75754df7093fa4339aa618b64b104f153a5b42cc85394fdb8735b13ea684a", + isStrongBox); } @Test diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/RsaEncryptionTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/RsaEncryptionTest.java index 32a78b3..ed291e0 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/RsaEncryptionTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/RsaEncryptionTest.java @@ -77,7 +77,8 @@ public class RsaEncryptionTest { */ // This is a false positive, since errorprone cannot track values passed into a method. @SuppressWarnings("InsecureCryptoUsage") - protected static PrivateKey getPrivateKey(JsonObject object) throws Exception { + protected static PrivateKey getPrivateKey(JsonObject object, boolean isStrongBox) + throws Exception { KeyFactory kf; kf = KeyFactory.getInstance("RSA"); byte[] encoded = TestUtil.hexToBytes(object.get("privateKeyPkcs8").getAsString()); @@ -90,6 +91,7 @@ public class RsaEncryptionTest { KeyStore keyStore = KeyStoreUtil.saveKeysToKeystore(KEY_ALIAS_SIG, pubKey, intermediateKey, new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) + .setIsStrongBoxBacked(isStrongBox) .build()); return (PrivateKey) keyStore.getKey(KEY_ALIAS_SIG, null); } @@ -143,6 +145,10 @@ public class RsaEncryptionTest { */ @SuppressWarnings("InsecureCryptoUsage") public void testDecryption(String filename) throws Exception { + testDecryption(filename, false); + } + @SuppressWarnings("InsecureCryptoUsage") + public void testDecryption(String filename, boolean isStrongBox) throws Exception { JsonObject test = JsonUtil.getTestVectors(this.getClass(), filename); // Padding oracle attacks become simpler when the decryption leaks detailed information about // invalid paddings. Hence implementations are expected to not include such information in the @@ -157,7 +163,7 @@ public class RsaEncryptionTest { Cipher decrypter = Cipher.getInstance("RSA/ECB/PKCS1Padding", EXPECTED_PROVIDER_NAME); for (JsonElement g : test.getAsJsonArray("testGroups")) { JsonObject group = g.getAsJsonObject(); - PrivateKey key = getPrivateKey(group); + PrivateKey key = getPrivateKey(group, isStrongBox); for (JsonElement t : group.getAsJsonArray("tests")) { JsonObject testcase = t.getAsJsonObject(); int tcid = testcase.get("tcId").getAsInt(); @@ -209,6 +215,11 @@ public class RsaEncryptionTest { public void testDecryption2048() throws Exception { testDecryption("rsa_pkcs1_2048_test.json"); } + @Test + public void testDecryption2048_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testDecryption("rsa_pkcs1_2048_test.json", true); + } @Test public void testDecryption3072() throws Exception { diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/RsaOaepTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/RsaOaepTest.java index 4fd12cb..ed4987c 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/RsaOaepTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/RsaOaepTest.java @@ -15,6 +15,7 @@ package com.google.security.wycheproof; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -57,7 +58,8 @@ public class RsaOaepTest { } private static PrivateKey saveKeyPairToKeystoreAndReturnPrivateKey(PublicKey pubKey, - PrivateKey privKey) throws Exception { + PrivateKey privKey, String digest, String mgfDigest, boolean isStrongBox) + throws Exception { return (PrivateKey) KeyStoreUtil.saveKeysToKeystore(KEY_ALIAS_1, pubKey, privKey, new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY | @@ -65,9 +67,8 @@ public class RsaOaepTest { KeyProperties.PURPOSE_DECRYPT) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1, KeyProperties.ENCRYPTION_PADDING_RSA_OAEP) - .setDigests(KeyProperties.DIGEST_SHA1, KeyProperties.DIGEST_SHA224, - KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA384, - KeyProperties.DIGEST_SHA512) + .setDigests(digest, mgfDigest) + .setIsStrongBoxBacked(isStrongBox) .build()) .getKey(KEY_ALIAS_1, null); } @@ -162,7 +163,8 @@ public class RsaOaepTest { */ // This is a false positive, since errorprone cannot track values passed into a method. @SuppressWarnings("InsecureCryptoUsage") - protected static PrivateKey getPrivateKey(JsonObject object) throws Exception { + protected static PrivateKey getPrivateKey(JsonObject object, boolean isStrongBox) + throws Exception { KeyFactory kf; kf = KeyFactory.getInstance("RSA"); byte[] encoded = TestUtil.hexToBytes(getString(object, "privateKeyPkcs8")); @@ -171,7 +173,17 @@ public class RsaOaepTest { BigInteger modulus = new BigInteger(TestUtil.hexToBytes(object.get("n").getAsString())); BigInteger exponent = new BigInteger(TestUtil.hexToBytes(object.get("e").getAsString())); PublicKey pubKey = kf.generatePublic(new RSAPublicKeySpec(modulus, exponent)); - return saveKeyPairToKeystoreAndReturnPrivateKey(pubKey, intermediateKey); + String digest = getString(object, "sha"); + String mgfDigest = getString(object, "mgfSha"); + int keysize = object.get("keysize").getAsInt(); + if (isStrongBox + && (!KeyStoreUtil.isStrongBoxSupportDigest(digest) + || !KeyStoreUtil.isStrongBoxSupportDigest(mgfDigest) + || !KeyStoreUtil.isStrongBoxSupportKeySize(keysize))) { + throw new UnsupportedKeyParametersException(); + } + return saveKeyPairToKeystoreAndReturnPrivateKey(pubKey, intermediateKey, digest, mgfDigest, + isStrongBox); } protected static String getOaepAlgorithmName(JsonObject group) throws Exception { @@ -185,9 +197,15 @@ public class RsaOaepTest { String sha = getString(group, "sha"); String mgf = getString(group, "mgf"); String mgfSha = getString(group, "mgfSha"); + // mgfDigest other than SHA-1 are supported from KeyMint V1 and above. + if (!mgfSha.equalsIgnoreCase("SHA-1")) { + assumeTrue("This test is valid for KeyMint version 1 and above.", + KeyStoreUtil.getFeatureVersionKeystore() >= KeyStoreUtil.KM_VERSION_KEYMINT_1); + } PSource p = PSource.PSpecified.DEFAULT; if (test.has("label") && !TextUtils.isEmpty(getString(test, "label"))) { - p = new PSource.PSpecified(getBytes(test, "label")); + // p = new PSource.PSpecified(getBytes(test, "label")); + throw new UnsupportedKeyParametersException(); } return new OAEPParameterSpec(sha, mgf, new MGF1ParameterSpec(mgfSha), p); } @@ -235,7 +253,17 @@ public class RsaOaepTest { * specified. **/ public void testOaep(String filename, boolean allowSkippingKeys) + throws Exception { + testOaep(filename, allowSkippingKeys, false); + } + + private static class UnsupportedKeyParametersException extends Exception { } + + public void testOaep(String filename, boolean allowSkippingKeys, boolean isStrongBox) throws Exception { + if (isStrongBox) { + KeyStoreUtil.assumeStrongBox(); + } JsonObject test = JsonUtil.getTestVectors(this.getClass(), filename); // Compares the expected and actual JSON schema of the test vector file. @@ -258,7 +286,18 @@ public class RsaOaepTest { int skippedKeys = 0; for (JsonElement g : test.getAsJsonArray("testGroups")) { JsonObject group = g.getAsJsonObject(); - PrivateKey key = getPrivateKey(group); + PrivateKey key = null; + try { + key = getPrivateKey(group, isStrongBox); + } catch (UnsupportedKeyParametersException e) { + skippedKeys++; + if (isStrongBox) { + continue; + } + if (!allowSkippingKeys) { + throw e; + } + } String algorithm = getOaepAlgorithmName(group); Cipher decrypter = Cipher.getInstance(algorithm, EXPECTED_PROVIDER_NAME); for (JsonElement t : group.getAsJsonArray("tests")) { @@ -266,7 +305,13 @@ public class RsaOaepTest { JsonObject testcase = t.getAsJsonObject(); int tcid = testcase.get("tcId").getAsInt(); String messageHex = TestUtil.bytesToHex(getBytes(testcase, "msg")); - OAEPParameterSpec params = getOaepParameters(group, testcase); + OAEPParameterSpec params; + try { + params = getOaepParameters(group, testcase); + } catch (UnsupportedKeyParametersException e) { + // TODO This try catch block should be removed once issue b/229183581 is fixed. + continue; + } byte[] ciphertext = getBytes(testcase, "ct"); String ciphertextHex = TestUtil.bytesToHex(ciphertext); String result = getString(testcase, "result"); @@ -296,13 +341,13 @@ public class RsaOaepTest { String decryptedHex = TestUtil.bytesToHex(decrypted); if (result.equals("invalid")) { Log.e(TAG, - String.format("Invalid ciphertext decrypted. filename:%s tcId:%d expected:%s decrypted:%s\n", - filename, tcid, messageHex, decryptedHex)); + String.format("Invalid ciphertext decrypted. filename:%s tcId:%d expected:%s" + + " decrypted:%s\n", filename, tcid, messageHex, decryptedHex)); errors++; } else if (!decryptedHex.equals(messageHex)) { Log.e(TAG, - String.format("Incorrect decryption. filename:%s tcId:%d expected:%s decrypted:%s\n", - filename, tcid, messageHex, decryptedHex)); + String.format("Incorrect decryption. filename:%s tcId:%d expected:%s" + + " decrypted:%s\n", filename, tcid, messageHex, decryptedHex)); errors++; } } @@ -311,118 +356,117 @@ public class RsaOaepTest { assertEquals(0, errors); if (skippedKeys > 0) { Log.d(TAG, "RSAES-OAEP: file:" + filename + " skipped key:" + skippedKeys); - assertTrue(allowSkippingKeys); + assertTrue(!allowSkippingKeys); } else { assertEquals(numTests, cntTests); } } @Test - @Ignore //TODO Reverify after bugs b/229182999 and b/229183581 are fixed. public void testRsaOaep2048Sha1Mgf1Sha1() throws Exception { testOaep("rsa_oaep_2048_sha1_mgf1sha1_test.json", false); } @Test - @Ignore //TODO Reverify after bugs b/229182999 and b/229183581 are fixed. + public void testRsaOaep2048Sha1Mgf1Sha1_StrongBox() throws Exception { + testOaep("rsa_oaep_2048_sha1_mgf1sha1_test.json", true, true); + } + + @Test public void testRsaOaep2048Sha224Mgf1Sha1() throws Exception { testOaep("rsa_oaep_2048_sha224_mgf1sha1_test.json", false); } @Test - @Ignore //TODO Reverify after bugs b/229182999 and b/229183581 are fixed. public void testRsaOaep2048Sha224Mgf1Sha224() throws Exception { testOaep("rsa_oaep_2048_sha224_mgf1sha224_test.json", false); } @Test - @Ignore //TODO Reverify after bugs b/229182999 and b/229183581 are fixed. public void testRsaOaep2048Sha256Mgf1Sha1() throws Exception { - testOaep("rsa_oaep_2048_sha256_mgf1sha1_test.json", false); + testOaep("rsa_oaep_2048_sha256_mgf1sha1_test.json", false); + } + @Test + public void testRsaOaep2048Sha256Mgf1Sha1_StrongBox() throws Exception { + testOaep("rsa_oaep_2048_sha256_mgf1sha1_test.json", false, true); } @Test - @Ignore //TODO Reverify after bugs b/229182999 and b/229183581 are fixed. public void testRsaOaep2048Sha256Mgf1Sha256() throws Exception { - testOaep("rsa_oaep_2048_sha256_mgf1sha256_test.json", false); + testOaep("rsa_oaep_2048_sha256_mgf1sha256_test.json", false); + } + @Test + public void testRsaOaep2048Sha256Mgf1Sha256_StrongBox() throws Exception { + testOaep("rsa_oaep_2048_sha256_mgf1sha256_test.json", false, true); } @Test - @Ignore //TODO Reverify after bugs b/229182999 and b/229183581 are fixed. public void testRsaOaep2048Sha384Mgf1Sha1() throws Exception { testOaep("rsa_oaep_2048_sha384_mgf1sha1_test.json", false); } @Test - @Ignore //TODO Reverify after bugs b/229182999 and b/229183581 are fixed. public void testRsaOaep2048Sha384Mgf1Sha384() throws Exception { testOaep("rsa_oaep_2048_sha384_mgf1sha384_test.json", false); } @Test - @Ignore //TODO Reverify after bugs b/229182999 and b/229183581 are fixed. public void testRsaOaep2048Sha512Mgf1Sha1() throws Exception { testOaep("rsa_oaep_2048_sha512_mgf1sha1_test.json", false); } @Test - @Ignore //TODO Reverify after bugs b/229182999 and b/229183581 are fixed. public void testRsaOaep2048Sha512Mgf1Sha512() throws Exception { testOaep("rsa_oaep_2048_sha512_mgf1sha512_test.json", false); } @Test - @Ignore //TODO Reverify after bugs b/229182999 and b/229183581 are fixed. public void testRsaOaep3072Sha256Mgf1Sha1() throws Exception { testOaep("rsa_oaep_3072_sha256_mgf1sha1_test.json", false); } @Test - @Ignore //TODO Reverify after bugs b/229182999 and b/229183581 are fixed. public void testRsaOaep3072Sha256Mgf1Sha256() throws Exception { testOaep("rsa_oaep_3072_sha256_mgf1sha256_test.json", false); } @Test - @Ignore //TODO Reverify after bugs b/229182999 and b/229183581 are fixed. public void testRsaOaep3072Sha512Mgf1Sha1() throws Exception { testOaep("rsa_oaep_3072_sha512_mgf1sha1_test.json", false); } @Test - @Ignore //TODO Reverify after bugs b/229182999 and b/229183581 are fixed. public void testRsaOaep3072Sha512Mgf1Sha512() throws Exception { testOaep("rsa_oaep_3072_sha512_mgf1sha512_test.json", false); } @Test - @Ignore //TODO Reverify after bugs b/229182999 and b/229183581 are fixed. public void testRsaOaep4096Sha256Mgf1Sha1() throws Exception { testOaep("rsa_oaep_4096_sha256_mgf1sha1_test.json", false); } @Test - @Ignore //TODO Reverify after bugs b/229182999 and b/229183581 are fixed. public void testRsaOaep4096Sha256Mgf1Sha256() throws Exception { testOaep("rsa_oaep_4096_sha256_mgf1sha256_test.json", false); } @Test - @Ignore //TODO Reverify after bugs b/229182999 and b/229183581 are fixed. public void testRsaOaep4096Sha512Mgf1Sha1() throws Exception { testOaep("rsa_oaep_4096_sha512_mgf1sha1_test.json", false); } @Test - @Ignore //TODO Reverify after bugs b/229182999 and b/229183581 are fixed. public void testRsaOaep4096Sha512Mgf1Sha512() throws Exception { testOaep("rsa_oaep_4096_sha512_mgf1sha512_test.json", false); } @Test - @Ignore //TODO Reverify after bugs b/229182999 and b/229183581 are fixed. public void testRsaOaepMisc() throws Exception { - testOaep("rsa_oaep_misc_test.json", false); + testOaep("rsa_oaep_misc_test.json", false); + } + @Test + public void testRsaOaepMisc_StrongBox() throws Exception { + testOaep("rsa_oaep_misc_test.json", false, true); } } diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/RsaSignatureTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/RsaSignatureTest.java index 57382f6..d41bb98 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/RsaSignatureTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/RsaSignatureTest.java @@ -57,13 +57,14 @@ public class RsaSignatureTest { KeyStoreUtil.cleanUpKeyStore(); } - private static PrivateKey getKeystorePrivateKey(PublicKey pubKey, PrivateKey privKey) - throws Exception { + private static PrivateKey getKeystorePrivateKey(PublicKey pubKey, PrivateKey privKey, + boolean isStrongBox) throws Exception { KeyStore keyStore = KeyStoreUtil.saveKeysToKeystore(KEY_ALIAS_1, pubKey, privKey, new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY) - .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) + .setDigests(KeyProperties.DIGEST_SHA256) .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1) + .setIsStrongBoxBacked(isStrongBox) .build()); return (PrivateKey) keyStore.getKey(KEY_ALIAS_1, null); } @@ -1110,6 +1111,14 @@ public class RsaSignatureTest { @Test public void testBasic() throws Exception { + testBasic(false); + } + @Test + public void testBasic_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testBasic(true); + } + private void testBasic(boolean isStrongBox) throws Exception { String algorithm = "SHA256WithRSA"; String hashAlgorithm = "SHA-256"; String message = "Hello"; @@ -1125,7 +1134,7 @@ public class RsaSignatureTest { Signature signer = Signature.getInstance(algorithm, TestUtil.EXPECTED_CRYPTO_OP_PROVIDER_NAME); Signature verifier = Signature.getInstance(algorithm, TestUtil.EXPECTED_CRYPTO_OP_PROVIDER_NAME); - signer.initSign(getKeystorePrivateKey(pub, priv)); + signer.initSign(getKeystorePrivateKey(pub, priv, isStrongBox)); signer.update(messageBytes); byte[] signature = signer.sign(); verifier.initVerify(pub); @@ -1256,6 +1265,14 @@ public class RsaSignatureTest { */ @Test public void testFaultySigner() throws Exception { + testFaultySigner(false); + } + @Test + public void testFaultySigner_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testFaultySigner(true); + } + private void testFaultySigner(boolean isStrongBox) throws Exception { BigInteger e = new BigInteger("65537"); BigInteger d = new BigInteger( "1491581187972832788084570222215155297353839087630599492610691218" @@ -1296,7 +1313,7 @@ public class RsaSignatureTest { RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(n, e); PublicKey pubKey = kf.generatePublic(pubKeySpec); - signer.initSign(getKeystorePrivateKey(pubKey, validPrivKey)); + signer.initSign(getKeystorePrivateKey(pubKey, validPrivKey, isStrongBox)); signer.update(message); byte[] signature = signer.sign(); PrivateKey invalidPrivKey = null; @@ -1318,6 +1335,7 @@ public class RsaSignatureTest { new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN) .setDigests(KeyProperties.DIGEST_SHA256) .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1) + .setIsStrongBoxBacked(isStrongBox) .build()); } catch (InvalidKeySpecException | java.security.KeyStoreException ex) { // The provider checks the private key and notices a mismatch. |