Unit tests for all registered ECDH KeyAgreement instances.
The BouncyCastle (BC) provider is currently failing the following
tests:
* testInit_withUnsupportedAlgorithmParameterSpec because it accepts
AlgorithmParameterSpec instances which have nothing to do with
key agreement.
Change-Id: Ibce14f23e8b711e92d0312030642ae452941dc6d
diff --git a/luni/src/test/java/libcore/java/security/SignatureTest.java b/luni/src/test/java/libcore/java/security/SignatureTest.java
index 4e2cf61..4afc67d 100644
--- a/luni/src/test/java/libcore/java/security/SignatureTest.java
+++ b/luni/src/test/java/libcore/java/security/SignatureTest.java
@@ -246,7 +246,7 @@
+ "34fdadc44326b9b3f3fa828652bab07f0362ac141c8c3784ebdec44e0b156a5e7bccdc81a56fe954"
+ "56ac8c0e4ae12d97");
- private static byte[] hexToBytes(String s) {
+ public static byte[] hexToBytes(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
diff --git a/luni/src/test/java/libcore/javax/crypto/ECDHKeyAgreementTest.java b/luni/src/test/java/libcore/javax/crypto/ECDHKeyAgreementTest.java
new file mode 100644
index 0000000..cabe5c9
--- /dev/null
+++ b/luni/src/test/java/libcore/javax/crypto/ECDHKeyAgreementTest.java
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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 libcore.javax.crypto;
+
+import static libcore.java.security.SignatureTest.hexToBytes;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
+import java.util.Comparator;
+
+import javax.crypto.KeyAgreement;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+
+/**
+ * Tests for all registered Elliptic Curve Diffie-Hellman {@link KeyAgreement} providers.
+ */
+public class ECDHKeyAgreementTest extends TestCase {
+ // Two key pairs and the resulting shared secret for the Known Answer Test
+ private static final byte[] KAT_PUBLIC_KEY1_X509 = hexToBytes(
+ "3059301306072a8648ce3d020106082a8648ce3d030107034200049fc2f71f85446b1371244491d83"
+ + "9cf97b5d27cedbb04d2c0058b59709df3a216e6b4ca1b2d622588c5a0e6968144a8965e816a600c"
+ + "05305a1da3df2bf02b41d1");
+ private static final byte[] KAT_PRIVATE_KEY1_PKCS8 = hexToBytes(
+ "308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420e1e683003"
+ + "c8b963a92742e5f955ce7fddc81d0c3ae9b149d6af86a0cacb2271ca00a06082a8648ce3d030107"
+ + "a144034200049fc2f71f85446b1371244491d839cf97b5d27cedbb04d2c0058b59709df3a216e6b"
+ + "4ca1b2d622588c5a0e6968144a8965e816a600c05305a1da3df2bf02b41d1");
+
+ private static final byte[] KAT_PUBLIC_KEY2_X509 = hexToBytes(
+ "3059301306072a8648ce3d020106082a8648ce3d03010703420004358efb6d91e5bbcae21774af3f6"
+ + "d85d0848630e7e61dbeb5ac9e47036ed0f8d38c7a1d1bb249f92861c7c9153fff33f45ab5b171eb"
+ + "e8cad741125e6bb4fc6b07");
+ private static final byte[] KAT_PRIVATE_KEY2_PKCS8 = hexToBytes(
+ "308193020100301306072a8648ce3d020106082a8648ce3d0301070479307702010104202b1810a69"
+ + "e12b74d50bf0343168f705f0104f76299855268aa526fdb31e6eec0a00a06082a8648ce3d030107"
+ + "a14403420004358efb6d91e5bbcae21774af3f6d85d0848630e7e61dbeb5ac9e47036ed0f8d38c7"
+ + "a1d1bb249f92861c7c9153fff33f45ab5b171ebe8cad741125e6bb4fc6b07");
+
+ private static final byte[] KAT_SECRET =
+ hexToBytes("4faa0594c0e773eb26c8df2163af2443e88aab9578b9e1f324bc61e42d222783");
+
+ private static final ECPublicKey KAT_PUBLIC_KEY1;
+ private static final ECPrivateKey KAT_PRIVATE_KEY1;
+ private static final ECPublicKey KAT_PUBLIC_KEY2;
+ private static final ECPrivateKey KAT_PRIVATE_KEY2;
+ static {
+ try {
+ KAT_PUBLIC_KEY1 = getPublicKey(KAT_PUBLIC_KEY1_X509);
+ KAT_PRIVATE_KEY1 = getPrivateKey(KAT_PRIVATE_KEY1_PKCS8);
+ KAT_PUBLIC_KEY2 = getPublicKey(KAT_PUBLIC_KEY2_X509);
+ KAT_PRIVATE_KEY2 = getPrivateKey(KAT_PRIVATE_KEY2_PKCS8);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to decode KAT key pairs using default provider", e);
+ }
+ }
+
+ /**
+ * Performs a known-answer test of the shared secret for all permutations of {@code Providers}
+ * of: first key pair, second key pair, and the {@code KeyAgreement}. This is to check that
+ * the {@code KeyAgreement} instances work with keys of all registered providers.
+ */
+ public void testKnownAnswer() throws Exception {
+ for (Provider keyFactoryProvider1 : getKeyFactoryProviders()) {
+ ECPrivateKey privateKey1 = getPrivateKey(KAT_PRIVATE_KEY1_PKCS8, keyFactoryProvider1);
+ ECPublicKey publicKey1 = getPublicKey(KAT_PUBLIC_KEY1_X509, keyFactoryProvider1);
+ for (Provider keyFactoryProvider2 : getKeyFactoryProviders()) {
+ ECPrivateKey privateKey2 =
+ getPrivateKey(KAT_PRIVATE_KEY2_PKCS8, keyFactoryProvider2);
+ ECPublicKey publicKey2 =
+ getPublicKey(KAT_PUBLIC_KEY2_X509, keyFactoryProvider2);
+ for (Provider keyAgreementProvider : getKeyAgreementProviders()) {
+ try {
+ testKnownAnswer(publicKey1, privateKey1, publicKey2, privateKey2,
+ keyAgreementProvider);
+ } catch (Throwable e) {
+ throw new RuntimeException(getClass().getSimpleName() + ".testKnownAnswer("
+ + keyFactoryProvider1.getName()
+ + ", " + keyFactoryProvider2.getName()
+ + ", " + keyAgreementProvider.getName() + ")",
+ e);
+ }
+ }
+ }
+ }
+ }
+
+ void testKnownAnswer(
+ ECPublicKey publicKey1, ECPrivateKey privateKey1,
+ ECPublicKey publicKey2, ECPrivateKey privateKey2,
+ Provider keyAgreementProvider) throws Exception {
+ assertTrue(Arrays.equals(
+ KAT_SECRET, generateSecret(keyAgreementProvider, privateKey1, publicKey2)));
+ assertTrue(Arrays.equals(
+ KAT_SECRET, generateSecret(keyAgreementProvider, privateKey2, publicKey1)));
+ }
+
+ public void testGetAlgorithm() throws Exception {
+ invokeCallingMethodForEachKeyAgreementProvider();
+ }
+
+ void testGetAlgorithm(Provider provider) throws Exception {
+ assertEquals("ECDH", getKeyAgreement(provider).getAlgorithm());
+ }
+
+ public void testGetProvider() throws Exception {
+ invokeCallingMethodForEachKeyAgreementProvider();
+ }
+
+ void testGetProvider(Provider provider) throws Exception {
+ assertSame(provider, getKeyAgreement(provider).getProvider());
+ }
+
+ public void testInit_withNullPrivateKey() throws Exception {
+ invokeCallingMethodForEachKeyAgreementProvider();
+ }
+
+ void testInit_withNullPrivateKey(Provider provider) throws Exception {
+ KeyAgreement keyAgreement = getKeyAgreement(provider);
+ try {
+ keyAgreement.init(null);
+ fail();
+ } catch (InvalidKeyException expected) {}
+ }
+
+ public void testInit_withUnsupportedPrivateKeyType() throws Exception {
+ invokeCallingMethodForEachKeyAgreementProvider();
+ }
+
+ void testInit_withUnsupportedPrivateKeyType(Provider provider) throws Exception {
+ KeyAgreement keyAgreement = getKeyAgreement(provider);
+ try {
+ keyAgreement.init(KAT_PUBLIC_KEY1);
+ fail();
+ } catch (InvalidKeyException expected) {}
+ }
+
+ public void testInit_withUnsupportedAlgorithmParameterSpec() throws Exception {
+ invokeCallingMethodForEachKeyAgreementProvider();
+ }
+
+ void testInit_withUnsupportedAlgorithmParameterSpec(Provider provider) throws Exception {
+ try {
+ getKeyAgreement(provider).init(KAT_PRIVATE_KEY1, new ECGenParameterSpec("prime256v1"));
+ fail();
+ } catch (InvalidAlgorithmParameterException expected) {}
+ }
+
+ public void testDoPhase_whenNotInitialized() throws Exception {
+ invokeCallingMethodForEachKeyAgreementProvider();
+ }
+
+ void testDoPhase_whenNotInitialized(Provider provider) throws Exception {
+ try {
+ getKeyAgreement(provider).doPhase(KAT_PUBLIC_KEY1, true);
+ fail();
+ } catch (IllegalStateException expected) {}
+ }
+
+ public void testDoPhaseReturnsNull() throws Exception {
+ invokeCallingMethodForEachKeyAgreementProvider();
+ }
+
+ void testDoPhaseReturnsNull(Provider provider) throws Exception {
+ KeyAgreement keyAgreement = getKeyAgreement(provider);
+ keyAgreement.init(KAT_PRIVATE_KEY1);
+ assertNull(keyAgreement.doPhase(KAT_PUBLIC_KEY2, true));
+ }
+
+ public void testDoPhase_withPhaseWhichIsNotLast() throws Exception {
+ invokeCallingMethodForEachKeyAgreementProvider();
+ }
+
+ void testDoPhase_withPhaseWhichIsNotLast(Provider provider) throws Exception {
+ KeyAgreement keyAgreement = getKeyAgreement(provider);
+ keyAgreement.init(KAT_PRIVATE_KEY1);
+ try {
+ keyAgreement.doPhase(KAT_PUBLIC_KEY2, false);
+ fail();
+ } catch (IllegalStateException expected) {}
+ }
+
+ public void testDoPhase_withNullKey() throws Exception {
+ invokeCallingMethodForEachKeyAgreementProvider();
+ }
+
+ void testDoPhase_withNullKey(Provider provider) throws Exception {
+ KeyAgreement keyAgreement = getKeyAgreement(provider);
+ keyAgreement.init(KAT_PRIVATE_KEY1);
+ try {
+ keyAgreement.doPhase(null, true);
+ fail();
+ } catch (InvalidKeyException expected) {}
+ }
+
+ public void testDoPhase_withInvalidKeyType() throws Exception {
+ invokeCallingMethodForEachKeyAgreementProvider();
+ }
+
+ void testDoPhase_withInvalidKeyType(Provider provider) throws Exception {
+ KeyAgreement keyAgreement = getKeyAgreement(provider);
+ keyAgreement.init(KAT_PRIVATE_KEY1);
+ try {
+ keyAgreement.doPhase(KAT_PRIVATE_KEY1, true);
+ fail();
+ } catch (InvalidKeyException expected) {}
+ }
+
+ public void testGenerateSecret_withNullOutputBuffer() throws Exception {
+ invokeCallingMethodForEachKeyAgreementProvider();
+ }
+
+ void testGenerateSecret_withNullOutputBuffer(Provider provider) throws Exception {
+ KeyAgreement keyAgreement = getKeyAgreement(provider);
+ keyAgreement.init(KAT_PRIVATE_KEY1);
+ keyAgreement.doPhase(KAT_PUBLIC_KEY2, true);
+ try {
+ keyAgreement.generateSecret(null, 0);
+ fail();
+ } catch (NullPointerException expected) {}
+ }
+
+ public void testGenerateSecret_withBufferOfTheRightSize() throws Exception {
+ invokeCallingMethodForEachKeyAgreementProvider();
+ }
+
+ void testGenerateSecret_withBufferOfTheRightSize(Provider provider) throws Exception {
+ KeyAgreement keyAgreement = getKeyAgreement(provider);
+ keyAgreement.init(KAT_PRIVATE_KEY1);
+ keyAgreement.doPhase(KAT_PUBLIC_KEY2, true);
+
+ byte[] buffer = new byte[KAT_SECRET.length];
+ int secretLengthBytes = keyAgreement.generateSecret(buffer, 0);
+ assertEquals(KAT_SECRET.length, secretLengthBytes);
+ assertTrue(Arrays.equals(KAT_SECRET, buffer));
+ }
+
+ public void testGenerateSecret_withLargerThatNeededBuffer() throws Exception {
+ invokeCallingMethodForEachKeyAgreementProvider();
+ }
+
+ void testGenerateSecret_withLargerThatNeededBuffer(Provider provider) throws Exception {
+ KeyAgreement keyAgreement = getKeyAgreement(provider);
+ keyAgreement.init(KAT_PRIVATE_KEY1);
+ keyAgreement.doPhase(KAT_PUBLIC_KEY2, true);
+
+ // Place the shared secret in the middle of the larger buffer and check that only that
+ // part of the buffer is affected.
+ byte[] buffer = new byte[KAT_SECRET.length + 2];
+ buffer[0] = (byte) 0x85; // arbitrary canary value
+ buffer[buffer.length - 1] = (byte) 0x3b; // arbitrary canary value
+ int secretLengthBytes = keyAgreement.generateSecret(buffer, 1);
+ assertEquals(KAT_SECRET.length, secretLengthBytes);
+ assertEquals((byte) 0x85, buffer[0]);
+ assertEquals((byte) 0x3b, buffer[buffer.length - 1]);
+ byte[] secret = new byte[KAT_SECRET.length];
+ System.arraycopy(buffer, 1, secret, 0, secret.length);
+ assertTrue(Arrays.equals(KAT_SECRET, secret));
+ }
+
+ public void testGenerateSecret_withSmallerThanNeededBuffer() throws Exception {
+ invokeCallingMethodForEachKeyAgreementProvider();
+ }
+
+ void testGenerateSecret_withSmallerThanNeededBuffer(Provider provider) throws Exception {
+ KeyAgreement keyAgreement = getKeyAgreement(provider);
+ keyAgreement.init(KAT_PRIVATE_KEY1);
+ keyAgreement.doPhase(KAT_PUBLIC_KEY2, true);
+ try {
+ // Although the buffer is big enough (1024 bytes) the shared secret should be placed
+ // at offset 1020 thus leaving only 4 bytes for the secret, which is not enough.
+ keyAgreement.generateSecret(new byte[1024], 1020);
+ fail();
+ } catch (ShortBufferException expected) {}
+ }
+
+ public void testGenerateSecret_withoutBuffer() throws Exception {
+ invokeCallingMethodForEachKeyAgreementProvider();
+ }
+
+ void testGenerateSecret_withoutBuffer(Provider provider) throws Exception {
+ KeyAgreement keyAgreement = getKeyAgreement(provider);
+ keyAgreement.init(KAT_PRIVATE_KEY2);
+ keyAgreement.doPhase(KAT_PUBLIC_KEY1, true);
+
+ byte[] secret = keyAgreement.generateSecret();
+ assertTrue(Arrays.equals(KAT_SECRET, secret));
+ }
+
+ public void testGenerateSecret_withAlgorithm() throws Exception {
+ invokeCallingMethodForEachKeyAgreementProvider();
+ }
+
+ void testGenerateSecret_withAlgorithm(Provider provider) throws Exception {
+ KeyAgreement keyAgreement = getKeyAgreement(provider);
+ keyAgreement.init(KAT_PRIVATE_KEY2);
+ keyAgreement.doPhase(KAT_PUBLIC_KEY1, true);
+
+ SecretKey key = keyAgreement.generateSecret("AES");
+ assertEquals("AES", key.getAlgorithm());
+ // The check below will need to change if it's a hardware-backed key.
+ // We'll have to encrypt a known plaintext and check that the ciphertext is as
+ // expected.
+ assertTrue(Arrays.equals(KAT_SECRET, key.getEncoded()));
+ }
+
+ private void invokeCallingMethodForEachKeyAgreementProvider() throws Exception {
+ StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
+ String callingMethodName = null;
+ for (int i = 0; i < stackTrace.length; i++) {
+ if ("invokeCallingMethodForEachKeyAgreementProvider".equals(
+ stackTrace[i].getMethodName())) {
+ callingMethodName = stackTrace[i + 1].getMethodName();
+ }
+ }
+ if (callingMethodName == null) {
+ throw new RuntimeException("Failed to deduce calling method name from stack trace");
+ }
+
+ String invokedMethodName = callingMethodName;
+ Method method;
+ try {
+ method = getClass().getDeclaredMethod(invokedMethodName, Provider.class);
+ } catch (NoSuchMethodError e) {
+ throw new AssertionFailedError("Failed to find per-Provider test method "
+ + getClass().getSimpleName() + "." + invokedMethodName + "(Provider)");
+ }
+
+ for (Provider provider : getKeyAgreementProviders()) {
+ try {
+ method.invoke(this, provider);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(getClass().getSimpleName() + "." + invokedMethodName
+ + "(provider: " + provider.getName() + ") failed",
+ e.getCause());
+ }
+ }
+ }
+
+ private static Provider[] getKeyAgreementProviders() {
+ Provider[] providers = Security.getProviders("KeyAgreement.ECDH");
+ if (providers == null) {
+ return new Provider[0];
+ }
+ // Sort providers by name to guarantee non-determinism in the order in which providers are
+ // used in the tests.
+ return sortByName(providers);
+ }
+
+ private static Provider[] getKeyFactoryProviders() {
+ Provider[] providers = Security.getProviders("KeyFactory.EC");
+ if (providers == null) {
+ return new Provider[0];
+ }
+ // Sort providers by name to guarantee non-determinism in the order in which providers are
+ // used in the tests.
+ return sortByName(providers);
+ }
+
+ private static ECPrivateKey getPrivateKey(byte[] pkcs8EncodedKey, Provider provider)
+ throws GeneralSecurityException {
+ KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);
+ return (ECPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8EncodedKey));
+ }
+
+ private static ECPublicKey getPublicKey(byte[] x509EncodedKey, Provider provider)
+ throws GeneralSecurityException {
+ KeyFactory keyFactory = KeyFactory.getInstance("EC", provider);
+ return (ECPublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(x509EncodedKey));
+ }
+
+ private static ECPrivateKey getPrivateKey(byte[] pkcs8EncodedKey)
+ throws GeneralSecurityException {
+ KeyFactory keyFactory = KeyFactory.getInstance("EC");
+ return (ECPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8EncodedKey));
+ }
+
+ private static ECPublicKey getPublicKey(byte[] x509EncodedKey)
+ throws GeneralSecurityException {
+ KeyFactory keyFactory = KeyFactory.getInstance("EC");
+ return (ECPublicKey) keyFactory.generatePublic(new X509EncodedKeySpec(x509EncodedKey));
+ }
+
+ private static KeyAgreement getKeyAgreement(Provider provider) throws NoSuchAlgorithmException {
+ return KeyAgreement.getInstance("ECDH", provider);
+ }
+
+ private static byte[] generateSecret(
+ Provider keyAgreementProvider, PrivateKey privateKey, PublicKey publicKey)
+ throws GeneralSecurityException {
+ KeyAgreement keyAgreement = getKeyAgreement(keyAgreementProvider);
+ keyAgreement.init(privateKey);
+ keyAgreement.doPhase(publicKey, true);
+ return keyAgreement.generateSecret();
+ }
+
+ private static Provider[] sortByName(Provider[] providers) {
+ Arrays.sort(providers, new Comparator<Provider>() {
+ @Override
+ public int compare(Provider lhs, Provider rhs) {
+ return lhs.getName().compareTo(rhs.getName());
+ }
+ });
+ return providers;
+ }
+}