/*
* 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;
}
}