diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 04:58:56 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 04:58:56 +0000 |
commit | 1659bf81857ac7d54e085d3c15ad08dbf54ebfa1 (patch) | |
tree | 9ac96473c50aa2acf7282589fb25f892024a1c0a | |
parent | 0f533fc2c2e144ced8eca14121e75640c36a40fb (diff) | |
parent | 2e04f8c5649563fe5846226db10a00285b680565 (diff) | |
download | wycheproof-1659bf81857ac7d54e085d3c15ad08dbf54ebfa1.tar.gz |
Snap for 10453563 from 2e04f8c5649563fe5846226db10a00285b680565 to mainline-media-swcodec-release
Change-Id: I580817d509cf174209a810a617c072155444b9bb
20 files changed, 1629 insertions, 1319 deletions
@@ -56,34 +56,27 @@ java_library_static { visibility: [ "//cts/tests/tests/keystore", ], - srcs: ["keystore-cts/java/**/*.java"], - exclude_srcs: [ - "keystore-cts/java/com/google/security/wycheproof/testcases/CipherInputStreamTest.java", - "keystore-cts/java/com/google/security/wycheproof/testcases/CipherOutputStreamTest.java", - "keystore-cts/java/com/google/security/wycheproof/testcases/EcdhTest.java", - "keystore-cts/java/com/google/security/wycheproof/testcases/EcdsaTest.java", - "keystore-cts/java/com/google/security/wycheproof/testcases/JsonCipherTest.java", - "keystore-cts/java/com/google/security/wycheproof/testcases/JsonEcdhTest.java", - "keystore-cts/java/com/google/security/wycheproof/testcases/JsonMacTest.java", - "keystore-cts/java/com/google/security/wycheproof/testcases/JsonSignatureTest.java", - "keystore-cts/java/com/google/security/wycheproof/testcases/MacTest.java", - "keystore-cts/java/com/google/security/wycheproof/testcases/RsaEncryptionTest.java", - "keystore-cts/java/com/google/security/wycheproof/testcases/RsaOaepTest.java", - "keystore-cts/java/com/google/security/wycheproof/testcases/RsaPssTest.java", - "keystore-cts/java/com/google/security/wycheproof/testcases/RsaSignatureTest.java", + srcs: [ + "keystore-cts/java/**/*.java", + "keystore-cts/android/**/*.java", ], java_resource_dirs: ["keystore-cts/testvectors"], - sdk_version: "current", libs: [ "bouncycastle-bcpkix-unbundled", "bouncycastle-unbundled", "junit", "wycheproof-gson", + "cts-core-test-runner-axt", + "cts-keystore-test-util", ], + platform_apis: true, } java_import { name: "wycheproof-gson", - visibility: ["//cts/tests/tests/keystore"], + 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 new file mode 100644 index 0000000..ea5d91b --- /dev/null +++ b/keystore-cts/java/android/keystore/cts/util/KeyStoreUtil.java @@ -0,0 +1,176 @@ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +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; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Date; +import java.util.Enumeration; +import java.util.List; +import javax.crypto.spec.SecretKeySpec; +import javax.security.auth.x500.X500Principal; + +/** Keystore utilities */ +public class KeyStoreUtil { + // Known KeyMaster/KeyMint versions. This is the version number + // which appear in the keymasterVersion field. + public static final int KM_VERSION_KEYMASTER_1 = 10; + public static final int KM_VERSION_KEYMASTER_1_1 = 11; + public static final int KM_VERSION_KEYMASTER_2 = 20; + public static final int KM_VERSION_KEYMASTER_3 = 30; + public static final int KM_VERSION_KEYMASTER_4 = 40; + public static final int KM_VERSION_KEYMASTER_4_1 = 41; + public static final int KM_VERSION_KEYMINT_1 = 100; + + private static final List kmSupportedDigests = List.of("md5","sha-1","sha-224","sha-384", + "sha-256","sha-512"); + + public static KeyStore saveKeysToKeystore(String alias, PublicKey pubKey, PrivateKey privKey, + KeyProtection keyProtection) throws Exception { + KeyPair keyPair = new KeyPair(pubKey, privKey); + X509Certificate certificate = createCertificate(keyPair, + new X500Principal("CN=Test1"), + new X500Principal("CN=Test1")); + Certificate[] certChain = new Certificate[]{certificate}; + KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + keyStore.load(null); + keyStore.setEntry(alias, + new KeyStore.PrivateKeyEntry(privKey, certChain), + keyProtection); + return keyStore; + } + + public static KeyStore saveSecretKeyToKeystore(String alias, SecretKeySpec keySpec, + KeyProtection keyProtection) throws Exception { + KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + keyStore.load(null); + keyStore.setEntry(alias, + new KeyStore.SecretKeyEntry(keySpec), + keyProtection); + return keyStore; + } + + public static void cleanUpKeyStore() throws Exception { + KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); + keyStore.load(null); + for (Enumeration<String> aliases = keyStore.aliases(); aliases.hasMoreElements();) { + String alias = aliases.nextElement(); + keyStore.deleteEntry(alias); + } + } + + 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 isSupportedDigest(String digest, boolean isStrongBox) { + if (isStrongBox) { + return digest.equalsIgnoreCase("sha-256"); + } + return kmSupportedDigests.contains(digest.toLowerCase()); + } + + public static boolean isSupportedMgfDigest(String digest, boolean isStrongBox) { + if (isStrongBox) { + return digest.equalsIgnoreCase("sha-1") + || digest.equalsIgnoreCase("sha-256"); + } + return kmSupportedDigests.contains(digest.toLowerCase()); + } + + public static boolean isSupportedRsaKeySize(int keySize, boolean isStrongBox) { + if (isStrongBox) { + return keySize == 2048; + } + return keySize == 2048 || keySize == 3072 || keySize == 4096; + } + + public static X509Certificate createCertificate( + KeyPair keyPair, X500Principal subject, X500Principal issuer) + throws OperatorCreationException, CertificateException, IOException { + // Make the certificate valid for two days. + long millisPerDay = 24 * 60 * 60 * 1000; + long now = System.currentTimeMillis(); + Date start = new Date(now - millisPerDay); + Date end = new Date(now + millisPerDay); + + // Assign a random serial number. + byte[] serialBytes = new byte[16]; + new SecureRandom().nextBytes(serialBytes); + BigInteger serialNumber = new BigInteger(1, serialBytes); + + // Create the certificate builder + X509v3CertificateBuilder x509cg = + new X509v3CertificateBuilder( + X500Name.getInstance(issuer.getEncoded()), + serialNumber, + start, + end, + X500Name.getInstance(subject.getEncoded()), + SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded())); + + // Choose a signature algorithm matching the key format. + String keyAlgorithm = keyPair.getPrivate().getAlgorithm(); + String signatureAlgorithm; + if (keyAlgorithm.equals("RSA")) { + signatureAlgorithm = "SHA256withRSA"; + } else if (keyAlgorithm.equals("EC")) { + signatureAlgorithm = "SHA256withECDSA"; + } else { + throw new IllegalArgumentException("Unknown key algorithm " + keyAlgorithm); + } + + // Sign the certificate and generate it. + X509CertificateHolder x509holder = + x509cg.build( + new JcaContentSignerBuilder(signatureAlgorithm) + .build(keyPair.getPrivate())); + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + X509Certificate x509c = + (X509Certificate) + certFactory.generateCertificate( + new ByteArrayInputStream(x509holder.getEncoded())); + return x509c; + } +} diff --git a/keystore-cts/java/com/google/security/wycheproof/CertificateUtil.java b/keystore-cts/java/com/google/security/wycheproof/CertificateUtil.java deleted file mode 100644 index d5d343a..0000000 --- a/keystore-cts/java/com/google/security/wycheproof/CertificateUtil.java +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.security.wycheproof; - -import org.bouncycastle.asn1.x500.X500Name; -import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; -import org.bouncycastle.cert.X509CertificateHolder; -import org.bouncycastle.cert.X509v3CertificateBuilder; -import org.bouncycastle.operator.OperatorCreationException; -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.math.BigInteger; -import java.security.KeyPair; -import java.security.SecureRandom; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.Date; -import javax.security.auth.x500.X500Principal; - -/** Certificate utilities */ -public class CertificateUtil { - - public static X509Certificate createCertificate( - KeyPair keyPair, X500Principal subject, X500Principal issuer) - throws OperatorCreationException, CertificateException, IOException { - // Make the certificate valid for two days. - long millisPerDay = 24 * 60 * 60 * 1000; - long now = System.currentTimeMillis(); - Date start = new Date(now - millisPerDay); - Date end = new Date(now + millisPerDay); - - // Assign a random serial number. - byte[] serialBytes = new byte[16]; - new SecureRandom().nextBytes(serialBytes); - BigInteger serialNumber = new BigInteger(1, serialBytes); - - // Create the certificate builder - X509v3CertificateBuilder x509cg = - new X509v3CertificateBuilder( - X500Name.getInstance(issuer.getEncoded()), - serialNumber, - start, - end, - X500Name.getInstance(subject.getEncoded()), - SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded())); - - // Choose a signature algorithm matching the key format. - String keyAlgorithm = keyPair.getPrivate().getAlgorithm(); - String signatureAlgorithm; - if (keyAlgorithm.equals("RSA")) { - signatureAlgorithm = "SHA256withRSA"; - } else if (keyAlgorithm.equals("EC")) { - signatureAlgorithm = "SHA256withECDSA"; - } else { - throw new IllegalArgumentException("Unknown key algorithm " + keyAlgorithm); - } - - // Sign the certificate and generate it. - X509CertificateHolder x509holder = - x509cg.build( - new JcaContentSignerBuilder(signatureAlgorithm) - .build(keyPair.getPrivate())); - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - X509Certificate x509c = - (X509Certificate) - certFactory.generateCertificate( - new ByteArrayInputStream(x509holder.getEncoded())); - return x509c; - } -} diff --git a/keystore-cts/java/com/google/security/wycheproof/EcUtil.java b/keystore-cts/java/com/google/security/wycheproof/EcUtil.java index 56c6548..492dd43 100644 --- a/keystore-cts/java/com/google/security/wycheproof/EcUtil.java +++ b/keystore-cts/java/com/google/security/wycheproof/EcUtil.java @@ -46,22 +46,6 @@ public class EcUtil { return parameters.getParameterSpec(ECParameterSpec.class); } - public static void printParameters(ECParameterSpec spec) { - System.out.println("cofactor:" + spec.getCofactor()); - EllipticCurve curve = spec.getCurve(); - System.out.println("A:" + curve.getA()); - System.out.println("B:" + curve.getB()); - ECField field = curve.getField(); - System.out.println("field size:" + field.getFieldSize()); - if (field instanceof ECFieldFp) { - ECFieldFp fp = (ECFieldFp) field; - System.out.println("P:" + fp.getP()); - } - ECPoint generator = spec.getGenerator(); - System.out.println("Gx:" + generator.getAffineX()); - System.out.println("Gy:" + generator.getAffineY()); - System.out.println("order:" + spec.getOrder()); - } /** Returns the bit size of a given curve. TODO(bleichen): add all curves that are tested. */ public static int getCurveSize(String name) throws NoSuchAlgorithmException { 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 d02b4f1..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; @@ -37,11 +42,10 @@ import javax.crypto.spec.SecretKeySpec; import org.junit.Test; import org.junit.Ignore; import org.junit.Before; +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. @@ -54,17 +58,35 @@ import java.security.UnrecoverableKeyException; public class AesGcmTest { private static final String EXPECTED_PROVIDER_NAME = TestUtil.EXPECTED_CRYPTO_OP_PROVIDER_NAME; private KeyStore keyStore; + private static final String KEY_ALIAS_1 = "Key1"; + private static final String KEY_ALIAS_2 = "Key2"; + private static final String KEY_ALIAS_3 = "Key3"; + private static final String KEY_ALIAS_4 = "Key4"; + private static final String KEY_ALIAS_5 = "Key5"; + private static final String KEY_ALIAS_6 = "Key6"; + private static final String KEY_ALIAS_7 = "Key7"; + private static final String KEY_ALIAS_8 = "Key8"; + private static final String KEY_ALIAS_9 = "Key9"; @Before 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); + } } } - private SecretKey setKeystoreEntry(String alias, SecretKeySpec key) + @After + public void tearDown() throws Exception { + KeyStoreUtil.cleanUpKeyStore(); + } + + private SecretKey setKeystoreEntry(String alias, SecretKeySpec key, boolean isStrongBox) throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { keyStore.setEntry( alias, @@ -73,11 +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; @@ -90,6 +127,7 @@ public class AesGcmTest { final int nonceLengthInBits; final int tagLengthInBits; final String alias; + final String alias_sb; public GcmTestVector( String message, @@ -109,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"; } }; @@ -119,35 +158,35 @@ public class AesGcmTest { "028318abc1824029138141a2", "", "26073cc1d851beff176384dc9896d5ff", - "0a3ea7a5487cb5f7d70fb6c58d038554", "Key1"), + "0a3ea7a5487cb5f7d70fb6c58d038554", KEY_ALIAS_1), new GcmTestVector( "001d0c231287c1182784554ca3a21908", "5b9604fe14eadba931b0ccf34843dab9", "921d2507fa8007b7bd067d34", "00112233445566778899aabbccddeeff", "49d8b9783e911913d87094d1f63cc765", - "1e348ba07cca2cf04c618cb4", "Key2"), + "1e348ba07cca2cf04c618cb4", KEY_ALIAS_2), new GcmTestVector( "2035af313d1346ab00154fea78322105", "aa023d0478dcb2b2312498293d9a9129", "0432bc49ac34412081288127", "aac39231129872a2", "eea945f3d0f98cc0fbab472a0cf24e87", - "4bb9b4812519dadf9e1232016d068133", "Key3"), + "4bb9b4812519dadf9e1232016d068133", KEY_ALIAS_3), new GcmTestVector( "2035af313d1346ab00154fea78322105", "aa023d0478dcb2b2312498293d9a9129", "0432bc49ac344120", "aac39231129872a2", "64c36bb3b732034e3a7d04efc5197785", - "b7d0dd70b00d65b97cfd080ff4b819d1", "Key4"), + "b7d0dd70b00d65b97cfd080ff4b819d1", KEY_ALIAS_4), new GcmTestVector( "02efd2e5782312827ed5d230189a2a342b277ce048462193", "2034a82547276c83dd3212a813572bce", "3254202d854734812398127a3d134421", "1a0293d8f90219058902139013908190bc490890d3ff12a3", "64069c2d58690561f27ee199e6b479b6369eec688672bde9", - "9b7abadd6e69c1d9ec925786534f5075", "Key5"), + "9b7abadd6e69c1d9ec925786534f5075", KEY_ALIAS_5), // GCM uses GHASH to compute the initial counter J0 if the nonce is not 12 bytes long. // The counter is incremented modulo 2^32 in counter mode. The following test vectors verify // the behavior of an implementation for initial counter values J0 close to a 2^32 limit. @@ -158,7 +197,7 @@ public class AesGcmTest { "7b95b8c356810a84711d68150a1b7750", "", "84d4c9c08b4f482861e3a9c6c35bc4d91df927374513bfd49f436bd73f325285daef4ff7e13d46a6", - "213a3cb93855d18e69337eee66aeec07", "Key6"), + "213a3cb93855d18e69337eee66aeec07", KEY_ALIAS_6), // J0:ffffffffffffffffffffffffffffffff new GcmTestVector( "00000000000000000000000000000000000000000000000000000000000000000000000000000000", @@ -166,7 +205,7 @@ public class AesGcmTest { "1a552e67cdc4dc1a33b824874ebf0bed", "", "948ca37a8e6649e88aeffb1c598f3607007702417ea0e0bc3c60ad5a949886de968cf53ea6462aed", - "99b381bfa2af9751c39d1b6e86d1be6a", "Key6"), + "99b381bfa2af9751c39d1b6e86d1be6a", KEY_ALIAS_7), // J0:000102030405060708090a0bffffffff new GcmTestVector( "00000000000000000000000000000000000000000000000000000000000000000000000000000000", @@ -174,7 +213,7 @@ public class AesGcmTest { "99821c2dd5daecded07300f577f7aff1", "", "127af9b39ecdfc57bb11a2847c7c2d3d8f938f40f877e0c4af37d0fe9af033052bd537c4ae978f60", - "07eb2fe4a958f8434d40684899507c7c", "Key7"), + "07eb2fe4a958f8434d40684899507c7c", KEY_ALIAS_8), // J0:000102030405060708090a0bfffffffe new GcmTestVector( "00000000000000000000000000000000000000000000000000000000000000000000000000000000", @@ -182,7 +221,7 @@ public class AesGcmTest { "5e4a3900142358d1c774d8d124d8d27d", "", "0cf6ae47156b14dce03c8a07a2e172b1127af9b39ecdfc57bb11a2847c7c2d3d8f938f40f877e0c4", - "f145c2dcaf339eede427be934357eac0", "Key8"), + "f145c2dcaf339eede427be934357eac0", KEY_ALIAS_9), }; /** @@ -195,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, (SecretKey) keyStore.getKey(test.alias, null), test.parameters); + getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.ENCRYPT_MODE, + test, isStrongBox); } catch (InvalidKeyException | InvalidAlgorithmParameterException ex) { // Not supported continue; @@ -216,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, (SecretKey) keyStore.getKey(test.alias, null), 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)); @@ -228,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, (SecretKey) keyStore.getKey(test.alias, null), 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); @@ -255,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, (SecretKey) keyStore.getKey(test.alias, null), 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); @@ -279,7 +342,8 @@ public class AesGcmTest { // Simple test that a modified ciphertext fails. ptBuffer.clear(); - cipher.init(Cipher.DECRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters); + cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.DECRYPT_MODE, test, + isStrongBox); cipher.updateAAD(empty); cipher.updateAAD(test.aad); cipher.updateAAD(new byte[1]); @@ -313,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, (SecretKey) keyStore.getKey(test.alias, null), 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); @@ -346,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, (SecretKey) keyStore.getKey(test.alias, null), 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 { @@ -372,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, (SecretKey) keyStore.getKey(test.alias, null), 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, (SecretKey) keyStore.getKey(test.alias, null), 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); } @@ -388,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, (SecretKey) keyStore.getKey(test.alias, null), 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); @@ -401,7 +497,8 @@ public class AesGcmTest { // Decryption ctBuffer.flip(); - cipher.init(Cipher.DECRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), 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); @@ -413,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, (SecretKey) keyStore.getKey(test.alias, null), 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); @@ -430,7 +535,8 @@ public class AesGcmTest { // Decryption ByteBuffer decrypted = ByteBuffer.wrap(backingArray); ctBuffer.flip(); - cipher.init(Cipher.DECRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), 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)); @@ -440,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. @@ -450,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."); } @@ -522,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++) { @@ -532,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."); } @@ -636,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, (SecretKey) keyStore.getKey(test.alias, null), 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); @@ -650,7 +780,8 @@ public class AesGcmTest { // Decryption ctBuffer.flip(); ctBuffer = ctBuffer.asReadOnlyBuffer(); - cipher.init(Cipher.DECRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), 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); @@ -666,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(); @@ -678,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, (SecretKey) keyStore.getKey(test.alias, null), test.parameters); cipher.updateAAD(test.aad); cipher.doFinal(ptBuffer, ctBuffer); assertEquals(test.ctHex, TestUtil.byteBufferToHex(ctBuffer)); @@ -688,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, (SecretKey) keyStore.getKey(test.alias, null), 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)); @@ -697,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, (SecretKey) keyStore.getKey(test.alias, null), test.parameters); cipher.updateAAD(test.aad); try { cipher.doFinal(ptBuffer, ctBuffer); @@ -714,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, (SecretKey) keyStore.getKey(test.alias, null), test.parameters); + cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.DECRYPT_MODE, + test, isStrongBox); cipher.updateAAD(test.aad); try { cipher.doFinal(ctBuffer, decrypted); @@ -731,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, (SecretKey) keyStore.getKey(test.alias, null), test.parameters); int outputSize = cipher.getOutputSize(test.pt.length); ByteBuffer ctBuffer = ByteBuffer.allocate(outputSize); cipher.updateAAD(empty); @@ -750,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, (SecretKey) keyStore.getKey(test.alias, null), test.parameters); int outputSize = cipher.getOutputSize(test.ct.length); ByteBuffer ptBuffer = ByteBuffer.allocate(outputSize); cipher.updateAAD(empty); @@ -767,7 +932,8 @@ public class AesGcmTest { // Simple test that a modified ciphertext fails. ctBuffer.flip(); ptBuffer.clear(); - cipher.init(Cipher.DECRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters); + cipher = getInitializedCipherInstance("AES/GCM/NoPadding", Cipher.DECRYPT_MODE, + test, isStrongBox); cipher.updateAAD(empty); cipher.updateAAD(test.aad); cipher.updateAAD(new byte[1]); @@ -802,17 +968,12 @@ 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."); } Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME); - try { - cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(counter)); - } catch (InvalidAlgorithmParameterException ex) { - // OpenJDK8 does not support IvParameterSpec for GCM. - throw ex; - } + cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(counter)); byte[] output = cipher.doFinal(input); assertEquals(input.length + 16, output.length); } @@ -836,17 +997,12 @@ 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."); } Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME); - try { - AlgorithmParameterGenerator.getInstance("GCM"); - } catch (NoSuchAlgorithmException ex) { - // Conscrypt does not support AlgorithmParameterGenerator for GCM. - throw ex; - } + AlgorithmParameterGenerator.getInstance("GCM"); AlgorithmParameters param = AlgorithmParameterGenerator.getInstance("GCM").generateParameters(); cipher.init(Cipher.ENCRYPT_MODE, secretKey, param); byte[] output = cipher.doFinal(input); @@ -897,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."); } @@ -929,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"); @@ -936,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."); } @@ -957,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 3698e4e..85d84d7 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/CipherInputStreamTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/CipherInputStreamTest.java @@ -25,12 +25,17 @@ import java.security.spec.AlgorithmParameterSpec; import java.util.ArrayList; import java.util.Arrays; import javax.crypto.Cipher; +import javax.crypto.SecretKey; import javax.crypto.CipherInputStream; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; +import org.junit.After; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import org.junit.Ignore; +import android.security.keystore.KeyProtection; +import android.security.keystore.KeyProperties; +import java.security.KeyStore; +import android.keystore.cts.util.KeyStoreUtil; /** * CipherInputStream tests @@ -51,18 +56,33 @@ import org.junit.runners.JUnit4; * All other tests run under the assumption that returning an empty plaintext is acceptable * behaviour, so that the tests are able to catch additional problems. */ -@RunWith(JUnit4.class) public class CipherInputStreamTest { + private static final String EXPECTED_PROVIDER_NAME = TestUtil.EXPECTED_CRYPTO_OP_PROVIDER_NAME; static final SecureRandom rand = new SecureRandom(); + @After + public void tearDown() throws Exception { + KeyStoreUtil.cleanUpKeyStore(); + } + static byte[] randomBytes(int size) { byte[] bytes = new byte[size]; rand.nextBytes(bytes); return bytes; } - static SecretKeySpec randomKey(String algorithm, int keySizeInBytes) { - return new SecretKeySpec(randomBytes(keySizeInBytes), "AES"); + 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); } static AlgorithmParameterSpec randomParameters( @@ -76,7 +96,7 @@ public class CipherInputStreamTest { /** Test vectors */ public static class TestVector { public String algorithm; - public SecretKeySpec key; + public SecretKey key; public AlgorithmParameterSpec params; public byte[] pt; public byte[] aad; @@ -84,14 +104,14 @@ public class CipherInputStreamTest { @SuppressWarnings("InsecureCryptoUsage") public TestVector( - String algorithm, int keySize, int ivSize, int tagSize, int ptSize, int aadSize) - throws Exception { + String algorithm, String alias, int keySize, + int ivSize, int tagSize, int ptSize, int aadSize, boolean isStrongBox) throws Exception { this.algorithm = algorithm; - this.key = randomKey(algorithm, keySize); + this.key = randomKey(algorithm, alias, keySize, isStrongBox); this.params = randomParameters(algorithm, ivSize, tagSize); this.pt = randomBytes(ptSize); this.aad = randomBytes(aadSize); - Cipher cipher = Cipher.getInstance(algorithm); + Cipher cipher = Cipher.getInstance(algorithm, EXPECTED_PROVIDER_NAME); cipher.init(Cipher.ENCRYPT_MODE, this.key, this.params); cipher.updateAAD(aad); this.ct = cipher.doFinal(pt); @@ -104,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) { @@ -112,7 +133,10 @@ public class CipherInputStreamTest { for (int tagSize : tagSizes) { for (int ptSize : ptSizes) { for (int aadSize : aadSizes) { - result.add(new TestVector(algorithm, keySize, ivSize, tagSize, ptSize, aadSize)); + String keyAlias = "Key-" + keySize + "-" + ivSize + "-" + tagSize + + "-" + ptSize + "-" + aadSize; + result.add(new TestVector(algorithm, keyAlias, keySize, + ivSize, tagSize, ptSize, aadSize, isStrongBox)); } } } @@ -124,7 +148,7 @@ public class CipherInputStreamTest { @SuppressWarnings("InsecureCryptoUsage") public void testEncrypt(Iterable<TestVector> tests) throws Exception { for (TestVector t : tests) { - Cipher cipher = Cipher.getInstance(t.algorithm); + Cipher cipher = Cipher.getInstance(t.algorithm, EXPECTED_PROVIDER_NAME); cipher.init(Cipher.ENCRYPT_MODE, t.key, t.params); cipher.updateAAD(t.aad); InputStream is = new ByteArrayInputStream(t.pt); @@ -148,7 +172,7 @@ public class CipherInputStreamTest { @SuppressWarnings("InsecureCryptoUsage") public void testDecrypt(Iterable<TestVector> tests) throws Exception { for (TestVector t : tests) { - Cipher cipher = Cipher.getInstance(t.algorithm); + Cipher cipher = Cipher.getInstance(t.algorithm, EXPECTED_PROVIDER_NAME); cipher.init(Cipher.DECRYPT_MODE, t.key, t.params); cipher.updateAAD(t.aad); InputStream is = new ByteArrayInputStream(t.ct); @@ -184,7 +208,7 @@ public class CipherInputStreamTest { public void testCorruptDecrypt(Iterable<TestVector> tests, boolean acceptEmptyPlaintext) throws Exception { for (TestVector t : tests) { - Cipher cipher = Cipher.getInstance(t.algorithm); + Cipher cipher = Cipher.getInstance(t.algorithm, EXPECTED_PROVIDER_NAME); cipher.init(Cipher.DECRYPT_MODE, t.key, t.params); cipher.updateAAD(t.aad); byte[] ct = Arrays.copyOf(t.ct, t.ct.length); @@ -219,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); } @@ -250,19 +292,29 @@ 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); } /** Tests CipherOutputStream with AES-EAX if this algorithm is supported by the provider. */ @Test + @Ignore // Ignored due to AES/EAX algorithm is not supported in AndroidKeyStore public void testAesEax() throws Exception { final String algorithm = "AES/EAX/NoPadding"; final int[] keySizes = {16, 32}; @@ -270,14 +322,9 @@ public class CipherInputStreamTest { final int[] tagSizes = {12, 16}; final int[] ptSizes = {0, 8, 16, 65, 8100}; final int[] aadSizes = {0, 8, 24}; - try { - Cipher.getInstance(algorithm); - } catch (NoSuchAlgorithmException ex) { - System.out.println("Skipping testAesEax"); - return; - } 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 d016941..39a6044 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/CipherOutputStreamTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/CipherOutputStreamTest.java @@ -24,12 +24,17 @@ import java.security.spec.AlgorithmParameterSpec; import java.util.ArrayList; import java.util.Arrays; import javax.crypto.Cipher; +import javax.crypto.SecretKey; import javax.crypto.CipherOutputStream; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; +import org.junit.After; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import org.junit.Ignore; +import android.security.keystore.KeyProtection; +import android.security.keystore.KeyProperties; +import java.security.KeyStore; +import android.keystore.cts.util.KeyStoreUtil; /** * CipherOutputStream tests @@ -51,18 +56,33 @@ import org.junit.runners.JUnit4; * behaviour, so that the tests are able to catch additional problems. */ -@RunWith(JUnit4.class) public class CipherOutputStreamTest { + private static final String EXPECTED_PROVIDER_NAME = TestUtil.EXPECTED_CRYPTO_OP_PROVIDER_NAME; static final SecureRandom rand = new SecureRandom(); + @After + public void tearDown() throws Exception { + KeyStoreUtil.cleanUpKeyStore(); + } + static byte[] randomBytes(int size) { byte[] bytes = new byte[size]; rand.nextBytes(bytes); return bytes; } - static SecretKeySpec randomKey(String algorithm, int keySizeInBytes) { - return new SecretKeySpec(randomBytes(keySizeInBytes), "AES"); + 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); } static AlgorithmParameterSpec randomParameters( @@ -77,21 +97,21 @@ public class CipherOutputStreamTest { @SuppressWarnings("InsecureCryptoUsage") public static class TestVector { public String algorithm; - public SecretKeySpec key; + public SecretKey key; public AlgorithmParameterSpec params; public byte[] pt; public byte[] aad; public byte[] ct; public TestVector( - String algorithm, int keySize, int ivSize, int tagSize, int ptSize, int aadSize) - throws Exception { + String algorithm, String alias, int keySize, + int ivSize, int tagSize, int ptSize, int aadSize, boolean isStrongBox) throws Exception { this.algorithm = algorithm; - this.key = randomKey(algorithm, keySize); + this.key = randomKey(algorithm, alias, keySize, isStrongBox); this.params = randomParameters(algorithm, ivSize, tagSize); this.pt = randomBytes(ptSize); this.aad = randomBytes(aadSize); - Cipher cipher = Cipher.getInstance(algorithm); + Cipher cipher = Cipher.getInstance(algorithm, EXPECTED_PROVIDER_NAME); cipher.init(Cipher.ENCRYPT_MODE, this.key, this.params); cipher.updateAAD(aad); this.ct = cipher.doFinal(pt); @@ -104,15 +124,19 @@ 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>(); for (int keySize : keySizes) { for (int ivSize : ivSizes) { for (int tagSize : tagSizes) { for (int ptSize : ptSizes) { for (int aadSize : aadSizes) { - result.add(new TestVector(algorithm, keySize, ivSize, tagSize, ptSize, aadSize)); + String keyAlias = "Key" + counter++; + result.add(new TestVector(algorithm, keyAlias, keySize, + ivSize, tagSize, ptSize, aadSize, isStrongBox)); } } } @@ -124,7 +148,7 @@ public class CipherOutputStreamTest { @SuppressWarnings("InsecureCryptoUsage") public void testEncrypt(Iterable<TestVector> tests) throws Exception { for (TestVector t : tests) { - Cipher cipher = Cipher.getInstance(t.algorithm); + Cipher cipher = Cipher.getInstance(t.algorithm, EXPECTED_PROVIDER_NAME); cipher.init(Cipher.ENCRYPT_MODE, t.key, t.params); cipher.updateAAD(t.aad); ByteArrayOutputStream os = new ByteArrayOutputStream(); @@ -138,7 +162,7 @@ public class CipherOutputStreamTest { @SuppressWarnings("InsecureCryptoUsage") public void testDecrypt(Iterable<TestVector> tests) throws Exception { for (TestVector t : tests) { - Cipher cipher = Cipher.getInstance(t.algorithm); + Cipher cipher = Cipher.getInstance(t.algorithm, EXPECTED_PROVIDER_NAME); cipher.init(Cipher.DECRYPT_MODE, t.key, t.params); cipher.updateAAD(t.aad); ByteArrayOutputStream os = new ByteArrayOutputStream(); @@ -160,7 +184,7 @@ public class CipherOutputStreamTest { public void testCorruptDecrypt(Iterable<TestVector> tests, boolean acceptEmptyPlaintext) throws Exception { for (TestVector t : tests) { - Cipher cipher = Cipher.getInstance(t.algorithm); + Cipher cipher = Cipher.getInstance(t.algorithm, EXPECTED_PROVIDER_NAME); cipher.init(Cipher.DECRYPT_MODE, t.key, t.params); cipher.updateAAD(t.aad); byte[] ct = Arrays.copyOf(t.ct, t.ct.length); @@ -192,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; @@ -212,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; @@ -228,6 +270,7 @@ public class CipherOutputStreamTest { /** Tests CipherOutputStream with AES-EAX if AES-EAS is supported by the provider. */ @SuppressWarnings("InsecureCryptoUsage") @Test + @Ignore // Ignored due to AES/EAX algorithm is not supported in AndroidKeyStore public void testAesEax() throws Exception { final String algorithm = "AES/EAX/NoPadding"; final int[] keySizes = {16, 32}; @@ -235,14 +278,9 @@ public class CipherOutputStreamTest { final int[] tagSizes = {12, 16}; final int[] ptSizes = {8, 16, 65, 8100}; final int[] aadSizes = {0, 8, 24}; - try { - Cipher.getInstance(algorithm); - } catch (NoSuchAlgorithmException ex) { - System.out.println("Skipping testAesEax"); - return; - } 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 517d23b..f57ad29 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/EcdhTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/EcdhTest.java @@ -16,18 +16,14 @@ package com.google.security.wycheproof; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; - -import com.google.security.wycheproof.WycheproofRunner.NoPresubmitTest; -import com.google.security.wycheproof.WycheproofRunner.ProviderType; -import com.google.security.wycheproof.WycheproofRunner.SlowTest; -import java.lang.management.ManagementFactory; -import java.lang.management.ThreadMXBean; +import static org.junit.Assume.assumeTrue; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; +import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; @@ -44,9 +40,16 @@ import java.security.spec.EllipticCurve; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import javax.crypto.KeyAgreement; +import org.junit.After; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import org.junit.Ignore; +import android.content.Context; +import android.security.keystore.KeyProtection; +import android.security.keystore.KeyProperties; +import android.security.keystore.KeyGenParameterSpec; +import android.keystore.cts.util.KeyStoreUtil; + +import androidx.test.InstrumentationRegistry; /** * Testing ECDH. @@ -89,8 +92,40 @@ import org.junit.runners.JUnit4; // certificate. // - CVE-2014-3572: OpenSSL downgrades ECDHE to ECDH // - CVE-2011-3210: OpenSSL was not thread safe -@RunWith(JUnit4.class) public class EcdhTest { + private static final String EXPECTED_PROVIDER_NAME = TestUtil.EXPECTED_PROVIDER_NAME; + private static final String KEY_ALIAS_1 = "TestKey"; + private static final String KEY_ALIAS_2 = "wycheproofkey1"; + private static final String KEY_ALIAS_3 = "wycheproofkey2"; + + @After + public void tearDown() throws Exception { + KeyStoreUtil.cleanUpKeyStore(); + } + + 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, boolean isStrongBox) + throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC", EXPECTED_PROVIDER_NAME); + KeyGenParameterSpec ecKeySpec = + new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_AGREE_KEY) + .setAlgorithmParameterSpec(ecSpec) + .setIsStrongBoxBacked(isStrongBox) + .build(); + + keyGen.initialize(ecKeySpec); + return keyGen.generateKeyPair(); + } static final String[] ECDH_VARIANTS = { // Raw ECDH. The shared secret is the x-coordinate of the ECDH computation. @@ -161,7 +196,6 @@ public class EcdhTest { ECPublicKeySpec pub = new ECPublicKeySpec(pubPoint, params); return pub; } catch (Exception ex) { - System.out.println(comment + " throws " + ex.toString()); return null; } } @@ -509,14 +543,21 @@ public static final EcPublicKeyTestVector EC_VALID_PUBLIC_KEY = /** Checks that key agreement using ECDH works. */ @Test public void testBasic() throws Exception { - KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); - ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1"); - keyGen.initialize(ecSpec); - KeyPair keyPairA = keyGen.generateKeyPair(); - KeyPair keyPairB = keyGen.generateKeyPair(); + 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"); - KeyAgreement kaB = KeyAgreement.getInstance("ECDH"); + KeyAgreement kaA = KeyAgreement.getInstance("ECDH", EXPECTED_PROVIDER_NAME); + KeyAgreement kaB = KeyAgreement.getInstance("ECDH", EXPECTED_PROVIDER_NAME); kaA.init(keyPairA.getPrivate()); kaB.init(keyPairB.getPrivate()); kaA.doPhase(keyPairB.getPublic(), true); @@ -526,10 +567,6 @@ public static final EcPublicKeyTestVector EC_VALID_PUBLIC_KEY = assertEquals(TestUtil.bytesToHex(kAB), TestUtil.bytesToHex(kBA)); } - @NoPresubmitTest( - providers = {ProviderType.BOUNCY_CASTLE}, - bugs = {"BouncyCastle uses long encoding. Is this a bug?"} - ) @Test public void testEncode() throws Exception { KeyFactory kf = KeyFactory.getInstance("EC"); @@ -559,26 +596,23 @@ public static final EcPublicKeyTestVector EC_VALID_PUBLIC_KEY = */ @SuppressWarnings("InsecureCryptoUsage") public void testModifiedPublic(String algorithm) throws Exception { - KeyAgreement ka; - try { - ka = KeyAgreement.getInstance(algorithm); - } catch (NoSuchAlgorithmException ex) { - System.out.println("testWrongOrder: " + algorithm + " not supported"); - return; - } - KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); - keyGen.initialize(EcUtil.getNistP256Params()); - ECPrivateKey priv = (ECPrivateKey) keyGen.generateKeyPair().getPrivate(); + 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"), + isStrongBox); KeyFactory kf = KeyFactory.getInstance("EC"); ECPublicKey validKey = (ECPublicKey) kf.generatePublic(EC_VALID_PUBLIC_KEY.getSpec()); - ka.init(priv); + ka.init(pair.getPrivate()); ka.doPhase(validKey, true); String expected = TestUtil.bytesToHex(ka.generateSecret()); for (EcPublicKeyTestVector test : EC_MODIFIED_PUBLIC_KEYS) { try { X509EncodedKeySpec spec = test.getX509EncodedKeySpec(); ECPublicKey modifiedKey = (ECPublicKey) kf.generatePublic(spec); - ka.init(priv); + ka.init(pair.getPrivate()); ka.doPhase(modifiedKey, true); String shared = TestUtil.bytesToHex(ka.generateSecret()); // The implementation did not notice that the public key was modified. @@ -593,7 +627,6 @@ public static final EcPublicKeyTestVector EC_VALID_PUBLIC_KEY = assertEquals("algorithm:" + algorithm + " test:" + test.comment, expected, shared); } catch (GeneralSecurityException ex) { // OK, since the public keys have been modified. - System.out.println("testModifiedPublic:" + test.comment + " throws " + ex.toString()); } } } @@ -604,19 +637,16 @@ public static final EcPublicKeyTestVector EC_VALID_PUBLIC_KEY = */ @SuppressWarnings("InsecureCryptoUsage") public void testModifiedPublicSpec(String algorithm) throws Exception { - KeyAgreement ka; - try { - ka = KeyAgreement.getInstance(algorithm); - } catch (NoSuchAlgorithmException ex) { - System.out.println("testWrongOrder: " + algorithm + " not supported"); - return; - } - KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); - keyGen.initialize(EcUtil.getNistP256Params()); - ECPrivateKey priv = (ECPrivateKey) keyGen.generateKeyPair().getPrivate(); + 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"), + isStrongBox); KeyFactory kf = KeyFactory.getInstance("EC"); ECPublicKey validKey = (ECPublicKey) kf.generatePublic(EC_VALID_PUBLIC_KEY.getSpec()); - ka.init(priv); + ka.init(pair.getPrivate()); ka.doPhase(validKey, true); String expected = TestUtil.bytesToHex(ka.generateSecret()); for (EcPublicKeyTestVector test : EC_MODIFIED_PUBLIC_KEYS) { @@ -628,7 +658,7 @@ public static final EcPublicKeyTestVector EC_VALID_PUBLIC_KEY = } try { ECPublicKey modifiedKey = (ECPublicKey) kf.generatePublic(spec); - ka.init(priv); + ka.init(pair.getPrivate()); ka.doPhase(modifiedKey, true); String shared = TestUtil.bytesToHex(ka.generateSecret()); // The implementation did not notice that the public key was modified. @@ -643,77 +673,40 @@ public static final EcPublicKeyTestVector EC_VALID_PUBLIC_KEY = assertEquals("algorithm:" + algorithm + " test:" + test.comment, expected, shared); } catch (GeneralSecurityException ex) { // OK, since the public keys have been modified. - System.out.println("testModifiedPublic:" + test.comment + " throws " + ex.toString()); } } } @Test - public void testModifiedPublic() throws Exception { + 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 + public void testEcdhcModifiedPublic() throws Exception { testModifiedPublic("ECDHC"); } @Test - public void testModifiedPublicSpec() throws Exception { + public void testEcdhModifiedPublicSpec() throws Exception { testModifiedPublicSpec("ECDH"); - testModifiedPublicSpec("ECDHC"); + } + @Test + public void testEcdhModifiedPublicSpec_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testModifiedPublicSpec("ECDH", true); } - @SuppressWarnings("InsecureCryptoUsage") - public void testDistinctCurves(String algorithm, ECPrivateKey priv, ECPublicKey pub) - throws Exception { - KeyAgreement kaA; - try { - kaA = KeyAgreement.getInstance(algorithm); - } catch (NoSuchAlgorithmException ex) { - System.out.println("Algorithm not supported: " + algorithm); - return; - } - byte[] shared; - try { - kaA.init(priv); - kaA.doPhase(pub, true); - shared = kaA.generateSecret(); - } catch (InvalidKeyException ex) { - // This is expected. - return; - } - // Printing some information to determine what might have gone wrong: - // E.g., if the generated secret is the same as the x-coordinate of the public key - // then it is likely that the ECDH computation was using a fake group with small order. - // Such a situation is probably exploitable. - // This probably is exploitable. If the curve of the private key was used for the ECDH - // then the generated secret and the x-coordinate of the public key are likely - // distinct. - EllipticCurve pubCurve = pub.getParams().getCurve(); - EllipticCurve privCurve = priv.getParams().getCurve(); - ECPoint pubW = pub.getW(); - System.out.println("testDistinctCurves: algorithm=" + algorithm); - System.out.println( - "Private key: a=" - + privCurve.getA() - + " b=" - + privCurve.getB() - + " p" - + EcUtil.getModulus(privCurve)); - System.out.println(" s =" + priv.getS()); - System.out.println( - "Public key: a=" - + pubCurve.getA() - + " b=" - + pubCurve.getB() - + " p" - + EcUtil.getModulus(pubCurve)); - System.out.println(" w = (" + pubW.getAffineX() + ", " + pubW.getAffineY() + ")"); - System.out.println( - " = (" - + pubW.getAffineX().toString(16) - + ", " - + pubW.getAffineY().toString(16) - + ")"); - System.out.println("generated shared secret:" + TestUtil.bytesToHex(shared)); - fail("Generated secret with distinct Curves using " + algorithm); + @Test + @Ignore // ECDHC algorithm is not supported in AndroidKeyStore + public void testEcdhcModifiedPublicSpec() throws Exception { + testModifiedPublicSpec("ECDHC"); } /** @@ -724,26 +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 { - KeyAgreement ka; - try { - ka = KeyAgreement.getInstance(algorithm); - } catch (NoSuchAlgorithmException ex) { - System.out.println("testWrongOrder: " + algorithm + " not supported"); - return; - } - KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); - ECPrivateKey priv; - ECPublicKey pub; - try { - keyGen.initialize(spec); - priv = (ECPrivateKey) keyGen.generateKeyPair().getPrivate(); - pub = (ECPublicKey) keyGen.generateKeyPair().getPublic(); - } catch (GeneralSecurityException ex) { - // This is OK, since not all provider support Brainpool curves - System.out.println("testWrongOrder: could not generate keys for curve"); - return; - } + 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"), isStrongBox) + .getPrivate(); + ECPublicKey pub = (ECPublicKey) generateECKeyPair(KEY_ALIAS_3, + new ECGenParameterSpec("secp256r1"), isStrongBox) + .getPublic(); // Get the shared secret for the unmodified keys. ka.init(priv); ka.doPhase(pub, true); @@ -754,15 +741,7 @@ public static final EcPublicKeyTestVector EC_VALID_PUBLIC_KEY = spec.getCurve(), spec.getGenerator(), spec.getOrder().shiftRight(16), 1); ECPublicKeySpec modifiedPubSpec = new ECPublicKeySpec(pub.getW(), modifiedParams); KeyFactory kf = KeyFactory.getInstance("EC"); - ECPublicKey modifiedPub; - try { - modifiedPub = (ECPublicKey) kf.generatePublic(modifiedPubSpec); - } catch (GeneralSecurityException ex) { - // The provider does not support non-standard curves or did a validity check. - // Both would be correct. - System.out.println("testWrongOrder: can't modify order."); - return; - } + ECPublicKey modifiedPub = (ECPublicKey) kf.generatePublic(modifiedPubSpec); byte[] shared2; try { ka.init(priv); @@ -770,7 +749,6 @@ public static final EcPublicKeyTestVector EC_VALID_PUBLIC_KEY = shared2 = ka.generateSecret(); } catch (GeneralSecurityException ex) { // This is the expected behavior - System.out.println("testWrongOrder:" + ex.toString()); return; } // TODO(bleichen): Getting here is already a bug and we might flag this later. @@ -782,25 +760,29 @@ public static final EcPublicKeyTestVector EC_VALID_PUBLIC_KEY = // of modular reduction, can determine the private key, either by a binary search or by trying // to guess the private key modulo some small "order". // BouncyCastle v.1.53 fails this test, and leaks the private key. - System.out.println( - "Generated shared secret with a modified order:" - + algorithm - + "\n" - + "expected:" - + TestUtil.bytesToHex(shared) - + " computed:" - + TestUtil.bytesToHex(shared2)); + assertEquals( "Algorithm:" + algorithm, TestUtil.bytesToHex(shared), TestUtil.bytesToHex(shared2)); } @Test - public void testWrongOrderEcdh() throws Exception { + 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. + public void testWrongOrderEcdhBrainpool() throws Exception { testWrongOrder("ECDH", EcUtil.getBrainpoolP256r1Params()); } @Test + @Ignore // ECDHC algorithm not supported in AndroidKeyStore. public void testWrongOrderEcdhc() throws Exception { testWrongOrder("ECDHC", EcUtil.getNistP256Params()); testWrongOrder("ECDHC", EcUtil.getBrainpoolP256r1Params()); @@ -814,26 +796,28 @@ 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"); - ECPublicKey pub; - try { - keyGen.initialize(spec); - pub = (ECPublicKey) keyGen.generateKeyPair().getPublic(); - } catch (GeneralSecurityException ex) { - // curve is not supported - return; - } + keyGen.initialize(spec); + ECPublicKey pub = (ECPublicKey) keyGen.generateKeyPair().getPublic(); KeyFactory kf = KeyFactory.getInstance("EC"); - KeyAgreement ka = KeyAgreement.getInstance("ECDH"); + KeyAgreement ka = KeyAgreement.getInstance("ECDH", EXPECTED_PROVIDER_NAME); for (int i = 1; i <= 64; i++) { BigInteger p1 = BigInteger.valueOf(i); ECPrivateKeySpec spec1 = new ECPrivateKeySpec(p1, spec); ECPrivateKeySpec spec2 = new ECPrivateKeySpec(order.subtract(p1), spec); - ka.init(kf.generatePrivate(spec1)); + PrivateKey priv1 = kf.generatePrivate(spec1); + // 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, isStrongBox)); ka.doPhase(pub, true); byte[] shared1 = ka.generateSecret(); - ka.init(kf.generatePrivate(spec2)); + PrivateKey priv2 = kf.generatePrivate(spec2); + 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 @@ -843,230 +827,23 @@ public static final EcPublicKeyTestVector EC_VALID_PUBLIC_KEY = } @Test - public void testLargePrivateKey() throws Exception { + public void testNistCurveLargePrivateKey() throws Exception { testLargePrivateKey(EcUtil.getNistP224Params()); testLargePrivateKey(EcUtil.getNistP256Params()); testLargePrivateKey(EcUtil.getNistP384Params()); // This test failed before CVE-2017-10176 was fixed. testLargePrivateKey(EcUtil.getNistP521Params()); - testLargePrivateKey(EcUtil.getBrainpoolP256r1Params()); - } - - /** - * This test tries to determine whether point multipliplication using two distinct - * points leads to distinguishable timings. - * - * The main goal here is to determine if the attack by Toru Akishita and Tsuyoshi Takagi - * in https://www-old.cdc.informatik.tu-darmstadt.de/reports/TR/TI-03-01.zvp.pdf - * might be applicable. I.e. one of the points contains a zero value when multiplied - * by mul, the other one does not. - * - * In its current form the test here is quite weak for a number of reasons: - * (1) The timing is often noisy, because the test is run as a unit test. - * (2) The test is executed with only a small number of input points. - * (3) The number of samples is rather low. Running this test with a larger sample - * size would detect more timing differences. Unfortunately - * (4) The test does not determine if a variable run time is exploitable. For example - * if the tested provider uses windowed exponentiation and the special point is - * in the precomputation table then timing differences are easy to spot, but more - * difficult to exploit and hence additional experiments would be necessary. - * - * @param spec the specification of the curve - * @param p0 This is a special point. I.e. multiplying this point by mul - * may lead to a zero value that may be observable. - * @param p1 a random point on the curve - * @param mul an integer, such that multiplying p0 with this value may lead to a timing - * difference - * @param privKeySize the size of the private key in bits - * @param comment describes the test case - */ - private void testTiming(ECParameterSpec spec, ECPoint p0, ECPoint p1, - BigInteger mul, int privKeySize, String comment) throws Exception { - ThreadMXBean bean = ManagementFactory.getThreadMXBean(); - if (!bean.isCurrentThreadCpuTimeSupported()) { - System.out.println("getCurrentThreadCpuTime is not supported. Skipping"); - return; - } - SecureRandom random = new SecureRandom(); - int fixedSize = mul.bitLength(); - int missingBits = privKeySize - 2 * fixedSize; - assertTrue(missingBits > 0); - // possible values for tests, minCount: - // 1024, 410 - // 2048, 880 - // 4096, 1845 - // 10000, 4682 - // I.e. these are values, such that doing 'tests' coin flips results in <= minCount heads or - // tails with a probability smaller than 2^-32. - // - // def min_count(n, b=33): - // res, sum, k = 1,1,0 - // bnd = 2**(n-b) - // while sum < bnd: - // res *= n - k - // res //= 1 + k - // k += 1 - // sum += res - // return k - 1 - final int tests = 2048; - final int minCount = 880; - // the number of measurements done with each point - final int repetitions = 8; - // the number of warmup experiments that are ignored - final int warmup = 8; - final int sampleSize = warmup + tests; - KeyFactory kf = KeyFactory.getInstance("EC"); - PublicKey[] publicKeys = new PublicKey[2]; - try { - publicKeys[0] = kf.generatePublic(new ECPublicKeySpec(p0, spec)); - publicKeys[1] = kf.generatePublic(new ECPublicKeySpec(p1, spec)); - } catch (InvalidKeySpecException ex) { - // unsupported curve - return; - } - PrivateKey[] privKeys = new PrivateKey[sampleSize]; - for (int i = 0; i < sampleSize; i++) { - BigInteger m = new BigInteger(missingBits, random); - m = mul.shiftLeft(missingBits).add(m); - m = m.shiftLeft(fixedSize).add(mul); - ECPrivateKeySpec privSpec = new ECPrivateKeySpec(m, spec); - privKeys[i] = kf.generatePrivate(privSpec); - } - KeyAgreement ka = KeyAgreement.getInstance("ECDH"); - long[][] timings = new long[2][sampleSize]; - for (int i = 0; i < sampleSize; i++) { - for (int j = 0; j < 2 * repetitions; j++) { - // idx determines which key to use. - int idx = (j ^ i) & 1; - ka.init(privKeys[i]); - long start = bean.getCurrentThreadCpuTime(); - ka.doPhase(publicKeys[idx], true); - byte[] unused = ka.generateSecret(); - long time = bean.getCurrentThreadCpuTime() - start; - timings[idx][i] += time; - } - } - for (int i = 0; i < sampleSize; i++) { - for (int j = 0; j < 2; j++) { - timings[j][i] /= repetitions; - } - } - - // Performs some statistics. - boolean noisy = false; // Set to true, if the timings have a large variance. - System.out.println("ECDH timing test:" + comment); - double[] avg = new double[2]; - double[] var = new double[2]; - for (int i = 0; i < 2; i++) { - double sum = 0.0; - double sumSqr = 0.0; - for (int j = warmup; j < sampleSize; j++) { - double val = (double) timings[i][j]; - sum += val; - sumSqr += val * val; - } - avg[i] = sum / tests; - var[i] = (sumSqr - avg[i] * sum) / (tests - 1); - double stdDev = Math.sqrt(var[i]); - double cv = stdDev / avg[i]; - System.out.println("Timing for point " + i + " avg: " + avg[i] + " std dev: " + stdDev - + " cv:" + cv); - // The ratio 0.05 below is a somewhat arbitrary value that tries to determine if the noise - // is too big to detect even larger timing differences. - if (cv > 0.05) { - noisy = true; - } - } - // Paired Z-test: - // The outcome of this value can be significantly influenced by extreme outliers, such - // as slow timings because of things like a garbage collection. - double sigmas = Math.abs(avg[0] - avg[1]) / Math.sqrt((var[0] + var[1]) / tests); - System.out.println("Sigmas: " + sigmas); - - // Pairwise comparison: - // this comparison has the property that it compares timings done with the same - // private key, hence timing differences from using different addition chain sizes - // are ignored. Extreme outliers should not influence the result a lot, as long as the - // number of outliers is small. - int point0Faster = 0; - int equal = 0; - for (int i = 0; i < sampleSize; i++) { - if (timings[0][i] < timings[1][i]) { - point0Faster += 1; - } else if (timings[0][i] < timings[1][i]) { - equal += 1; - } - } - point0Faster += equal / 2; - System.out.println("Point 0 multiplication is faster: " + point0Faster); - if (point0Faster < minCount || point0Faster > sampleSize - minCount) { - fail("Timing differences in ECDH computation detected"); - } else if (noisy) { - System.out.println("Timing was too noisy to expect results."); - } } - - @SlowTest(providers = - {ProviderType.BOUNCY_CASTLE, ProviderType.SPONGY_CASTLE, ProviderType.OPENJDK}) - @Test - public void testTimingSecp256r1() throws Exception { - // edge case for projective coordinates - BigInteger x1 = - new BigInteger("81bfb55b010b1bdf08b8d9d8590087aa278e28febff3b05632eeff09011c5579", 16); - BigInteger y1 = - new BigInteger("732d0e65267ea28b7af8cfcb148936c2af8664cbb4f04e188148a1457400c2a7", 16); - ECPoint p1 = new ECPoint(x1, y1); - // random point - BigInteger x2 = - new BigInteger("8608e36a91f1fba12e4074972af446176b5608c9c58dc318bd0742754c3dcee7", 16); - BigInteger y2 = - new BigInteger("bc2c9ecd44af916ca58d9e3ef1257f698d350ef486eb86137fe69a7375bcc191", 16); - ECPoint p2 = new ECPoint(x2, y2); - testTiming(EcUtil.getNistP256Params(), p1, p2, new BigInteger("2"), 256, "secp256r1"); - } - - @SlowTest(providers = - {ProviderType.BOUNCY_CASTLE, ProviderType.SPONGY_CASTLE, ProviderType.OPENJDK}) @Test - public void testTimingSecp384r1() throws Exception { - // edge case for projective coordinates - BigInteger x1 = - new BigInteger("7a6fadfee03eb09554f2a04fe08300aca88bb3a46e8f6347bace672cfe427698" - + "8541cef8dc10536a84580215f5f90a3b", 16); - BigInteger y1 = - new BigInteger("6d243d5d9de1cdddd04cbeabdc7a0f6c244391f7cb2d5738fe13c334add4b458" - + "5fef61ffd446db33b39402278713ae78", 16); - ECPoint p1 = new ECPoint(x1, y1); - // random point - BigInteger x2 = - new BigInteger("71f3c57d6a879889e582af2c7c5444b0eb6ba95d88365b21ca9549475273ecdd" - + "3930aa0bebbd1cf084e4049667278602", 16); - BigInteger y2 = - new BigInteger("9dcbc4d843af8944eb4ba018d369b351a9ea0f7b9e3561df2ee218d54e198f7c" - + "837a3abaa41dffd2d2cb771a7599ed9e", 16); - ECPoint p2 = new ECPoint(x2, y2); - testTiming(EcUtil.getNistP384Params(), p1, p2, new BigInteger("2"), 384, "secp384r1"); + public void testNistCurveLargePrivateKey_StrongBox() throws Exception { + KeyStoreUtil.assumeStrongBox(); + testLargePrivateKey(EcUtil.getNistP256Params(), true); } - @SlowTest(providers = - {ProviderType.BOUNCY_CASTLE, ProviderType.SPONGY_CASTLE, ProviderType.OPENJDK}) @Test - public void testTimingBrainpoolP256r1() throws Exception { - // edge case for Jacobian and projective coordinates - BigInteger x1 = - new BigInteger("79838c22d2b8dc9af2e6cf56f8826dc3dfe10fcb17b6aaaf551ee52bef12f826", 16); - BigInteger y1 = - new BigInteger("1e2ed3d453088c8552c6feecf898667bc1e15905002edec6b269feb7bea09d5b", 16); - ECPoint p1 = new ECPoint(x1, y1); - - // random point - BigInteger x2 = - new BigInteger("2720b2e821b2ac8209b573bca755a68821e1e09deb580666702570dd527dd4c1", 16); - BigInteger y2 = - new BigInteger("25cdd610243c7e693fad7bd69b43ae3e63e94317c4c6b717d9c8bc3be8c996fb", 16); - ECPoint p2 = new ECPoint(x2, y2); - testTiming(EcUtil.getBrainpoolP256r1Params(), p1, p2, new BigInteger("2"), 255, - "brainpoolP256r1"); + @Ignore // Brainpool curves are not supported in AndroidKeyStore. + public void testBrainpoolCurveLargePrivateKey() throws Exception { + testLargePrivateKey(EcUtil.getBrainpoolP256r1Params()); } } 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 a6ce23a..2e7108f 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/EcdsaTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/EcdsaTest.java @@ -16,25 +16,29 @@ package com.google.security.wycheproof; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import com.google.security.wycheproof.WycheproofRunner.ProviderType; -import com.google.security.wycheproof.WycheproofRunner.SlowTest; -import java.lang.management.ManagementFactory; -import java.lang.management.ThreadMXBean; import java.math.BigInteger; +import java.nio.ByteBuffer; import java.security.InvalidAlgorithmParameterException; import java.security.KeyPair; import java.security.KeyPairGenerator; +import java.security.KeyStore; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.Signature; +import java.security.PrivateKey; +import java.security.PublicKey; import java.security.interfaces.ECPrivateKey; 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.runner.RunWith; -import org.junit.runners.JUnit4; +import org.junit.Ignore; +import android.security.keystore.KeyProtection; +import android.security.keystore.KeyProperties; +import android.keystore.cts.util.KeyStoreUtil; /** * Tests ECDSA signatures. @@ -44,8 +48,29 @@ import org.junit.runners.JUnit4; * * @author bleichen@google.com (Daniel Bleichenbacher) */ -@RunWith(JUnit4.class) 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, + 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); + return (PrivateKey) keyStore.getKey(KEY_ALIAS_1, null); + } /** * Determines the Hash name from the ECDSA algorithm. There is a small inconsistency in the naming @@ -66,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. */ @@ -96,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) { @@ -114,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); } @@ -131,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. @@ -149,15 +215,11 @@ public class EcdsaTest { ECPublicKey pub = (ECPublicKey) keyPair.getPublic(); ECPrivateKey priv = (ECPrivateKey) keyPair.getPrivate(); - // Print the parameters. - System.out.println("Parameters for curve:" + curve); - EcUtil.printParameters(pub.getParams()); - Signature signer; Signature verifier; try { - signer = Signature.getInstance(algorithm); - verifier = Signature.getInstance(algorithm); + signer = Signature.getInstance(algorithm, EXPECTED_PROVIDER_NAME); + verifier = Signature.getInstance(algorithm, EXPECTED_PROVIDER_NAME); } catch (NoSuchAlgorithmException ex) { // The algorithm is not supported. return false; @@ -165,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(priv); + signer.initSign(getKeystorePrivateKey(pub, priv, isStrongBox)); signer.update(messageBytes); byte[] signature = signer.sign(); verifier.initVerify(pub); @@ -184,18 +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 { - KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); - try { - keyGen.initialize(ecParams); - } catch (InvalidAlgorithmParameterException ex) { - System.out.println("This provider does not support curve:" + curve); - return; + 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(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 @@ -203,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); - signer.initSign(priv); 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); } @@ -227,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++; @@ -259,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); } @@ -277,141 +347,117 @@ public class EcdsaTest { } } - @SlowTest( - providers = { - ProviderType.BOUNCY_CASTLE, - ProviderType.CONSCRYPT, - ProviderType.OPENJDK, - ProviderType.SPONGY_CASTLE - } - ) @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()); - testBias("SHA256WithECDSA", "brainpoolP256r1", EcUtil.getBrainpoolP256r1Params()); + public void testBiasSecp224r1() throws Exception { + testBias("SHA224WithECDSA", "secp224r1"); + } + + @Test + public void testBiasSecp256r1() throws Exception { + testBias("SHA256WithECDSA", "secp256r1"); + } + + @Test + public void testBiasSecp256r1_StrongBox() throws Exception { + testBias("SHA256WithECDSA", "secp256r1", true); + } + + @Test + public void testBiasSecp384r1() throws Exception { + testBias("SHA384WithECDSA", "secp384r1"); + } + + @Test + public void testBiasSecp521r1() throws Exception { + testBias("SHA512WithECDSA", "secp521r1"); + } + + @Test + @Ignore // Brainpool curve are not supported in AndroidKeyStore + 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 for a potential timing attack. This test checks if there is a correlation between the - * timing of signature generation and the size of the one-time key k. This is for example the case - * if a double and add method is used for the point multiplication. The test fails if such a - * correlation can be shown with high confidence. Further analysis will be necessary to determine - * how easy it is to exploit the bias in a timing attack. + * 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. */ - // TODO(bleichen): Determine if there are exploitable providers. - // - // SunEC currently fails this test. Since ECDSA typically is used with EC groups whose order - // is 224 bits or larger, it is unclear whether the same attacks that apply to DSA are practical. - // - // The ECDSA implementation in BouncyCastle leaks information about k through timing too. - // The test has not been optimized to detect this bias. It would require about 5'000'000 samples, - // which is too much for a simple unit test. - // - // BouncyCastle uses FixedPointCombMultiplier for ECDSA. This is a method using - // precomputation. The implementation is not constant time, since the precomputation table - // contains the point at infinity and adding this point is faster than ordinary point additions. - // The timing leak only has a small correlation to the size of k and at the moment it is is very - // unclear if the can be exploited. (Randomizing the precomputation table by adding the same - // random point to each element in the table and precomputing the necessary offset to undo the - // precomputation seems much easier than analyzing this.) - public void testTiming(String algorithm, String curve, ECParameterSpec ecParams) - throws Exception { - ThreadMXBean bean = ManagementFactory.getThreadMXBean(); - if (!bean.isCurrentThreadCpuTimeSupported()) { - System.out.println("getCurrentThreadCpuTime is not supported. Skipping"); - return; + 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"); - try { - keyGen.initialize(ecParams); - } catch (InvalidAlgorithmParameterException ex) { - System.out.println("This provider does not support curve:" + curve); - return; - } + keyGen.initialize(new ECGenParameterSpec(curve)); KeyPair keyPair = keyGen.generateKeyPair(); - ECPrivateKey priv = (ECPrivateKey) keyPair.getPrivate(); - - String message = "Hello"; - String hashAlgorithm = getHashAlgorithm(algorithm); - byte[] messageBytes = message.getBytes("UTF-8"); - byte[] digest = MessageDigest.getInstance(hashAlgorithm).digest(messageBytes); - BigInteger h = new BigInteger(1, digest); - Signature signer = Signature.getInstance(algorithm); - signer.initSign(priv); - // The number of samples used for the test. This number is a bit low. - // I.e. it just barely detects that SunEC leaks information about the size of k. - int samples = 50000; - long[] timing = new long[samples]; - BigInteger[] k = new BigInteger[samples]; + 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++) { - long start = bean.getCurrentThreadCpuTime(); - signer.update(messageBytes); + // This is the function call that is tested by this test. + signer.initSign(priv, null); + signer.update(message[i]); byte[] signature = signer.sign(); - timing[i] = bean.getCurrentThreadCpuTime() - start; - k[i] = extractK(signature, h, priv); - } - long[] sorted = Arrays.copyOf(timing, timing.length); - Arrays.sort(sorted); - double n = priv.getParams().getOrder().doubleValue(); - double expectedAverage = n / 2; - double maxSigma = 0; - System.out.println("testTiming algorithm:" + algorithm); - for (int idx = samples - 1; idx > 10; idx /= 2) { - long cutoff = sorted[idx]; - int count = 0; - BigInteger total = BigInteger.ZERO; - for (int i = 0; i < samples; i++) { - if (timing[i] <= cutoff) { - total = total.add(k[i]); - count += 1; - } - } - double expectedStdDev = n / Math.sqrt(12 * count); - double average = total.doubleValue() / count; - // Number of standard deviations that the average is away from - // the expected value: - double sigmas = Math.abs(expectedAverage - average) / expectedStdDev; - if (sigmas > maxSigma) { - maxSigma = sigmas; - } - System.out.println( - "count:" - + count - + " cutoff:" - + cutoff - + " relative average:" - + (average / expectedAverage) - + " sigmas:" - + sigmas); - } - // Checks if the signatures with a small timing have a biased k. - // We use 7 standard deviations, so that the probability of a false positive is smaller - // than 10^{-10}. - if (maxSigma >= 7) { - fail("Signatures with short timing have a biased k"); + BigInteger r = extractR(signature); + assertTrue("Same r computed twice", rSet.add(r)); } } - @SlowTest( - providers = { - ProviderType.BOUNCY_CASTLE, - ProviderType.CONSCRYPT, - ProviderType.OPENJDK, - ProviderType.SPONGY_CASTLE - } - ) @Test - public void testTimingAll() throws Exception { - testTiming("SHA256WithECDSA", "secp256r1", EcUtil.getNistP256Params()); - // TODO(bleichen): crypto libraries sometimes use optimized code for curves that are frequently - // used. Hence it would make sense to test distinct curves. But at the moment testing many - // curves is not practical since one test alone is already quite time consuming. - // testTiming("SHA224WithECDSA", "secp224r1", EcUtil.getNistP224Params()); - // testTiming("SHA384WithECDSA", "secp384r1", EcUtil.getNistP384Params()); - // testTiming("SHA512WithECDSA", "secp521r1", EcUtil.getNistP521Params()); - // testTiming("SHA256WithECDSA", "brainpoolP256r1", EcUtil.getBrainpoolP256r1Params()); + 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 c34f3f7..c4a6b63 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/JsonAeadTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonAeadTest.java @@ -14,12 +14,14 @@ package com.google.security.wycheproof; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import java.security.AlgorithmParameters; import java.security.GeneralSecurityException; +import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import javax.crypto.Cipher; import javax.crypto.SecretKey; @@ -28,15 +30,23 @@ import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.junit.Test; import org.junit.Ignore; +import org.junit.After; import android.security.keystore.KeyProtection; import android.security.keystore.KeyProperties; -import java.security.KeyStore; +import android.keystore.cts.util.KeyStoreUtil; /** This test uses test vectors in JSON format to test AEAD schemes. */ public class JsonAeadTest { private static final String EXPECTED_PROVIDER_NAME = TestUtil.EXPECTED_PROVIDER_NAME; - private static final String EXPECTED_CRYPTO_PROVIDER_NAME = TestUtil.EXPECTED_CRYPTO_OP_PROVIDER_NAME; + private static final String EXPECTED_CRYPTO_PROVIDER_NAME = + TestUtil.EXPECTED_CRYPTO_OP_PROVIDER_NAME; + private static final String KEY_ALIAS_1 = "Key1"; + + @After + public void tearDown() throws Exception { + KeyStoreUtil.cleanUpKeyStore(); + } /** Joins two bytearrays. */ protected static byte[] join(byte[] head, byte[] tail) { @@ -79,24 +89,20 @@ 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")) { SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); - - KeyStore keyStore = KeyStore.getInstance(EXPECTED_PROVIDER_NAME); - keyStore.load(null); - keyStore.setEntry( - "key1", - new KeyStore.SecretKeyEntry(keySpec), + KeyStore keyStore = KeyStoreUtil.saveSecretKeyToKeystore(KEY_ALIAS_1, 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. - SecretKey keyStoreKey = (SecretKey) keyStore.getKey("key1", null); + SecretKey keyStoreKey = (SecretKey) keyStore.getKey(KEY_ALIAS_1, null); AlgorithmParameters params = AlgorithmParameters.getInstance("GCM"); params.init(new GCMParameterSpec(tagSize, iv)); @@ -147,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 @@ -162,14 +171,17 @@ public class JsonAeadTest { final String expectedVersion = "0.6"; // Checking preconditions. - try { - Cipher.getInstance(algorithm, EXPECTED_CRYPTO_PROVIDER_NAME); - } catch (NoSuchAlgorithmException ex) { - throw ex; - } + Cipher.getInstance(algorithm, EXPECTED_CRYPTO_PROVIDER_NAME); JsonObject test = JsonUtil.getTestVectors(this.getClass(), filename); String generatorVersion = test.get("generatorVersion").getAsString(); + assertFalse( + algorithm + + ": expecting test vectors with version " + + expectedVersion + + " found vectors with version " + + generatorVersion, + generatorVersion.equals(expectedVersion)); int numTests = test.get("numberOfTests").getAsInt(); int cntTests = 0; int errors = 0; @@ -195,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. @@ -226,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; @@ -257,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 a072989..d6dae08 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/JsonCipherTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonCipherTest.java @@ -14,6 +14,7 @@ package com.google.security.wycheproof; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; import com.google.gson.JsonElement; @@ -23,11 +24,15 @@ import java.security.NoSuchAlgorithmException; import java.util.Set; import java.util.TreeSet; import javax.crypto.Cipher; +import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import org.junit.After; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import android.security.keystore.KeyProtection; +import android.security.keystore.KeyProperties; +import java.security.KeyStore; +import android.keystore.cts.util.KeyStoreUtil; /** * This test uses test vectors in JSON format to test symmetric ciphers. @@ -36,8 +41,14 @@ import org.junit.runners.JUnit4; * are randomized using an initialization vector as long as the JSON test vectors are represented * with the type "IndCpaTest". */ -@RunWith(JUnit4.class) public class JsonCipherTest { + private static final String EXPECTED_PROVIDER_NAME = TestUtil.EXPECTED_CRYPTO_OP_PROVIDER_NAME; + private static final String KEY_ALIAS_1 = "Key1"; + + @After + public void tearDown() throws Exception { + KeyStoreUtil.cleanUpKeyStore(); + } /** Convenience method to get a byte array from a JsonObject. */ protected static byte[] getBytes(JsonObject object, String name) throws Exception { @@ -63,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"); @@ -73,7 +85,17 @@ public class JsonCipherTest { fail("Unsupported algorithm:" + algorithm); } IvParameterSpec ivSpec = new IvParameterSpec(iv); - cipher.init(opmode, keySpec, ivSpec); + KeyStore keyStore = KeyStoreUtil.saveSecretKeyToKeystore(KEY_ALIAS_1, keySpec, + new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + .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); + + cipher.init(opmode, keyStoreKey, ivSpec); } @@ -104,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. @@ -111,27 +137,20 @@ public class JsonCipherTest { // the minor number if only the test vectors (but not the format) changes. // Versions meant for distribution have no status. final String expectedVersion = "0.4"; - JsonObject test = JsonUtil.getTestVectors(filename); + JsonObject test = JsonUtil.getTestVectors(this.getClass(), filename); Set<String> exceptions = new TreeSet<String>(); String generatorVersion = test.get("generatorVersion").getAsString(); - if (!generatorVersion.equals(expectedVersion)) { - System.out.println( + assertFalse( algorithm + ": expecting test vectors with version " + expectedVersion + " found vectors with version " - + generatorVersion); - } + + generatorVersion, + generatorVersion.equals(expectedVersion)); int numTests = test.get("numberOfTests").getAsInt(); int cntTests = 0; int errors = 0; - Cipher cipher; - try { - cipher = Cipher.getInstance(algorithm); - } catch (NoSuchAlgorithmException ex) { - System.out.println("Algorithm is not supported. Skipping test for " + algorithm); - return; - } + Cipher cipher = Cipher.getInstance(algorithm, EXPECTED_PROVIDER_NAME); for (JsonElement g : test.getAsJsonArray("testGroups")) { JsonObject group = g.getAsJsonObject(); for (JsonElement t : group.getAsJsonArray("tests")) { @@ -151,11 +170,10 @@ 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. - System.out.println(ex.toString()); continue; } try { @@ -164,22 +182,15 @@ public class JsonCipherTest { if (result.equals("invalid")) { if (eq) { // Some test vectors use invalid parameters that should be rejected. - System.out.println("Encrypted " + tc); errors++; } } else { if (!eq) { - System.out.println( - "Incorrect ciphertext for " - + tc - + " ciphertext:" - + TestUtil.bytesToHex(encrypted)); errors++; } } } catch (GeneralSecurityException ex) { if (result.equals("valid")) { - System.out.println("Failed to encrypt " + tc); errors++; } } @@ -190,9 +201,8 @@ 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) { - System.out.println("Parameters accepted for encryption but not decryption " + tc); errors++; continue; } @@ -200,18 +210,15 @@ public class JsonCipherTest { byte[] decrypted = cipher.doFinal(ciphertext); boolean eq = arrayEquals(decrypted, msg); if (result.equals("invalid")) { - System.out.println("Decrypted invalid ciphertext " + tc + " eq:" + eq); errors++; } else { if (!eq) { - System.out.println( - "Incorrect decryption " + tc + " decrypted:" + TestUtil.bytesToHex(decrypted)); + errors++; } } } catch (GeneralSecurityException ex) { - exceptions.add(ex.getMessage()); + exceptions.add(ex.getMessage() == null ? "" : ex.getMessage()); if (result.equals("valid")) { - System.out.println("Failed to decrypt " + tc); errors++; } } @@ -228,15 +235,25 @@ public class JsonCipherTest { // AES/CBC/PKCS5Padding with the tested provider are vulnerable to attacks. Rather it means // that the provider might simplify attacks if the protocol is using AES/CBC/PKCS5Padding // incorrectly. - System.out.println("Number of distinct exceptions:" + exceptions.size()); + StringBuilder sb = new StringBuilder(); + sb.append("Exceptions: "); for (String ex : exceptions) { - System.out.println(ex); + sb.append(ex.toString() + " "); } - assertEquals(1, exceptions.size()); + assertEquals(sb.toString(), 1, exceptions.size()); } @Test - public void testAesCbcPkcs5() throws Exception { - testCipher("aes_cbc_pkcs5_test.json", "AES/CBC/PKCS5Padding"); + 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 5a8b877..779dc42 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/JsonEcdhTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonEcdhTest.java @@ -20,6 +20,7 @@ import com.google.gson.JsonObject; import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.KeyFactory; +import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; @@ -27,13 +28,24 @@ import java.security.spec.ECPrivateKeySpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import javax.crypto.KeyAgreement; +import org.junit.After; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import org.junit.Ignore; +import android.security.keystore.KeyProtection; +import android.security.keystore.KeyProperties; +import android.keystore.cts.util.KeyStoreUtil; +import android.util.Log; /** This test uses test vectors in JSON format to check implementations of ECDH. */ -@RunWith(JUnit4.class) public class JsonEcdhTest { + private static final String TAG = "JsonEcdhTest"; + private static final String EXPECTED_PROVIDER_NAME = TestUtil.EXPECTED_PROVIDER_NAME; + private static final String KEY_ALIAS_1 = "Key1"; + + @After + public void tearDown() throws Exception { + KeyStoreUtil.cleanUpKeyStore(); + } /** Convenience mehtod to get a String from a JsonObject */ protected static String getString(JsonObject object, String name) throws Exception { @@ -76,7 +88,10 @@ public class JsonEcdhTest { * ... **/ public void testEcdhComp(String filename) throws Exception { - JsonObject test = JsonUtil.getTestVectors(filename); + 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. // In particular, this means that the public keys use X509 encoding. @@ -107,12 +122,20 @@ public class JsonEcdhTest { PrivateKey privKey = kf.generatePrivate(spec); X509EncodedKeySpec x509keySpec = new X509EncodedKeySpec(publicEncoded); PublicKey pubKey = kf.generatePublic(x509keySpec); - KeyAgreement ka = KeyAgreement.getInstance("ECDH"); - ka.init(privKey); - ka.doPhase(pubKey, true); + + 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); + PublicKey publicKey = keyStore.getCertificate(KEY_ALIAS_1).getPublicKey(); + + ka.init(keyStorePrivateKey); + ka.doPhase(publicKey, true); String sharedHex = TestUtil.bytesToHex(ka.generateSecret()); if (result.equals("invalid")) { - System.out.println( + Log.e(TAG, "Computed ECDH with invalid parameters" + " tcId:" + tcid @@ -122,7 +145,7 @@ public class JsonEcdhTest { + sharedHex); errors++; } else if (!expectedHex.equals(sharedHex)) { - System.out.println( + Log.e(TAG, "Incorrect ECDH computation" + " tcId:" + tcid @@ -137,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")) { @@ -146,14 +171,14 @@ public class JsonEcdhTest { } } catch (Exception ex) { // Other exceptions typically indicate that something is wrong with the implementation. - System.out.println( + Log.e(TAG, "Test vector with tcId:" + tcid + " comment:" + comment + " throws:" + ex.toString()); errors++; } } } assertEquals(0, errors); - assertEquals(numTests, passedTests + rejectedTests + skippedTests); + assertEquals(numTests, passedTests + rejectedTests); } @Test @@ -165,6 +190,12 @@ public class JsonEcdhTest { 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 { @@ -177,31 +208,37 @@ public class JsonEcdhTest { } @Test + @Ignore // Secp256k1 curve not supported in AndroidKeystore public void testSecp256k1() throws Exception { testEcdhComp("ecdh_secp256k1_test.json"); } @Test + @Ignore // Brainpool curves are not supported in AndroidKeystore public void testBrainpoolP224r1() throws Exception { testEcdhComp("ecdh_brainpoolP224r1_test.json"); } @Test + @Ignore // Brainpool curves are not supported in AndroidKeystore public void testBrainpoolP256r1() throws Exception { testEcdhComp("ecdh_brainpoolP256r1_test.json"); } @Test + @Ignore // Brainpool curves are not supported in AndroidKeystore public void testBrainpoolP320r1() throws Exception { testEcdhComp("ecdh_brainpoolP320r1_test.json"); } @Test + @Ignore // Brainpool curves are not supported in AndroidKeystore public void testBrainpoolP384r1() throws Exception { testEcdhComp("ecdh_brainpoolP384r1_test.json"); } @Test + @Ignore // Brainpool curves are not supported in AndroidKeystore public void testBrainpoolP512r1() throws Exception { testEcdhComp("ecdh_brainpoolP512r1_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 eeb48ec..d813d02 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/JsonMacTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonMacTest.java @@ -12,9 +12,12 @@ package com.google.security.wycheproof; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import java.security.Key; +import java.security.KeyStore; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; @@ -23,13 +26,23 @@ import java.util.Locale; import javax.crypto.Mac; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import org.junit.After; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import org.junit.Ignore; +import android.security.keystore.KeyProtection; +import android.security.keystore.KeyProperties; +import java.io.IOException; +import android.keystore.cts.util.KeyStoreUtil; /** This test uses test vectors in JSON format to test MAC primitives. */ -@RunWith(JUnit4.class) public class JsonMacTest { + private static final String EXPECTED_PROVIDER_NAME = TestUtil.EXPECTED_CRYPTO_OP_PROVIDER_NAME; + private static final String KEY_ALIAS_1 = "Key1"; + + @After + public void tearDown() throws Exception { + KeyStoreUtil.cleanUpKeyStore(); + } /** Convenience method to get a byte array from an JsonObject */ protected static byte[] getBytes(JsonObject obj, String name) throws Exception { @@ -59,9 +72,9 @@ 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 GeneralSecurityException { - Mac mac = Mac.getInstance(algorithm); + 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")) { SecretKeySpec keySpec = new SecretKeySpec(key, algorithm); @@ -77,7 +90,13 @@ public class JsonMacTest { // But this class is often not supported. Hence the computation here, just computes a // full length tag and truncates it. The drawback of having to truncate tags is that // the caller has to compare truncated tags during verification. - mac.init(keySpec); + KeyStore keyStore = KeyStoreUtil.saveSecretKeyToKeystore(KEY_ALIAS_1, keySpec, + 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); mac.update(msg); byte[] tag = mac.doFinal(); return Arrays.copyOf(tag, tagSize / 8); @@ -93,15 +112,13 @@ 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(filename); + JsonObject test = JsonUtil.getTestVectors(this.getClass(), filename); String algorithm = test.get("algorithm").getAsString(); - try { - Mac.getInstance(algorithm); - } catch (NoSuchAlgorithmException ex) { - System.out.println("Algorithm is not supported. Skipping test for " + algorithm); - return; - } + Mac.getInstance(algorithm, EXPECTED_PROVIDER_NAME); int numTests = test.get("numberOfTests").getAsInt(); int cntTests = 0; @@ -118,23 +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; - try { - computedTag = computeMac(algorithm, key, msg, tagSize); - } catch (GeneralSecurityException ex) { - // Some libraries restrict key size or tag size. Hence valid MACs might be - // rejected. - continue; - } catch (IllegalArgumentException ex) { - // Thrown by javax.crypto.spec.SecretKeySpec (e.g. when the key is empty). - continue; - } + computedTag = computeMac(algorithm, key, msg, tagSize, isStrongBox); boolean eq = arrayEquals(expectedTag, computedTag); if (result.equals("invalid")) { @@ -142,26 +153,17 @@ public class JsonMacTest { // Some test vectors use invalid parameters that should be rejected. // E.g. an implementation must not allow AES-GMAC with an IV of length 0, // since this leaks the authentication key. - System.out.println("Computed mac for test case " + tc); errors++; } } else { if (eq) { passedTests++; } else { - System.out.println( - "Incorrect tag for " - + tc - + " expected:" - + TestUtil.bytesToHex(expectedTag) - + " computed:" - + TestUtil.bytesToHex(computedTag)); errors++; } } } } - System.out.println("passed Tests for " + algorithm + ":" + passedTests); assertEquals(0, errors); assertEquals(numTests, cntTests); } @@ -180,7 +182,7 @@ public class JsonMacTest { */ protected static Mac getInitializedMacWithIv(String algorithm, byte[] key, byte[] iv, int tagSize) throws GeneralSecurityException { - Mac mac = Mac.getInstance(algorithm); + Mac mac = Mac.getInstance(algorithm, EXPECTED_PROVIDER_NAME); algorithm = algorithm.toUpperCase(Locale.ENGLISH); if (algorithm.equals("AES-GMAC")) { SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); @@ -207,14 +209,9 @@ public class JsonMacTest { */ public void testMacWithIv(String filename, String algorithm) throws Exception { // Checking preconditions. - try { - Mac.getInstance(algorithm); - } catch (NoSuchAlgorithmException ex) { - System.out.println("Algorithm is not supported. Skipping test for " + algorithm); - return; - } + Mac.getInstance(algorithm, EXPECTED_PROVIDER_NAME); - JsonObject test = JsonUtil.getTestVectors(filename); + JsonObject test = JsonUtil.getTestVectors(this.getClass(), filename); int numTests = test.get("numberOfTests").getAsInt(); int cntTests = 0; int passedTests = 0; @@ -237,17 +234,7 @@ public class JsonMacTest { // "acceptable" are test vectors with weak parameters or legacy formats. String result = testcase.get("result").getAsString(); - Mac mac; - try { - mac = getInitializedMacWithIv(algorithm, key, iv, tagSize); - } catch (GeneralSecurityException ex) { - // Some libraries restrict key size, iv size and tag size. - // Because of the initialization of the Mac might fail. - continue; - } catch (IllegalArgumentException ex) { - // Thrown by javax.crypto.spec.SecretKeySpec (e.g. when the key is empty). - continue; - } + Mac mac = getInitializedMacWithIv(algorithm, key, iv, tagSize); byte[] computedTag = mac.doFinal(msg); boolean eq = arrayEquals(expectedTag, computedTag); @@ -256,26 +243,17 @@ public class JsonMacTest { // Some test vectors use invalid parameters that should be rejected. // E.g. an implementation must not allow AES-GMAC with an IV of length 0, // since this leaks the authentication key. - System.out.println("Computed mac for test case " + tc); errors++; } } else { if (eq) { passedTests++; } else { - System.out.println( - "Incorrect tag for " - + tc - + " expected:" - + TestUtil.bytesToHex(expectedTag) - + " computed:" - + TestUtil.bytesToHex(computedTag)); errors++; } } } } - System.out.println("passed Tests for " + algorithm + ":" + passedTests); assertEquals(0, errors); assertEquals(numTests, cntTests); } @@ -294,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 { @@ -306,26 +289,31 @@ public class JsonMacTest { } @Test + @Ignore // HMAC Sha3 algorithms are not supported in AndroidKeyStore public void testHmacSha3_224() throws Exception { testMac("hmac_sha3_224_test.json"); } @Test + @Ignore // HMAC Sha3 algorithms are not supported in AndroidKeyStore public void testHmacSha3_256() throws Exception { testMac("hmac_sha3_256_test.json"); } @Test + @Ignore // HMAC Sha3 algorithms are not supported in AndroidKeyStore public void testHmacSha3_384() throws Exception { testMac("hmac_sha3_384_test.json"); } @Test + @Ignore // HMAC Sha3 algorithms are not supported in AndroidKeyStore public void testHmacSha3_512() throws Exception { testMac("hmac_sha3_512_test.json"); } @Test + @Ignore // Ignored due to AES-GMAC algorithm not supported in AndroidKeyStore public void testAesGmac() throws Exception { testMacWithIv("gmac_test.json", "AES-GMAC"); } 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 21bf0d5..bbea28a 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/JsonSignatureTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonSignatureTest.java @@ -15,16 +15,16 @@ package com.google.security.wycheproof; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.security.wycheproof.WycheproofRunner.ExcludedTest; -import com.google.security.wycheproof.WycheproofRunner.NoPresubmitTest; -import com.google.security.wycheproof.WycheproofRunner.ProviderType; import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.KeyFactory; +import java.security.KeyStore; import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; @@ -33,17 +33,38 @@ import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.HashSet; import java.util.Set; +import org.junit.After; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import org.junit.Ignore; +import android.security.keystore.KeyProtection; +import android.security.keystore.KeyProperties; +import android.keystore.cts.util.KeyStoreUtil; /** * This test uses test vectors in JSON format to check digital signature schemes. There are still a * lot of open questions, e.g. the format for the test vectors is not yet finalized. Therefore, we * are not integrating the tests here into other tests */ -@RunWith(JUnit4.class) public class JsonSignatureTest { + private static final String EXPECTED_PROVIDER_NAME = TestUtil.EXPECTED_CRYPTO_OP_PROVIDER_NAME; + private static final String KEY_ALIAS_1 = "TestKey"; + + @After + public void tearDown() throws Exception { + KeyStoreUtil.cleanUpKeyStore(); + } + + private static PrivateKey getKeystorePrivateKey(PublicKey pubKey, PrivateKey privKey, String digest, + boolean isStrongBox) throws Exception { + KeyProtection keyProtection = new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN) + .setDigests(digest) + .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1) + .setIsStrongBoxBacked(isStrongBox) + .build(); + KeyStore keyStore = + KeyStoreUtil.saveKeysToKeystore(KEY_ALIAS_1, pubKey, privKey, keyProtection); + return (PrivateKey) keyStore.getKey(KEY_ALIAS_1, null); + } /** * Defines the format of the signatures. RAW is used when the signature scheme already @@ -105,14 +126,14 @@ public class JsonSignatureTest { */ protected static Signature getSignatureInstance( JsonObject group, String signatureAlgorithm, Format signatureFormat) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException, NoSuchProviderException { String md = ""; if (group.has("sha")) { md = convertMdName(getString(group, "sha")); } if (signatureAlgorithm.equals("ECDSA") || signatureAlgorithm.equals("DSA")) { if (signatureFormat == Format.ASN) { - return Signature.getInstance(md + "WITH" + signatureAlgorithm); + return Signature.getInstance(md + "WITH" + signatureAlgorithm, EXPECTED_PROVIDER_NAME); } else if (signatureFormat == Format.P1363) { // The algorithm names for signature schemes with P1363 format have distinct names // in distinct providers. This is mainly the case since the P1363 format has only @@ -120,32 +141,28 @@ public class JsonSignatureTest { // than that. Hence the code below just tries known algorithm names. try { String jdkName = md + "WITH" + signatureAlgorithm + "inP1363Format"; - return Signature.getInstance(jdkName); + return Signature.getInstance(jdkName, EXPECTED_PROVIDER_NAME); } catch (NoSuchAlgorithmException ex) { // jdkName is not known. } - try { - String bcName = md + "WITHPLAIN-" + signatureAlgorithm; - return Signature.getInstance(bcName); - } catch (NoSuchAlgorithmException ex) { - // bcName is not known. - } + String bcName = md + "WITHPLAIN-" + signatureAlgorithm; + return Signature.getInstance(bcName, EXPECTED_PROVIDER_NAME); } } else if (signatureAlgorithm.equals("RSA")) { if (signatureFormat == Format.RAW) { - return Signature.getInstance(md + "WITH" + signatureAlgorithm); + return Signature.getInstance(md + "WITH" + signatureAlgorithm, EXPECTED_PROVIDER_NAME); } } else if (signatureAlgorithm.equals("ED25519") || signatureAlgorithm.equals("ED448")) { if (signatureFormat == Format.RAW) { // http://openjdk.java.net/jeps/339 try { - return Signature.getInstance(signatureAlgorithm); + return Signature.getInstance(signatureAlgorithm, EXPECTED_PROVIDER_NAME); } catch (NoSuchAlgorithmException ex) { // signatureAlgorithm is not known. } // An alternative name (e.g. used by BouncyCastle) is "EDDSA". try { - return Signature.getInstance("EDDSA"); + return Signature.getInstance("EDDSA", EXPECTED_PROVIDER_NAME); } catch (NoSuchAlgorithmException ex) { // "EDDSA" is not known either. } @@ -245,12 +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); - return kf.generatePrivate(keySpec); + PrivateKey intermediateKey = kf.generatePrivate(keySpec); + X509EncodedKeySpec x509keySpec = new X509EncodedKeySpec(pubEncoded); + PublicKey pubKey = kf.generatePublic(x509keySpec); + return getKeystorePrivateKey(pubKey, intermediateKey, digest, isStrongBox); } else { throw new NoSuchAlgorithmException("Algorithm " + algorithm + " is not supported"); } @@ -298,19 +321,18 @@ public class JsonSignatureTest { public void testVerification( String filename, String signatureAlgorithm, Format signatureFormat, boolean allowSkippingKeys) throws Exception { - JsonObject test = JsonUtil.getTestVectors(filename); + JsonObject test = JsonUtil.getTestVectors(this.getClass(), filename); // Checks whether the test vectors in the file use the expected algorithm and the expected // format for the signatures. String schema = expectedSchema(signatureAlgorithm, signatureFormat, true); String actualSchema = getString(test, "schema"); - if (!schema.isEmpty() && !schema.equals(actualSchema)) { - System.out.println( + assertFalse( signatureAlgorithm + ": expecting test vectors with schema " + schema + " found vectors with schema " - + actualSchema); - } + + actualSchema, + !schema.isEmpty() && !schema.equals(actualSchema)); int numTests = test.get("numberOfTests").getAsInt(); int cntTests = 0; int verifiedSignatures = 0; @@ -376,49 +398,14 @@ public class JsonSignatureTest { } catch (Exception ex) { // Other exceptions (i.e. unchecked exceptions) are considered as error // since a third party should never be able to cause such exceptions. - System.out.println( - signatureAlgorithm - + " signature throws " - + ex.toString() - + " " - + filename - + " tcId:" - + tcid - + " sig:" - + sig); verified = false; failure = ex; errors++; } if (!verified && result.equals("valid")) { - String reason = ""; - if (failure != null) { - reason = " reason:" + failure; - } - System.out.println( - "Valid " - + signatureAlgorithm - + " signature not verified." - + " " - + filename - + " tcId:" - + tcid - + " sig:" - + sig - + reason); errors++; } else if (verified) { if (result.equals("invalid")) { - System.out.println( - "Invalid" - + signatureAlgorithm - + " signature verified." - + " " - + filename - + " tcId:" - + tcid - + " sig:" - + sig); errors++; } else { verifiedSignatures++; @@ -426,24 +413,6 @@ public class JsonSignatureTest { } } } - // Prints some information if tests were skipped. This avoids giving - // the impression that algorithms are supported. - if (skippedKeys > 0 || skippedAlgorithms > 0 || verifiedSignatures == 0) { - System.out.println( - "File:" - + filename - + " number of skipped keys:" - + skippedKeys - + " number of skipped algorithms:" - + skippedAlgorithms - + " number of supported keys:" - + supportedKeys - + " verified signatures:" - + verifiedSignatures); - for (String s : skippedGroups) { - System.out.println("Skipped groups where " + s); - } - } assertEquals(0, errors); if (skippedKeys == 0 && skippedAlgorithms == 0) { assertEquals(numTests, cntTests); @@ -464,21 +433,14 @@ 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 { - JsonObject test = JsonUtil.getTestVectors(filename); - // Checks whether the test vectors in the file use the expected algorithm and the expected - // format for the signatures. - String schema = expectedSchema(signatureAlgorithm, signatureFormat, false); - String actualSchema = getString(test, "schema"); - if (!schema.isEmpty() && !schema.equals(actualSchema)) { - System.out.println( - signatureAlgorithm - + ": expecting test vectors with schema " - + schema - + " found vectors with schema " - + actualSchema); - } + boolean allowSkippingKeys, boolean isStrongBox) throws Exception { + JsonObject test = JsonUtil.getTestVectors(this.getClass(), filename); int cntTests = 0; int errors = 0; int skippedKeys = 0; @@ -486,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; @@ -510,39 +472,23 @@ public class JsonSignatureTest { signer.update(message); String sig = TestUtil.bytesToHex(signer.sign()); if (!sig.equals(expectedSig)) { - System.out.println( - "Incorrect signature generated " - + filename - + " tcId:" - + tcid - + " expected:" - + expectedSig - + " sig:" - + sig); + android.util.Log.e("JsonSignatureTest", "Signature mismatch error for test id " + tcid); errors++; } else { cntTests++; } } catch (InvalidKeyException | SignatureException ex) { if (result.equals("valid")) { - System.out.println( - "Failed to sign " - + filename - + " tcId:" - + tcid - + " with exception:" - + ex); - - errors++; + android.util.Log.e("JsonSignatureTest", "Unexpected exception for test id " + tcid, ex); + if (!isStrongBox) { + errors++; + } } } } } assertEquals(0, errors); if (skippedKeys > 0) { - System.out.println("File:" + filename); - System.out.println("Number of signatures verified:" + cntTests); - System.out.println("Number of skipped keys:" + skippedKeys); assertTrue(allowSkippingKeys); } } @@ -594,180 +540,209 @@ public class JsonSignatureTest { // Testing curves that may not be supported by a provider. @Test + @Ignore // Secp256k1 curve not supported in AndroidKeystore public void testSecp256k1Sha256() throws Exception { testVerification("ecdsa_secp256k1_sha256_test.json", "ECDSA", Format.ASN, true); } @Test + @Ignore // Secp256k1 curve not supported in AndroidKeystore public void testSecp256k1Sha512() throws Exception { testVerification("ecdsa_secp256k1_sha512_test.json", "ECDSA", Format.ASN, true); } - @NoPresubmitTest( - providers = {ProviderType.OPENJDK}, - bugs = {"b/117643131"} - ) @Test + @Ignore // Brainpool curves are not supported in AndroidKeyStore public void testBrainpoolP224r1Sha224() throws Exception { testVerification("ecdsa_brainpoolP224r1_sha224_test.json", "ECDSA", Format.ASN, true); } @Test + @Ignore // Brainpool curves are not supported in AndroidKeyStore public void testBrainpoolP256r1Sha256() throws Exception { testVerification("ecdsa_brainpoolP256r1_sha256_test.json", "ECDSA", Format.ASN, true); } @Test + @Ignore // Brainpool curves are not supported in AndroidKeyStore public void testBrainpoolP320r1Sha384() throws Exception { testVerification("ecdsa_brainpoolP320r1_sha384_test.json", "ECDSA", Format.ASN, true); } @Test + @Ignore // Brainpool curves are not supported in AndroidKeyStore public void testBrainpoolP384r1Sha384() throws Exception { testVerification("ecdsa_brainpoolP384r1_sha384_test.json", "ECDSA", Format.ASN, true); } @Test + @Ignore // Brainpool curves are not supported in AndroidKeyStore public void testBrainpoolP512r1Sha512() throws Exception { testVerification("ecdsa_brainpoolP512r1_sha512_test.json", "ECDSA", Format.ASN, true); } // SHA-3 signatures @Test + @Ignore // SHA3 algorithms are not supported in AndroidKeyStore public void testSecp224r1Sha3_224 () throws Exception { testVerification("ecdsa_secp224r1_sha3_224_test.json", "ECDSA", Format.ASN, true); } @Test + @Ignore // SHA3 algorithms are not supported in AndroidKeyStore public void testSecp224r1Sha3_256 () throws Exception { testVerification("ecdsa_secp224r1_sha3_256_test.json", "ECDSA", Format.ASN, true); } @Test + @Ignore // SHA3 algorithms are not supported in AndroidKeyStore public void testSecp224r1Sha3_512 () throws Exception { testVerification("ecdsa_secp224r1_sha3_512_test.json", "ECDSA", Format.ASN, true); } @Test + @Ignore // SHA3 algorithms are not supported in AndroidKeyStore public void testSecp256r1Sha3_256 () throws Exception { testVerification("ecdsa_secp256r1_sha3_256_test.json", "ECDSA", Format.ASN, true); } @Test + @Ignore // SHA3 algorithms are not supported in AndroidKeyStore public void testSecp256r1Sha3_512 () throws Exception { testVerification("ecdsa_secp256r1_sha3_512_test.json", "ECDSA", Format.ASN, true); } @Test + @Ignore // Secp256k1 curve and SHA3 algorithms not supported in AndroidKeystore public void testSecp256k1Sha3_256 () throws Exception { testVerification("ecdsa_secp256k1_sha3_256_test.json", "ECDSA", Format.ASN, true); } @Test + @Ignore // Secp256k1 curve and SHA3 algorithms not supported in AndroidKeystore public void testSecp256k1Sha3_512 () throws Exception { testVerification("ecdsa_secp256k1_sha3_512_test.json", "ECDSA", Format.ASN, true); } @Test + @Ignore // SHA3 algorithms are not supported in AndroidKeyStore public void testSecp384r1Sha3_384 () throws Exception { testVerification("ecdsa_secp384r1_sha3_384_test.json", "ECDSA", Format.ASN, true); } @Test + @Ignore // SHA3 algorithms are not supported in AndroidKeyStore public void testSecp384r1Sha3_512 () throws Exception { testVerification("ecdsa_secp384r1_sha3_512_test.json", "ECDSA", Format.ASN, true); } @Test + @Ignore // SHA3 algorithms are not supported in AndroidKeyStore public void testSecp521r1Sha3_512 () throws Exception { testVerification("ecdsa_secp521r1_sha3_512_test.json", "ECDSA", Format.ASN, true); } // jdk11 adds P1363 encoded signatures. @Test + @Ignore // P1363 encoding not supported in AndroidKeyStore public void testSecp224r1Sha224inP1363Format() throws Exception { testVerification("ecdsa_secp224r1_sha224_p1363_test.json", "ECDSA", Format.P1363, true); } @Test + @Ignore // P1363 encoding not supported in AndroidKeyStore public void testSecp224r1Sha256inP1363Format() throws Exception { testVerification("ecdsa_secp224r1_sha256_p1363_test.json", "ECDSA", Format.P1363, true); } @Test + @Ignore // P1363 encoding not supported in AndroidKeyStore public void testSecp224r1Sha512inP1363Format() throws Exception { testVerification("ecdsa_secp224r1_sha512_p1363_test.json", "ECDSA", Format.P1363, true); } @Test + @Ignore // P1363 encoding not supported in AndroidKeyStore public void testSecp256r1Sha256inP1363Format() throws Exception { testVerification("ecdsa_secp256r1_sha256_p1363_test.json", "ECDSA", Format.P1363, true); } @Test + @Ignore // P1363 encoding not supported in AndroidKeyStore public void testSecp256r1Sha512inP1363Format() throws Exception { testVerification("ecdsa_secp256r1_sha512_p1363_test.json", "ECDSA", Format.P1363, true); } @Test + @Ignore // P1363 encoding not supported in AndroidKeyStore public void testSecp384r1Sha384inP1363Format() throws Exception { testVerification("ecdsa_secp384r1_sha384_p1363_test.json", "ECDSA", Format.P1363, true); } @Test + @Ignore // P1363 encoding not supported in AndroidKeyStore public void testSecp384r1Sha512inP1363Format() throws Exception { testVerification("ecdsa_secp384r1_sha512_p1363_test.json", "ECDSA", Format.P1363, true); } @Test + @Ignore // P1363 encoding not supported in AndroidKeyStore public void testSecp521r1Sha512inP1363Format() throws Exception { testVerification("ecdsa_secp521r1_sha512_p1363_test.json", "ECDSA", Format.P1363, true); } @Test + @Ignore // Secp256k1 curve not supported in AndroidKeystore public void testSecp256k1Sha256inP1363Format() throws Exception { testVerification("ecdsa_secp256k1_sha256_p1363_test.json", "ECDSA", Format.P1363, true); } @Test + @Ignore // Secp256k1 curve not supported in AndroidKeystore public void testSecp256k1Sha512inP1363Format() throws Exception { testVerification("ecdsa_secp256k1_sha512_p1363_test.json", "ECDSA", Format.P1363, true); } - @NoPresubmitTest( - providers = {ProviderType.OPENJDK}, - bugs = {"b/117643131"} - ) @Test + @Ignore // Brainpool curves are not supported in AndroidKeyStore public void testBrainpoolP224r1Sha224inP1363Format() throws Exception { testVerification("ecdsa_brainpoolP224r1_sha224_p1363_test.json", "ECDSA", Format.P1363, true); } @Test + @Ignore // Brainpool curves are not supported in AndroidKeyStore public void testBrainpoolP256r1Sha256inP1363Format() throws Exception { testVerification("ecdsa_brainpoolP256r1_sha256_p1363_test.json", "ECDSA", Format.P1363, true); } @Test + @Ignore // Brainpool curves are not supported in AndroidKeyStore public void testBrainpoolP320r1Sha384inP1363Format() throws Exception { testVerification("ecdsa_brainpoolP320r1_sha384_p1363_test.json", "ECDSA", Format.P1363, true); } @Test + @Ignore // Brainpool curves are not supported in AndroidKeyStore public void testBrainpoolP384r1Sha384inP1363Format() throws Exception { testVerification("ecdsa_brainpoolP384r1_sha384_p1363_test.json", "ECDSA", Format.P1363, true); } @Test + @Ignore // Brainpool curves are not supported in AndroidKeyStore public void testBrainpoolP512r1Sha512inP1363Format() throws Exception { testVerification("ecdsa_brainpoolP512r1_sha512_p1363_test.json", "ECDSA", Format.P1363, true); } // 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 { @@ -822,21 +797,25 @@ public class JsonSignatureTest { // RSA signatures with truncated hashes. Tests may be skipped if the provider // does not support the hash. @Test + @Ignore // SHA-512/224 algorithm not supported in AndrdoiKeyStore public void testRsaSignatures2048sha512_224() throws Exception { testVerification("rsa_signature_2048_sha512_224_test.json", "RSA", Format.RAW, true); } @Test + @Ignore // SHA-512/256 algorithm not supported in AndrdoiKeyStore public void testRsaSignatures2048sha512_256() throws Exception { testVerification("rsa_signature_2048_sha512_256_test.json", "RSA", Format.RAW, true); } @Test + @Ignore // SHA-512/256 algorithm not supported in AndrdoiKeyStore public void testRsaSignatures3072sha512_256() throws Exception { testVerification("rsa_signature_3072_sha512_256_test.json", "RSA", Format.RAW, true); } @Test + @Ignore // SHA-512/256 algorithm not supported in AndrdoiKeyStore public void testRsaSignatures4096sha512_256() throws Exception { testVerification("rsa_signature_4096_sha512_256_test.json", "RSA", Format.RAW, true); } @@ -844,48 +823,50 @@ public class JsonSignatureTest { // RSA signatures with SHA-3. Not every provider supports SHA-3. Hence the tests // may be skipped. @Test + @Ignore // SHA3 algorithms are not supported in AndroidKeyStore public void testRsaSignature2048sha3_224() throws Exception { testVerification("rsa_signature_2048_sha3_224_test.json", "RSA", Format.RAW, true); } @Test + @Ignore // SHA3 algorithms are not supported in AndroidKeyStore public void testRsaSignatures2048sha3_256() throws Exception { testVerification("rsa_signature_2048_sha3_256_test.json", "RSA", Format.RAW, true); } @Test + @Ignore // SHA3 algorithms are not supported in AndroidKeyStore public void testRsaSignatures2048sha3_512() throws Exception { testVerification("rsa_signature_2048_sha3_512_test.json", "RSA", Format.RAW, true); } @Test + @Ignore // SHA3 algorithms are not supported in AndroidKeyStore public void testRsaSignatures3072sha3_256() throws Exception { testVerification("rsa_signature_3072_sha3_256_test.json", "RSA", Format.RAW, true); } @Test + @Ignore // SHA3 algorithms are not supported in AndroidKeyStore public void testRsaSignatures3072sha3_384() throws Exception { testVerification("rsa_signature_3072_sha3_384_test.json", "RSA", Format.RAW, true); } @Test + @Ignore // SHA3 algorithms are not supported in AndroidKeyStore public void testRsaSignatures3072sha3_512() throws Exception { testVerification("rsa_signature_3072_sha3_512_test.json", "RSA", Format.RAW, true); } // EdDSA - @NoPresubmitTest( - providers = {ProviderType.BOUNCY_CASTLE}, - bugs = {"https://github.com/bcgit/bc-java/issues/508"}) @Test + @Ignore // Ed25519 algorithm not supported in AndroidKeyStore public void testEd25519Verify() throws Exception { testVerification("eddsa_test.json", "ED25519", Format.RAW, true); } - @NoPresubmitTest( - providers = {ProviderType.BOUNCY_CASTLE}, - bugs = {"https://github.com/bcgit/bc-java/issues/508"}) @Test + @Ignore // Ed448 algorithm not supported in AndroidKeyStore public void testEd448Verify() throws Exception { testVerification("ed448_test.json", "ED448", Format.RAW, true); } @@ -893,10 +874,8 @@ public class JsonSignatureTest { // DSA // Two signature encodings for DSA are tested below: ASN encoded signatures // and P1363 encoded signatures. - @ExcludedTest( - providers = {ProviderType.CONSCRYPT}, - comment = "Conscrypt does not support DSA.") @Test + @Ignore // DSA algorithm not supported in AndroidKeyStore public void testDsa2048Sha224() throws Exception { testVerification("dsa_2048_224_sha224_test.json", "DSA", Format.ASN, true); } @@ -904,63 +883,48 @@ public class JsonSignatureTest { // NIST allows 2048-bit DSA keys with either a 224-bit q or a 256-bit q. // In both cases the security level is 112-bit. // Jdk generates DSA keys with a 224-bit q (unless specified). - @ExcludedTest( - providers = {ProviderType.CONSCRYPT}, - comment = "Conscrypt does not support DSA.") @Test + @Ignore // DSA algorithm not supported in AndroidKeyStore public void testDsa2048JdkSha256() throws Exception { testVerification("dsa_2048_224_sha256_test.json", "DSA", Format.ASN, true); } // OpenSSL generates DSA keys with a 256-bit q (unless specified). - @ExcludedTest( - providers = {ProviderType.CONSCRYPT}, - comment = "Conscrypt does not support DSA.") @Test + @Ignore // DSA algorithm not supported in AndroidKeyStore public void testDsa2048Sha256() throws Exception { testVerification("dsa_2048_256_sha256_test.json", "DSA", Format.ASN, true); } - @ExcludedTest( - providers = {ProviderType.CONSCRYPT}, - comment = "Conscrypt does not support DSA.") @Test + @Ignore // DSA algorithm not supported in AndroidKeyStore public void testDsa3072Sha256() throws Exception { testVerification("dsa_3072_256_sha256_test.json", "DSA", Format.ASN, true); } // DSA tests using P1363 formated signatures. - @ExcludedTest( - providers = {ProviderType.CONSCRYPT}, - comment = "Conscrypt does not support DSA.") @Test + @Ignore // DSA algorithm not supported in AndroidKeyStore public void testDsa2048Sha224inP1363Format() throws Exception { testVerification("dsa_2048_224_sha224_p1363_test.json", "DSA", Format.P1363, true); } - @ExcludedTest( - providers = {ProviderType.CONSCRYPT}, - comment = "Conscrypt does not support DSA.") @Test + @Ignore // DSA algorithm not supported in AndroidKeyStore public void testDsa2048JdkSha256inP1363Format() throws Exception { testVerification("dsa_2048_224_sha256_p1363_test.json", "DSA", Format.P1363, true); } - @ExcludedTest( - providers = {ProviderType.CONSCRYPT}, - comment = "Conscrypt does not support DSA.") @Test + @Ignore // DSA algorithm not supported in AndroidKeyStore public void testDsa2048Sha256inP1363Format() throws Exception { testVerification("dsa_2048_256_sha256_p1363_test.json", "DSA", Format.P1363, true); } - @ExcludedTest( - providers = {ProviderType.CONSCRYPT}, - comment = "Conscrypt does not support DSA.") @Test + @Ignore // DSA algorithm not supported in AndroidKeyStore public void testDsa3072Sha256inP1363Format() throws Exception { testVerification("dsa_3072_256_sha256_p1363_test.json", "DSA", Format.P1363, true); } - } 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 34b6115..3a42761 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/MacTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/MacTest.java @@ -15,18 +15,20 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; -import com.google.security.wycheproof.WycheproofRunner.ProviderType; -import com.google.security.wycheproof.WycheproofRunner.SlowTest; import java.nio.ByteBuffer; import java.security.GeneralSecurityException; import java.security.Key; +import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; +import org.junit.After; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import org.junit.Ignore; +import android.security.keystore.KeyProtection; +import android.security.keystore.KeyProperties; +import android.keystore.cts.util.KeyStoreUtil; /** * Tests for MACs. @@ -35,8 +37,23 @@ import org.junit.runners.JUnit4; * with known results are necessary. So far only simple test vectors for long messages are * available. */ -@RunWith(JUnit4.class) public class MacTest { + private static final String EXPECTED_PROVIDER_NAME = TestUtil.EXPECTED_CRYPTO_OP_PROVIDER_NAME; + private static final String KEY_ALIAS_1 = "TestKey"; + + @After + public void tearDown() throws Exception { + KeyStoreUtil.cleanUpKeyStore(); + } + + 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) + .setIsStrongBoxBacked(isStrongBox).build()); + return keyStore.getKey(KEY_ALIAS_1, null); + } /** * Computes the maximum of an array with at least one element. @@ -79,7 +96,7 @@ public class MacTest { */ private void testUpdateWithChunks(String algorithm, Key key, byte[] data, int... chunkSizes) throws Exception { - Mac mac = Mac.getInstance(algorithm); + Mac mac = Mac.getInstance(algorithm, EXPECTED_PROVIDER_NAME); // First evaluation: compute MAC in one piece. int totalLength = 0; @@ -177,16 +194,18 @@ 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); + Mac.getInstance(algorithm, EXPECTED_PROVIDER_NAME); } catch (NoSuchAlgorithmException ex) { - System.out.println("Algorithm " + algorithm + " is not supported. Skipping test."); - return; + fail("Algorithm " + algorithm + " is not supported."); } byte[] key = new byte[keySize]; SecureRandom rand = new SecureRandom(); rand.nextBytes(key); - testUpdate(algorithm, new SecretKeySpec(key, algorithm)); + testUpdate(algorithm, getKeyStoreSecretKey(key, algorithm, isStrongBox)); } @Test @@ -205,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); } @@ -215,21 +241,25 @@ public class MacTest { } @Test + @Ignore // HmacSha3 algorithms are not supported in AndroidKeyStore public void testHmacSha3_224() throws Exception { testMac("HMACSHA3-224", 28); } @Test + @Ignore // HmacSha3 algorithms are not supported in AndroidKeyStore public void testHmacSha3_256() throws Exception { testMac("HMACSHA3-256", 32); } @Test + @Ignore // HmacSha3 algorithms are not supported in AndroidKeyStore public void testHmacSha3_384() throws Exception { testMac("HMACSHA3-384", 48); } @Test + @Ignore // HmacSha3 algorithms are not supported in AndroidKeyStore public void testHmacSha3_512() throws Exception { testMac("HMACSHA3-512", 64); } @@ -246,7 +276,7 @@ public class MacTest { */ public byte[] macRepeatedMessage(String algorithm, Key key, byte[] message, long repetitions) throws Exception { - Mac mac = Mac.getInstance(algorithm); + Mac mac = Mac.getInstance(algorithm, EXPECTED_PROVIDER_NAME); mac.init(key); // If the message is short then it is more efficient to collect multiple copies // of the message in one chunk and call update with the larger chunk. @@ -280,29 +310,26 @@ 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 = new SecretKeySpec(TestUtil.hexToBytes(keyhex), algorithm); + Key key = getKeyStoreSecretKey(TestUtil.hexToBytes(keyhex), algorithm, isStrongBox); byte[] bytes = message.getBytes(UTF_8); byte[] mac = null; try { mac = macRepeatedMessage(algorithm, key, bytes, repetitions); } catch (NoSuchAlgorithmException ex) { - System.out.println("Algorithm " + algorithm + " is not supported. Skipping test."); - return; + fail("Algorithm " + algorithm + " is not supported."); } String hexmac = TestUtil.bytesToHex(mac); assertEquals(expected, hexmac); } - @SlowTest( - providers = { - ProviderType.OPENJDK, - ProviderType.BOUNCY_CASTLE, - ProviderType.SPONGY_CASTLE, - ProviderType.CONSCRYPT - }) @Test public void testLongMacSha1() throws Exception { testLongMac( @@ -319,36 +346,33 @@ public class MacTest { "d7f4c387f2237ea119fcc27cd7520fc5132b6230"); } - @SlowTest( - providers = { - ProviderType.OPENJDK, - ProviderType.BOUNCY_CASTLE, - ProviderType.SPONGY_CASTLE, - ProviderType.CONSCRYPT - }) @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); } - @SlowTest( - providers = { - ProviderType.OPENJDK, - ProviderType.BOUNCY_CASTLE, - ProviderType.SPONGY_CASTLE, - ProviderType.CONSCRYPT - }) @Test public void testLongMacSha384() throws Exception { testLongMac( @@ -369,13 +393,6 @@ public class MacTest { + "a477e6a84d159d8b7a3daaa89c4f2372"); } - @SlowTest( - providers = { - ProviderType.OPENJDK, - ProviderType.BOUNCY_CASTLE, - ProviderType.SPONGY_CASTLE, - ProviderType.CONSCRYPT - }) @Test public void testLongMacSha512() throws Exception { testLongMac( 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 5f82420..ed291e0 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/RsaEncryptionTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/RsaEncryptionTest.java @@ -18,25 +18,39 @@ import static org.junit.Assert.fail; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import java.math.BigInteger; +import java.security.KeyStore; +import java.security.KeyPair; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; +import java.security.PublicKey; import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.security.spec.RSAPublicKeySpec; import java.util.Set; import java.util.TreeSet; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; +import org.junit.After; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import android.security.keystore.KeyProtection; +import android.security.keystore.KeyProperties; +import android.keystore.cts.util.KeyStoreUtil; /** * RSA encryption tests * * @author bleichen@google.com (Daniel Bleichenbacher) */ -@RunWith(JUnit4.class) public class RsaEncryptionTest { + private static final String EXPECTED_PROVIDER_NAME = TestUtil.EXPECTED_CRYPTO_OP_PROVIDER_NAME; + private static final String KEY_ALIAS_SIG = "SigningKey"; + + @After + public void tearDown() throws Exception { + KeyStoreUtil.cleanUpKeyStore(); + } /** * Providers that implement RSA with PKCS1Padding but not OAEP are outdated and should be avoided @@ -46,15 +60,11 @@ public class RsaEncryptionTest { */ @Test public void testOutdatedProvider() throws Exception { + Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding", EXPECTED_PROVIDER_NAME); try { - Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding"); - try { - Cipher.getInstance("RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING"); - } catch (NoSuchPaddingException | NoSuchAlgorithmException ex) { - fail("Provider " + c.getProvider().getName() + " is outdated and should not be used."); - } + Cipher.getInstance("RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING", EXPECTED_PROVIDER_NAME); } catch (NoSuchPaddingException | NoSuchAlgorithmException ex) { - System.out.println("RSA/ECB/PKCS1Padding is not implemented"); + fail("Provider " + c.getProvider().getName() + " is outdated and should not be used."); } } @@ -67,12 +77,23 @@ 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()); + BigInteger modulus = new BigInteger(TestUtil.hexToBytes(object.get("n").getAsString())); + BigInteger exponent = new BigInteger(TestUtil.hexToBytes(object.get("e").getAsString())); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); - return kf.generatePrivate(keySpec); + PrivateKey intermediateKey = kf.generatePrivate(keySpec); + PublicKey pubKey = kf.generatePublic(new RSAPublicKeySpec(modulus, exponent)); + 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); } /** Convenience method to get a byte array from a JsonObject */ @@ -124,16 +145,11 @@ public class RsaEncryptionTest { */ @SuppressWarnings("InsecureCryptoUsage") public void testDecryption(String filename) throws Exception { - final String expectedSchema = "rsaes_pkcs1_decrypt_schema.json"; - JsonObject test = JsonUtil.getTestVectors(filename); - String schema = test.get("schema").getAsString(); - if (!schema.equals(expectedSchema)) { - System.out.println( - "Expecting test vectors with schema " - + expectedSchema - + " found vectors with schema " - + schema); - } + 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 // exception thrown in the case of an invalid padding. @@ -144,10 +160,10 @@ public class RsaEncryptionTest { Set<String> exceptions = new TreeSet<String>(); int errors = 0; - Cipher decrypter = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + 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(); @@ -178,31 +194,18 @@ public class RsaEncryptionTest { } } if (decrypted == null && result.equals("valid")) { - System.out.printf( - "Valid ciphertext not decrypted. filename:%s tcId:%d ct:%s cause:%s\n", - filename, tcid, ciphertextHex, exception); errors++; } else if (decrypted != null) { String decryptedHex = TestUtil.bytesToHex(decrypted); if (result.equals("invalid")) { - System.out.printf( - "Invalid ciphertext decrypted. filename:%s tcId:%d expected:%s decrypted:%s\n", - filename, tcid, messageHex, decryptedHex); errors++; } else if (!decryptedHex.equals(messageHex)) { - System.out.printf( - "Incorrect decryption. filename:%s tcId:%d expected:%s decrypted:%s\n", - filename, tcid, messageHex, decryptedHex); errors++; } } } } if (exceptions.size() != 1) { - System.out.println("Exceptions for RSA/ECB/PKCS1Padding"); - for (String s : exceptions) { - System.out.println(s); - } fail("Exceptions leak information about the padding"); } assertEquals(0, errors); @@ -212,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 201dfbc..5eb47e5 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/RsaOaepTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/RsaOaepTest.java @@ -15,31 +15,63 @@ 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; +import java.math.BigInteger; import java.security.AlgorithmParameters; import java.security.GeneralSecurityException; import java.security.KeyFactory; +import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.MGF1ParameterSpec; import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.RSAPublicKeySpec; import java.security.spec.X509EncodedKeySpec; import javax.crypto.Cipher; import javax.crypto.spec.OAEPParameterSpec; import javax.crypto.spec.PSource; +import org.junit.After; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import org.junit.Ignore; +import android.security.keystore.KeyProtection; +import android.security.keystore.KeyProperties; +import android.keystore.cts.util.KeyStoreUtil; +import android.text.TextUtils; +import android.util.Log; /** * Checks implementations of RSA-OAEP. */ -@RunWith(JUnit4.class) public class RsaOaepTest { + private static final String TAG = "RsaOaepTest"; + private static final String EXPECTED_PROVIDER_NAME = TestUtil.EXPECTED_CRYPTO_OP_PROVIDER_NAME; + private static final String KEY_ALIAS_1 = "TestKey"; + + @After + public void tearDown() throws Exception { + KeyStoreUtil.cleanUpKeyStore(); + } + + private static PrivateKey saveKeyPairToKeystoreAndReturnPrivateKey(PublicKey pubKey, + 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 | + KeyProperties.PURPOSE_ENCRYPT | + KeyProperties.PURPOSE_DECRYPT) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1, + KeyProperties.ENCRYPTION_PADDING_RSA_OAEP) + .setDigests(digest, mgfDigest) + .setIsStrongBoxBacked(isStrongBox) + .build()) + .getKey(KEY_ALIAS_1, null); + } /** * A list of algorithm names for RSA-OAEP. @@ -59,19 +91,19 @@ public class RsaOaepTest { protected static void printParameters(AlgorithmParameterSpec params) { if (params instanceof OAEPParameterSpec) { OAEPParameterSpec oaepParams = (OAEPParameterSpec) params; - System.out.println("OAEPParameterSpec"); - System.out.println("digestAlgorithm:" + oaepParams.getDigestAlgorithm()); - System.out.println("mgfAlgorithm:" + oaepParams.getMGFAlgorithm()); + Log.d(TAG, "OAEPParameterSpec"); + Log.d(TAG, "digestAlgorithm:" + oaepParams.getDigestAlgorithm()); + Log.d(TAG, "mgfAlgorithm:" + oaepParams.getMGFAlgorithm()); printParameters(oaepParams.getMGFParameters()); } else if (params instanceof MGF1ParameterSpec) { MGF1ParameterSpec mgf1Params = (MGF1ParameterSpec) params; - System.out.println("MGF1ParameterSpec"); - System.out.println("digestAlgorithm:" + mgf1Params.getDigestAlgorithm()); + Log.d(TAG, "MGF1ParameterSpec"); + Log.d(TAG, "digestAlgorithm:" + mgf1Params.getDigestAlgorithm()); } else { - System.out.println(params.toString()); - } + Log.d(TAG, params.toString()); + } } - + /** * This is not a real test. The JCE algorithm names only specify one hash algorithm. But OAEP * uses two hases. One hash algorithm is used to hash the labels. The other hash algorithm is @@ -79,7 +111,7 @@ public class RsaOaepTest { * * <p>Different provider use different default values for the hash function that is not specified * in the algorithm name. Jdk uses mgfsha1 as default. BouncyCastle and Conscrypt use the same - * hash for labels and mgf. Every provider allows to specify all the parameters using + * hash for labels and mgf. Every provider allows to specify all the parameters using * an OAEPParameterSpec instance. * * <p>This test simply tries a number of algorithm names for RSA-OAEP and prints the OAEP @@ -104,15 +136,11 @@ public class RsaOaepTest { X509EncodedKeySpec x509keySpec = new X509EncodedKeySpec(TestUtil.hexToBytes(pubKey)); PublicKey key = kf.generatePublic(x509keySpec); for (String oaepName : OaepAlgorithmNames) { - try { - Cipher c = Cipher.getInstance(oaepName); + Cipher c = Cipher.getInstance(oaepName, EXPECTED_PROVIDER_NAME); c.init(Cipher.ENCRYPT_MODE, key); - System.out.println("Algorithm " + oaepName + " uses the following defaults"); + Log.d(TAG, "Algorithm " + oaepName + " uses the following defaults"); AlgorithmParameters params = c.getParameters(); printParameters(params.getParameterSpec(OAEPParameterSpec.class)); - } catch (NoSuchAlgorithmException ex) { - continue; - } } } @@ -135,12 +163,26 @@ 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")); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); - return kf.generatePrivate(keySpec); + PrivateKey intermediateKey = kf.generatePrivate(keySpec); + 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)); + String digest = getString(object, "sha"); + String mgfDigest = getString(object, "mgfSha"); + int keysize = object.get("keysize").getAsInt(); + if (!KeyStoreUtil.isSupportedDigest(digest, isStrongBox) + || !KeyStoreUtil.isSupportedMgfDigest(mgfDigest, isStrongBox) + || !KeyStoreUtil.isSupportedRsaKeySize(keysize, isStrongBox)) { + throw new UnsupportedKeyParametersException(); + } + return saveKeyPairToKeystoreAndReturnPrivateKey(pubKey, intermediateKey, digest, mgfDigest, + isStrongBox); } protected static String getOaepAlgorithmName(JsonObject group) throws Exception { @@ -150,18 +192,24 @@ public class RsaOaepTest { } protected static OAEPParameterSpec getOaepParameters(JsonObject group, - JsonObject test) throws Exception { + JsonObject test) throws Exception { 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")) { - p = new PSource.PSpecified(getBytes(test, "label")); + if (test.has("label") && !TextUtils.isEmpty(getString(test, "label"))) { + // p = new PSource.PSpecified(getBytes(test, "label")); + throw new UnsupportedKeyParametersException(); } - return new OAEPParameterSpec(sha, mgf, new MGF1ParameterSpec(mgfSha), p); + return new OAEPParameterSpec(sha, mgf, new MGF1ParameterSpec(mgfSha), p); } - /** + /** * Tests the signature verification with test vectors in a given JSON file. * * <p> Example format for test vectors @@ -171,7 +219,7 @@ public class RsaOaepTest { * ... * "testGroups" : [ * { - * "d" : "...", + * "d" : "...", * "e" : "10001", * "n" : "...", * "keysize" : 2048, @@ -204,23 +252,32 @@ 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 { - JsonObject test = JsonUtil.getTestVectors(filename); + if (isStrongBox) { + KeyStoreUtil.assumeStrongBox(); + } + JsonObject test = JsonUtil.getTestVectors(this.getClass(), filename); // Compares the expected and actual JSON schema of the test vector file. // Mismatched JSON schemas will likely lead to a test failure. String generatorVersion = getString(test, "generatorVersion"); String expectedSchema = "rsaes_oaep_decrypt_schema.json"; String actualSchema = getString(test, "schema"); - if (!expectedSchema.equals(actualSchema)) { - System.out.println( + assertTrue( "Expecting test vectors with schema " + expectedSchema + " found vectors with schema " + actualSchema + " generatorVersion:" - + generatorVersion); - } + + generatorVersion, + expectedSchema.equals(actualSchema)); int numTests = test.get("numberOfTests").getAsInt(); int cntTests = 0; @@ -228,38 +285,45 @@ public class RsaOaepTest { int skippedKeys = 0; for (JsonElement g : test.getAsJsonArray("testGroups")) { JsonObject group = g.getAsJsonObject(); - PrivateKey key; + PrivateKey key = null; try { - key = getPrivateKey(group); - } catch (GeneralSecurityException ex) { + key = getPrivateKey(group, isStrongBox); + } catch (UnsupportedKeyParametersException e) { skippedKeys++; if (!allowSkippingKeys) { - System.out.printf("Key generation throws:%s\n", ex.toString()); + throw e; + } else { + continue; } - continue; } String algorithm = getOaepAlgorithmName(group); - Cipher decrypter = Cipher.getInstance(algorithm); + Cipher decrypter = Cipher.getInstance(algorithm, EXPECTED_PROVIDER_NAME); for (JsonElement t : group.getAsJsonArray("tests")) { cntTests++; 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"); - decrypter.init(Cipher.DECRYPT_MODE, key, params); byte[] decrypted = null; try { + decrypter.init(Cipher.DECRYPT_MODE, key, params); decrypted = decrypter.doFinal(ciphertext); } catch (GeneralSecurityException ex) { decrypted = null; } catch (Exception ex) { // Other exceptions (i.e. unchecked exceptions) are considered as error // since a third party should never be able to cause such exceptions. - System.out.printf("Decryption throws %s. filename:%s tcId:%d ct:%s\n", - ex.toString(), filename, tcid, ciphertextHex); + Log.e(TAG, String.format("Decryption throws %s. filename:%s tcId:%d ct:%s\n", + ex.toString(), filename, tcid, ciphertextHex)); decrypted = null; // TODO(bleichen): BouncyCastle throws some non-conforming exceptions. // For the moment we do not count this as a problem to avoid that @@ -267,21 +331,21 @@ public class RsaOaepTest { // errors++; } if (decrypted == null && result.equals("valid")) { - System.out.printf( - "Valid ciphertext not decrypted. filename:%s tcId:%d ct:%s\n", - filename, tcid, ciphertextHex); + Log.e(TAG, + String.format("Valid ciphertext not decrypted. filename:%s tcId:%d ct:%s\n", + filename, tcid, ciphertextHex)); errors++; } else if (decrypted != null) { String decryptedHex = TestUtil.bytesToHex(decrypted); if (result.equals("invalid")) { - System.out.printf( - "Invalid ciphertext decrypted. filename:%s tcId:%d expected:%s decrypted:%s\n", - filename, tcid, messageHex, decryptedHex); + Log.e(TAG, + String.format("Invalid ciphertext decrypted. filename:%s tcId:%d expected:%s" + + " decrypted:%s\n", filename, tcid, messageHex, decryptedHex)); errors++; } else if (!decryptedHex.equals(messageHex)) { - System.out.printf( - "Incorrect decryption. filename:%s tcId:%d expected:%s decrypted:%s\n", - filename, tcid, messageHex, decryptedHex); + Log.e(TAG, + String.format("Incorrect decryption. filename:%s tcId:%d expected:%s" + + " decrypted:%s\n", filename, tcid, messageHex, decryptedHex)); errors++; } } @@ -289,7 +353,7 @@ public class RsaOaepTest { } assertEquals(0, errors); if (skippedKeys > 0) { - System.out.println("RSAES-OAEP: file:" + filename + " skipped key:" + skippedKeys); + Log.d(TAG, "RSAES-OAEP: file:" + filename + " skipped key:" + skippedKeys); assertTrue(allowSkippingKeys); } else { assertEquals(numTests, cntTests); @@ -302,6 +366,11 @@ public class RsaOaepTest { } @Test + 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); } @@ -313,12 +382,20 @@ public class RsaOaepTest { @Test 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 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 @@ -383,8 +460,11 @@ public class RsaOaepTest { @Test public void testRsaOaepMisc() throws Exception { - testOaep("rsa_oaep_misc_test.json", false); + testOaep("rsa_oaep_misc_test.json", true); + } + @Test + public void testRsaOaepMisc_StrongBox() throws Exception { + testOaep("rsa_oaep_misc_test.json", true, true); } - } diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/RsaPssTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/RsaPssTest.java index 8868c23..b9a0a72 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/RsaPssTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/RsaPssTest.java @@ -15,11 +15,10 @@ package com.google.security.wycheproof; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.security.wycheproof.WycheproofRunner.NoPresubmitTest; -import com.google.security.wycheproof.WycheproofRunner.ProviderType; import java.lang.reflect.Constructor; import java.math.BigInteger; import java.security.AlgorithmParameters; @@ -27,6 +26,7 @@ import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; +import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.Signature; @@ -38,21 +38,23 @@ import java.security.spec.RSAKeyGenParameterSpec; import java.security.spec.X509EncodedKeySpec; import java.util.HashSet; import java.util.Set; +import org.junit.After; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import org.junit.Ignore; +import android.util.Log; /** * Tests for RSA-PSS. */ -@RunWith(JUnit4.class) public class RsaPssTest { + private static final String TAG = "RsaPssTest"; + private static final String EXPECTED_PROVIDER_NAME = TestUtil.EXPECTED_CRYPTO_OP_PROVIDER_NAME; /** * Returns an AlgorithmParameterSpec for generating a RSASSA-PSS key, * which include the PSSParameters. * Requires jdk11. - * + * * @param keySizeInBits the size of the modulus in bits. * @param sha the name of the hash function for hashing the input (e.g. "SHA-256") * @param mgf the name of the mask generating function (typically "MGF1") @@ -85,55 +87,37 @@ public class RsaPssTest { /** * Tries encoding and decoding of RSASSA-PSS keys generated with RSASSA-PSS. - * + * * RSASSA-PSS keys contain the PSSParameters, hence their encodings are * somewhat different than plain RSA keys. */ - @NoPresubmitTest( - providers = {ProviderType.OPENJDK}, - bugs = {"b/120406853"} - ) @Test + @Ignore //TODO Reverify after b/215319125 is fixed. public void testEncodeDecodePublic() throws Exception { int keySizeInBits = 2048; - PublicKey pub; - try { - KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSASSA-PSS"); - keyGen.initialize(keySizeInBits); - KeyPair keypair = keyGen.genKeyPair(); - pub = keypair.getPublic(); - } catch (NoSuchAlgorithmException ex) { - System.out.println("Key generation for RSASSA-PSS is not supported."); - return; - } + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSASSA-PSS", EXPECTED_PROVIDER_NAME); + keyGen.initialize(keySizeInBits); + KeyPair keypair = keyGen.genKeyPair(); + PublicKey pub = keypair.getPublic(); byte[] encoded = pub.getEncoded(); assertEquals( "The test assumes that the public key is in X.509 format", "X.509", pub.getFormat()); - System.out.println("Generated RSA-PSS key"); - System.out.println(TestUtil.bytesToHex(encoded)); KeyFactory kf = KeyFactory.getInstance("RSASSA-PSS"); X509EncodedKeySpec spec = new X509EncodedKeySpec(encoded); kf.generatePublic(spec); - + // Tries to generate another pair or keys. This time the generator is given an // RSAKeyGenParameterSpec containing the key size an the PSS parameters. String sha = "SHA-256"; String mgf = "MGF1"; int saltLength = 20; - try { - RSAKeyGenParameterSpec params = - getPssAlgorithmParameters(keySizeInBits, sha, mgf, sha, saltLength); - KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSASSA-PSS"); - keyGen.initialize(params); - KeyPair keypair = keyGen.genKeyPair(); - pub = keypair.getPublic(); - } catch (NoSuchAlgorithmException | NoSuchMethodException ex) { - System.out.println("Key generation for RSASSA-PSS is not supported."); - return; - } + RSAKeyGenParameterSpec params = + getPssAlgorithmParameters(keySizeInBits, sha, mgf, sha, saltLength); + KeyPairGenerator keyGen1 = KeyPairGenerator.getInstance("RSASSA-PSS", EXPECTED_PROVIDER_NAME); + keyGen1.initialize(params); + KeyPair keypair1 = keyGen1.genKeyPair(); + pub = keypair1.getPublic(); byte[] encoded2 = pub.getEncoded(); - System.out.println("Generated RSA-PSS key with PSS parameters"); - System.out.println(TestUtil.bytesToHex(encoded2)); X509EncodedKeySpec spec2 = new X509EncodedKeySpec(encoded2); kf.generatePublic(spec2); } @@ -171,14 +155,8 @@ public class RsaPssTest { kf = KeyFactory.getInstance("RSA"); X509EncodedKeySpec x509keySpec = new X509EncodedKeySpec(TestUtil.hexToBytes(pubKey)); PublicKey key = kf.generatePublic(x509keySpec); - Signature verifier; - try { - verifier = Signature.getInstance(algorithm); - verifier.initVerify(key); - } catch (NoSuchAlgorithmException ex) { - System.out.println("Unsupported algorithm:" + algorithm); - return; - } + Signature verifier = Signature.getInstance(algorithm, EXPECTED_PROVIDER_NAME); + verifier.initVerify(key); AlgorithmParameters params = verifier.getParameters(); if (params == null) { // No defaults are specified. This is a good choice since this avoid @@ -234,6 +212,7 @@ public class RsaPssTest { * PSSParameters explicitly, and does not default to weak behaviour. */ @Test + @Ignore //TODO Reverify after b/215319125 is fixed. public void testDefaults() throws Exception { testDefaultForAlgorithm("SHA1withRSAandMGF1", "SHA-1", "MGF1", "SHA-1", 20, 1); testDefaultForAlgorithm("SHA224withRSAandMGF1", "SHA-224", "MGF1", "SHA-224", 28, 1); @@ -249,7 +228,7 @@ public class RsaPssTest { testDefaultForAlgorithm("SHA3-384withRSAandMGF1", "SHA3-384", "MGF1", "SHA3-384", 48, 1); testDefaultForAlgorithm("SHA3-512withRSAandMGF1", "SHA3-512", "MGF1", "SHA3-512", 64, 1); } - + /** Convenience mehtod to get a String from a JsonObject */ protected static String getString(JsonObject object, String name) throws Exception { return object.get(name).getAsString(); @@ -265,7 +244,7 @@ public class RsaPssTest { * Oracle previously specified that algorithm names for RSA-PSS are strings like * "SHA256WITHRSAandMGF1". * See http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html - * These algorithm names fail to specify the hash function for the MGF. A cleaner solution + * These algorithm names fail to specify the hash function for the MGF. A cleaner solution * in jdk11 is to use the algorithm name "RSASSA-PSS" and specify the parameters separately. * This function simply attempts to return an algorithm name that works. * @@ -275,7 +254,7 @@ public class RsaPssTest { */ protected static String getAlgorithmName(JsonObject group) throws Exception { try { - Signature.getInstance("RSASSA-PSS"); + Signature.getInstance("RSASSA-PSS", EXPECTED_PROVIDER_NAME); return "RSASSA-PSS"; } catch (NoSuchAlgorithmException ex) { // RSASSA-PSS is not known. Try the other option. @@ -385,15 +364,14 @@ public class RsaPssTest { // the minor number if only the test vectors (but not the format) changes. // Versions meant for distribution have no status. final String expectedVersion = "0.6"; - JsonObject test = JsonUtil.getTestVectors(filename); + JsonObject test = JsonUtil.getTestVectors(this.getClass(), filename); String generatorVersion = getString(test, "generatorVersion"); - if (!generatorVersion.equals(expectedVersion)) { - System.out.println( + assertFalse( "Expecting test vectors with version " + expectedVersion + " found vectors with version " - + generatorVersion); - } + + generatorVersion, + generatorVersion.equals(expectedVersion)); int numTests = test.get("numberOfTests").getAsInt(); int cntTests = 0; int errors = 0; @@ -407,9 +385,9 @@ public class RsaPssTest { Signature verifier = null; try { key = getPublicKey(group, paramsIncluded); - verifier = Signature.getInstance(algorithm); + verifier = Signature.getInstance(algorithm, EXPECTED_PROVIDER_NAME); if (!paramsIncluded) { - PSSParameterSpec pssParams = getPSSParams(group); + PSSParameterSpec pssParams = getPSSParams(group); verifier.setParameter(pssParams); } } catch (GeneralSecurityException ex) { @@ -417,10 +395,10 @@ public class RsaPssTest { skippedKeys++; skippedAlgorithms.add(algorithm); } else { - System.out.println("Failed to generate verifier for " + algorithm + ex); + Log.e(TAG, "Failed to generate verifier for " + algorithm + ex); errors++; } - continue; + throw ex; } for (JsonElement t : group.getAsJsonArray("tests")) { cntTests++; @@ -444,7 +422,7 @@ public class RsaPssTest { } catch (Exception ex) { // Other exceptions (i.e. unchecked exceptions) are considered as error // since a third party should never be able to cause such exceptions. - System.out.println( + Log.e(TAG, "Signature verification throws " + ex.toString() + " " @@ -461,7 +439,7 @@ public class RsaPssTest { if (reason != null) { comment = " exception:" + reason; } - System.out.println( + Log.e(TAG, "Valid signature not verified. " + filename + " tcId:" @@ -471,7 +449,7 @@ public class RsaPssTest { + comment); errors++; } else if (verified && result.equals("invalid")) { - System.out.println( + Log.e(TAG, "Invalid signature verified. " + filename + " tcId:" @@ -488,7 +466,7 @@ public class RsaPssTest { // Prints some information if tests were skipped. This avoids giving // the impression that algorithms are supported. if (skippedKeys > 0 || verifiedTests == 0) { - System.out.println( + Log.d(TAG, "File:" + filename + " number of skipped keys:" @@ -496,7 +474,7 @@ public class RsaPssTest { + " verified signatures:" + verifiedTests); for (String s : skippedAlgorithms) { - System.out.println("Skipped algorithms " + s); + Log.d(TAG, "Skipped algorithms " + s); } } @@ -509,47 +487,50 @@ public class RsaPssTest { } @Test + @Ignore //TODO Reverify after b/215319125 is fixed. public void testRsaPss2048Sha256() throws Exception { - testRsaPss("rsa_pss_2048_sha256_mgf1_32_test.json", true, false); + testRsaPss("rsa_pss_2048_sha256_mgf1_32_test.json", false, false); } - @NoPresubmitTest( - providers = {ProviderType.BOUNCY_CASTLE}, - bugs = {"b/111634359"} - ) @Test + @Ignore //TODO Reverify after b/215319125 is fixed. public void testRsaPss3072Sha256() throws Exception { - testRsaPss("rsa_pss_3072_sha256_mgf1_32_test.json", true, false); + testRsaPss("rsa_pss_3072_sha256_mgf1_32_test.json", false, false); } @Test + @Ignore //TODO Reverify after b/215319125 is fixed. public void testRsaPss4096Sha256() throws Exception { - testRsaPss("rsa_pss_4096_sha256_mgf1_32_test.json", true, false); + testRsaPss("rsa_pss_4096_sha256_mgf1_32_test.json", false, false); } @Test + @Ignore //TODO Reverify after b/215319125 is fixed. public void testRsaPss4096Sha512() throws Exception { - testRsaPss("rsa_pss_4096_sha512_mgf1_32_test.json", true, false); + testRsaPss("rsa_pss_4096_sha512_mgf1_32_test.json", false, false); } @Test + @Ignore //TODO Reverify after b/215319125 is fixed. public void testRsaPss2048Sha256NoSalt() throws Exception { - testRsaPss("rsa_pss_2048_sha256_mgf1_0_test.json", true, false); + testRsaPss("rsa_pss_2048_sha256_mgf1_0_test.json", false, false); } @Test + @Ignore //TODO Reverify after b/215319125 is fixed. public void testRsaPss2048Sha512_224() throws Exception { - testRsaPss("rsa_pss_2048_sha512_256_mgf1_28_test.json", true, false); + testRsaPss("rsa_pss_2048_sha512_256_mgf1_28_test.json", false, false); } @Test + @Ignore //TODO Reverify after b/215319125 is fixed. public void testRsaPss2048Sha512_256() throws Exception { - testRsaPss("rsa_pss_2048_sha512_256_mgf1_32_test.json", true, false); + testRsaPss("rsa_pss_2048_sha512_256_mgf1_32_test.json", false, false); } // BouncyCastle and Conscrypt do not support RSA-PSS Parameters in the // encoding of the key. jdk11 should support this, but as long as - // testEncodeDecodePublic fails it makes no sense to try this test. + // testEncodeDecodePublic fails it makes no sense to try this test. /* @ExcludedTest( providers = {ProviderType.BOUNCY_CASTLE, ProviderType.CONSCRYPT}, 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 32ea493..d41bb98 100644 --- a/keystore-cts/java/com/google/security/wycheproof/testcases/RsaSignatureTest.java +++ b/keystore-cts/java/com/google/security/wycheproof/testcases/RsaSignatureTest.java @@ -18,6 +18,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.math.BigInteger; +import java.security.KeyStore; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -31,16 +32,43 @@ import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.RSAPrivateCrtKeySpec; import java.security.spec.RSAPublicKeySpec; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import org.junit.After; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import java.security.cert.X509Certificate; +import javax.security.auth.x500.X500Principal; +import java.security.cert.Certificate; +import android.security.keystore.KeyProtection; +import android.security.keystore.KeyProperties; +import android.keystore.cts.util.KeyStoreUtil; /** Tests PKCS #1 v 1.5 signatures */ // TODO(bleichen): // - document stuff // - Join other RSA tests -@RunWith(JUnit4.class) public class RsaSignatureTest { + private static final String EXPECTED_PROVIDER_NAME = TestUtil.EXPECTED_PROVIDER_NAME; + private static final String KEY_ALIAS_1 = "TestKey"; + private static final String KEY_ALIAS_INVALID = "InvalidSigningKey"; + + @After + public void tearDown() throws Exception { + KeyStoreUtil.cleanUpKeyStore(); + } + + 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) + .setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1) + .setIsStrongBoxBacked(isStrongBox) + .build()); + return (PrivateKey) keyStore.getKey(KEY_ALIAS_1, null); + } + static final RSAPublicKeySpec RSA_KEY1 = new RSAPublicKeySpec( new BigInteger( @@ -1083,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"; @@ -1095,29 +1131,15 @@ public class RsaSignatureTest { RSAPrivateKey priv = (RSAPrivateKey) keyPair.getPrivate(); byte[] messageBytes = message.getBytes("UTF-8"); - Signature signer = Signature.getInstance(algorithm); - Signature verifier = Signature.getInstance(algorithm); - signer.initSign(priv); + 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, isStrongBox)); signer.update(messageBytes); byte[] signature = signer.sign(); verifier.initVerify(pub); verifier.update(messageBytes); assertTrue(verifier.verify(signature)); - - // Extract some parameters. - byte[] rawHash = MessageDigest.getInstance(hashAlgorithm).digest(messageBytes); - - // Print keys and signature, so that it can be used to generate new test vectors. - System.out.println("Message:" + message); - System.out.println("Hash:" + TestUtil.bytesToHex(rawHash)); - System.out.println("Public key:"); - System.out.println("Modulus:" + pub.getModulus().toString()); - System.out.println("E:" + pub.getPublicExponent().toString()); - System.out.println("encoded:" + TestUtil.bytesToHex(pub.getEncoded())); - System.out.println("Private key:"); - System.out.println("D:" + priv.getPrivateExponent().toString()); - System.out.println("encoded:" + TestUtil.bytesToHex(priv.getEncoded())); - System.out.println("Signature:" + TestUtil.bytesToHex(signature)); } /** @@ -1135,7 +1157,8 @@ public class RsaSignatureTest { private void testVectors(RSAPublicKeySpec key, String algorithm, String[] testvectors) throws Exception { byte[] message = "Test".getBytes("UTF-8"); - Signature verifier = Signature.getInstance(algorithm); + Signature verifier = Signature.getInstance(algorithm, + TestUtil.EXPECTED_CRYPTO_OP_PROVIDER_NAME); KeyFactory kf = KeyFactory.getInstance("RSA"); PublicKey pub = kf.generatePublic(key); int errors = 0; @@ -1151,10 +1174,8 @@ public class RsaSignatureTest { // verify can throw SignatureExceptions if the signature is malformed. } if (first && !verified) { - System.out.println("Valid signature not verified:" + signature); errors++; } else if (!first && verified) { - System.out.println("Incorrect signature verified:" + signature); errors++; } first = false; @@ -1201,9 +1222,11 @@ public class RsaSignatureTest { RSAPublicKeySpec key = RSA_KEY1; String algorithm = ALGORITHM_KEY1; byte[] message = "Test".getBytes("UTF-8"); - Signature verifier = Signature.getInstance(algorithm); + Signature verifier = Signature.getInstance(algorithm, + TestUtil.EXPECTED_CRYPTO_OP_PROVIDER_NAME); KeyFactory kf = KeyFactory.getInstance("RSA"); PublicKey pub = kf.generatePublic(key); + int nonverified = 0; for (String signature : LEGACY_SIGNATURES_KEY1) { byte[] signatureBytes = TestUtil.hexToBytes(signature); verifier.initVerify(pub); @@ -1213,13 +1236,10 @@ public class RsaSignatureTest { verified = verifier.verify(signatureBytes); } catch (SignatureException ex) { verified = false; - } - if (verified) { - System.out.println("Verfied legacy signature:" + signature); - } else { - System.out.println("Rejected legacy signature:" + signature); + nonverified++; } } + assertEquals(0, nonverified); } /** @@ -1245,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" @@ -1280,30 +1308,50 @@ public class RsaSignatureTest { byte[] message = "Test".getBytes("UTF-8"); KeyFactory kf = KeyFactory.getInstance("RSA"); PrivateKey validPrivKey = kf.generatePrivate(validKey); - Signature signer = Signature.getInstance("SHA256WithRSA"); - signer.initSign(validPrivKey); + Signature signer = Signature.getInstance("SHA256WithRSA", + TestUtil.EXPECTED_CRYPTO_OP_PROVIDER_NAME); + RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(n, e); + PublicKey pubKey = kf.generatePublic(pubKeySpec); + + signer.initSign(getKeystorePrivateKey(pubKey, validPrivKey, isStrongBox)); signer.update(message); byte[] signature = signer.sign(); PrivateKey invalidPrivKey = null; + // Here if we use common code to get key from keystore (getKeystorePrivateKey). + // It throws run time exception while creating Certificate. + // Hence creating certificate using vaid keys. + KeyPair keyPair = new KeyPair(pubKey, validPrivKey); + X509Certificate certificate = KeyStoreUtil.createCertificate(keyPair, + new X500Principal("CN=Test1"), + new X500Principal("CN=Test1")); + Certificate[] certChain = new Certificate[]{certificate}; + KeyStore keyStore2 = KeyStore.getInstance("AndroidKeyStore"); + keyStore2.load(null); try { invalidPrivKey = kf.generatePrivate(invalidKey); - } catch (InvalidKeySpecException ex) { + keyStore2.setEntry( + KEY_ALIAS_INVALID, + new KeyStore.PrivateKeyEntry(invalidPrivKey, certChain), + 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. // This is a good sign, though of course in this case it means that we can't // check for faults. - System.out.println("Provider catches invalid RSA key:" + ex); return; } byte[] invalidSignature = null; try { - signer.initSign(invalidPrivKey); + signer.initSign((PrivateKey)keyStore2.getKey(KEY_ALIAS_INVALID, null)); signer.update(message); invalidSignature = signer.sign(); } catch (Exception ex) { // We do not necessarily expect a checked exception here, since generating // an invalid signature typically indicates a programming error. // Though RuntimeExceptions are fine here. - System.out.println("Generating PKCS#1 signature with faulty key throws:" + ex); return; } String signatureHex = TestUtil.bytesToHex(signature); @@ -1311,7 +1359,6 @@ public class RsaSignatureTest { if (signatureHex.equals(invalidSignatureHex)) { // The provider generated a correct signature. This can for example happen if the provider // does not use the CRT parameters. - System.out.println("Signature generation did not use faulty parameter"); return; } fail("Generated faulty PKCS #1 signature with faulty parameters" |