blob: c8d0eae1bdf99b02dfa8f7f5bbb36d6b1b821e6a [file] [log] [blame]
/*
* Copyright (C) 2021 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 android.keystore.cts;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.KeyAgreement;
import junit.framework.TestCase;
import org.junit.Assert;
import android.content.Context;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyInfo;
import androidx.test.InstrumentationRegistry;
public class KeyAgreementTest extends TestCase {
private static final String PRIVATE_KEY_ALIAS = "TemporaryPrivateKey";
public void testGenerateSecret_succeeds() throws Exception {
KeyAgreement ka = getKeyStoreKeyAgreement();
ka.init(generateEphemeralAndroidKeyPair(PRIVATE_KEY_ALIAS).getPrivate());
ka.doPhase(generateEphemeralServerKeyPair().getPublic(), true);
byte[] sharedSecret = ka.generateSecret();
assertNotNull(sharedSecret);
}
public void testGenerateSecret_forTwoParties_returnsSameSharedSecret() throws Exception {
KeyPair ourKeyPair = generateEphemeralAndroidKeyPair(PRIVATE_KEY_ALIAS);
KeyPair theirKeyPair = generateEphemeralServerKeyPair();
KeyAgreement ka = getKeyStoreKeyAgreement();
// Generate the shared secret with our own private key and the public key of the other party
ka.init(ourKeyPair.getPrivate());
ka.doPhase(theirKeyPair.getPublic(), true);
byte[] ourSharedSecret = ka.generateSecret();
// Generate the shared secret as if we were the other party
KeyAgreement otherKeyAgreement = KeyAgreement.getInstance("ECDH"); // Use default provider
otherKeyAgreement.init(theirKeyPair.getPrivate());
otherKeyAgreement.doPhase(ourKeyPair.getPublic(), true);
byte[] theirSharedSecret = otherKeyAgreement.generateSecret();
Assert.assertArrayEquals(ourSharedSecret, theirSharedSecret);
}
public void testGenerateSecret_preservesPrivateKeyAndNothingElse() throws Exception {
KeyPair otherPartyKey = generateEphemeralServerKeyPair();
KeyAgreement ka = getKeyStoreKeyAgreement();
ka.init(generateEphemeralAndroidKeyPair(PRIVATE_KEY_ALIAS).getPrivate());
ka.doPhase(otherPartyKey.getPublic(), true);
byte[] sharedSecret1 = ka.generateSecret();
try {
ka.generateSecret();
fail(
"Should not be able to generate secret twice. Only private key should have"
+ " been retained");
} catch (IllegalStateException ise) {
// Expected
}
ka.doPhase(otherPartyKey.getPublic(), true);
byte[] sharedSecret2 = ka.generateSecret();
Assert.assertArrayEquals(sharedSecret1, sharedSecret2);
}
public void testInit_withNonPrivateKey_fails() throws Exception {
KeyAgreement ka = getKeyStoreKeyAgreement();
try {
ka.init(new TransparentSecretKey(new byte[0], null));
fail("Initializing KeyAgreement with non-keystore key didn't throw exception.");
} catch (InvalidKeyException ike) {
// Expected
}
}
public void testInit_withNonEcKey_fails() throws Exception {
KeyPairGenerator kpg =
KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
kpg.initialize(
new KeyGenParameterSpec.Builder("rsakey", KeyProperties.PURPOSE_AGREE_KEY).build());
KeyPair rsaKeyPair = kpg.genKeyPair();
KeyAgreement ka = getKeyStoreKeyAgreement();
try {
ka.init(rsaKeyPair.getPrivate());
fail("Initializing KeyAgreement with non-EC key should fail.");
} catch (InvalidKeyException ike) {
// Expected
}
}
public void testDoPhase_withoutInitialization_fails() throws Exception {
KeyAgreement ka = getKeyStoreKeyAgreement();
try {
ka.doPhase(generateEphemeralServerKeyPair().getPublic(), true);
fail("Should not be able to call doPhase without performing initialization first");
} catch (IllegalStateException ise) {
// Expected
}
}
public void testGenerateSecret_withoutSecondPartyKey_fails() throws Exception {
KeyAgreement ka = getKeyStoreKeyAgreement();
ka.init(generateEphemeralAndroidKeyPair(PRIVATE_KEY_ALIAS).getPrivate());
try {
ka.generateSecret();
fail("Should not be able to generate secret without other party key.");
} catch (IllegalStateException ise) {
// Expected
}
}
public void testDoPhase_multiparty_fails() throws Exception {
// Multi-party key agreement is not supported by Keymint
KeyAgreement ka = getKeyStoreKeyAgreement();
ka.init(generateEphemeralAndroidKeyPair(PRIVATE_KEY_ALIAS).getPrivate());
try {
ka.doPhase(generateEphemeralServerKeyPair().getPublic(), false);
fail("Calling doPhase with lastPhase set to false should fail.");
} catch (IllegalStateException ise) {
// Expected
}
ka.doPhase(generateEphemeralServerKeyPair().getPublic(), true);
try {
ka.doPhase(generateEphemeralServerKeyPair().getPublic(), true);
fail("Calling doPhase multiple times should fail.");
} catch (IllegalStateException ise) {
// Expected
}
}
private static KeyPair generateEphemeralAndroidKeyPair(String alias)
throws GeneralSecurityException {
KeyPairGenerator kpg =
KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
kpg.initialize(
new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_AGREE_KEY).build());
KeyPair kp = kpg.generateKeyPair();
KeyFactory factory = KeyFactory.getInstance(kp.getPrivate().getAlgorithm(),
"AndroidKeyStore");
KeyInfo keyInfo = null;
try {
keyInfo = factory.getKeySpec(kp.getPrivate(), KeyInfo.class);
} catch (InvalidKeySpecException e) {
// Not an Android KeyStore key.
fail("Unable to get KeyInfo for created key.");
}
// ECDH is only implemented in Secure Hardware if KeyMint is available.
int level = keyInfo.getSecurityLevel();
Context context = InstrumentationRegistry.getTargetContext();
if (TestUtils.getFeatureVersionKeystore(context) >= Attestation.KM_VERSION_KEYMINT_1) {
Assert.assertTrue(
level == KeyProperties.SECURITY_LEVEL_TRUSTED_ENVIRONMENT ||
level == KeyProperties.SECURITY_LEVEL_UNKNOWN_SECURE);
} else {
Assert.assertEquals(keyInfo.getSecurityLevel(),
KeyProperties.SECURITY_LEVEL_SOFTWARE);
}
return kp;
}
private static KeyPair generateEphemeralServerKeyPair() throws GeneralSecurityException {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC"); // Use default provider
kpg.initialize(256);
return kpg.generateKeyPair();
}
// Helper function allowing us to include information about Keystore2 likely not being available
// in the test output.
private static KeyAgreement getKeyStoreKeyAgreement() throws NoSuchProviderException {
try {
KeyAgreement ka = KeyAgreement.getInstance("ECDH", "AndroidKeyStore");
return ka;
} catch (NoSuchAlgorithmException nsa) {
fail(
"AndroidKeyStore is missing an ECDH implementation, which is likely caused by"
+ " keystore2 not being enabled. See b/160623310 for more information.");
return null;
}
}
}