Add tests for KeySyncUtils now that SecureBox is available
SecureBox is to be merged before this. Test: adb shell am instrument -w -e package com.android.server.locksettings.recoverablekeystore com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner Change-Id: I40f783c165faa595c8913ff073915726ae71524d
This commit is contained in:
@@ -112,7 +112,8 @@ public class KeySyncUtils {
|
||||
* @throws NoSuchAlgorithmException if any SecureBox algorithm is unavailable.
|
||||
* @throws InvalidKeyException if the hash cannot be used to encrypt for some reason.
|
||||
*/
|
||||
private static byte[] locallyEncryptRecoveryKey(byte[] lockScreenHash, SecretKey recoveryKey)
|
||||
@VisibleForTesting
|
||||
static byte[] locallyEncryptRecoveryKey(byte[] lockScreenHash, SecretKey recoveryKey)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
return SecureBox.encrypt(
|
||||
/*theirPublicKey=*/ null,
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.server.locksettings.recoverablekeystore;
|
||||
|
||||
import static junit.framework.Assert.fail;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
@@ -23,13 +25,20 @@ import static org.junit.Assert.assertFalse;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyPair;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.crypto.AEADBadTagException;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
@SmallTest
|
||||
@@ -39,6 +48,13 @@ public class KeySyncUtilsTest {
|
||||
private static final int THM_KF_HASH_SIZE = 256;
|
||||
private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
|
||||
private static final String SHA_256_ALGORITHM = "SHA-256";
|
||||
private static final String APPLICATION_KEY_ALGORITHM = "AES";
|
||||
private static final byte[] LOCK_SCREEN_HASH_1 =
|
||||
utf8Bytes("g09TEvo6XqVdNaYdRggzn5w2C5rCeE1F");
|
||||
private static final byte[] LOCK_SCREEN_HASH_2 =
|
||||
utf8Bytes("snQzsbvclkSsG6PwasAp1oFLzbq3KtFe");
|
||||
private static final byte[] RECOVERY_CLAIM_HEADER =
|
||||
"V1 KF_claim".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
@Test
|
||||
public void calculateThmKfHash_isShaOfLockScreenHashWithPrefix() throws Exception {
|
||||
@@ -97,6 +113,197 @@ public class KeySyncUtilsTest {
|
||||
utf8Bytes("!")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decryptApplicationKey_decryptsAnApplicationKeyEncryptedWithSecureBox()
|
||||
throws Exception {
|
||||
String alias = "phoebe";
|
||||
SecretKey recoveryKey = KeySyncUtils.generateRecoveryKey();
|
||||
SecretKey applicationKey = generateApplicationKey();
|
||||
Map<String, byte[]> encryptedKeys =
|
||||
KeySyncUtils.encryptKeysWithRecoveryKey(
|
||||
recoveryKey, ImmutableMap.of(alias, applicationKey));
|
||||
byte[] encryptedKey = encryptedKeys.get(alias);
|
||||
|
||||
byte[] keyMaterial =
|
||||
KeySyncUtils.decryptApplicationKey(recoveryKey.getEncoded(), encryptedKey);
|
||||
|
||||
assertArrayEquals(applicationKey.getEncoded(), keyMaterial);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decryptApplicationKey_throwsIfUnableToDecrypt() throws Exception {
|
||||
String alias = "casper";
|
||||
Map<String, byte[]> encryptedKeys =
|
||||
KeySyncUtils.encryptKeysWithRecoveryKey(
|
||||
KeySyncUtils.generateRecoveryKey(),
|
||||
ImmutableMap.of("casper", generateApplicationKey()));
|
||||
byte[] encryptedKey = encryptedKeys.get(alias);
|
||||
|
||||
try {
|
||||
KeySyncUtils.decryptApplicationKey(
|
||||
KeySyncUtils.generateRecoveryKey().getEncoded(), encryptedKey);
|
||||
fail("Did not throw decrypting with bad key.");
|
||||
} catch (AEADBadTagException error) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decryptRecoveryKey_decryptsALocallyEncryptedKey() throws Exception {
|
||||
SecretKey recoveryKey = KeySyncUtils.generateRecoveryKey();
|
||||
byte[] encrypted = KeySyncUtils.locallyEncryptRecoveryKey(
|
||||
LOCK_SCREEN_HASH_1, recoveryKey);
|
||||
|
||||
byte[] keyMaterial = KeySyncUtils.decryptRecoveryKey(LOCK_SCREEN_HASH_1, encrypted);
|
||||
|
||||
assertArrayEquals(recoveryKey.getEncoded(), keyMaterial);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decryptRecoveryKey_throwsIfCannotDecrypt() throws Exception {
|
||||
SecretKey recoveryKey = KeySyncUtils.generateRecoveryKey();
|
||||
byte[] encrypted = KeySyncUtils.locallyEncryptRecoveryKey(LOCK_SCREEN_HASH_1, recoveryKey);
|
||||
|
||||
try {
|
||||
KeySyncUtils.decryptRecoveryKey(LOCK_SCREEN_HASH_2, encrypted);
|
||||
fail("Did not throw decrypting with bad key.");
|
||||
} catch (AEADBadTagException error) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encryptRecoveryClaim_encryptsLockScreenAndKeyClaimant() throws Exception {
|
||||
KeyPair keyPair = SecureBox.genKeyPair();
|
||||
byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
|
||||
byte[] challenge = randomBytes(32);
|
||||
byte[] vaultParams = randomBytes(100);
|
||||
|
||||
byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim(
|
||||
keyPair.getPublic(),
|
||||
vaultParams,
|
||||
challenge,
|
||||
LOCK_SCREEN_HASH_1,
|
||||
keyClaimant);
|
||||
|
||||
byte[] decrypted = SecureBox.decrypt(
|
||||
keyPair.getPrivate(),
|
||||
/*sharedSecret=*/ null,
|
||||
/*header=*/ KeySyncUtils.concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge),
|
||||
encryptedRecoveryClaim);
|
||||
assertArrayEquals(KeySyncUtils.concat(LOCK_SCREEN_HASH_1, keyClaimant), decrypted);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encryptRecoveryClaim_cannotBeDecryptedWithoutChallenge() throws Exception {
|
||||
KeyPair keyPair = SecureBox.genKeyPair();
|
||||
byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
|
||||
byte[] vaultParams = randomBytes(100);
|
||||
|
||||
byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim(
|
||||
keyPair.getPublic(),
|
||||
vaultParams,
|
||||
/*challenge=*/ randomBytes(32),
|
||||
LOCK_SCREEN_HASH_1,
|
||||
keyClaimant);
|
||||
|
||||
try {
|
||||
SecureBox.decrypt(
|
||||
keyPair.getPrivate(),
|
||||
/*sharedSecret=*/ null,
|
||||
/*header=*/ KeySyncUtils.concat(
|
||||
RECOVERY_CLAIM_HEADER, vaultParams, randomBytes(32)),
|
||||
encryptedRecoveryClaim);
|
||||
fail("Should throw if challenge is incorrect.");
|
||||
} catch (AEADBadTagException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encryptRecoveryClaim_cannotBeDecryptedWithoutCorrectSecretKey() throws Exception {
|
||||
byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
|
||||
byte[] challenge = randomBytes(32);
|
||||
byte[] vaultParams = randomBytes(100);
|
||||
|
||||
byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim(
|
||||
SecureBox.genKeyPair().getPublic(),
|
||||
vaultParams,
|
||||
challenge,
|
||||
LOCK_SCREEN_HASH_1,
|
||||
keyClaimant);
|
||||
|
||||
try {
|
||||
SecureBox.decrypt(
|
||||
SecureBox.genKeyPair().getPrivate(),
|
||||
/*sharedSecret=*/ null,
|
||||
/*header=*/ KeySyncUtils.concat(
|
||||
RECOVERY_CLAIM_HEADER, vaultParams, challenge),
|
||||
encryptedRecoveryClaim);
|
||||
fail("Should throw if secret key is incorrect.");
|
||||
} catch (AEADBadTagException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encryptRecoveryClaim_cannotBeDecryptedWithoutCorrectVaultParams() throws Exception {
|
||||
KeyPair keyPair = SecureBox.genKeyPair();
|
||||
byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
|
||||
byte[] challenge = randomBytes(32);
|
||||
|
||||
byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim(
|
||||
keyPair.getPublic(),
|
||||
/*vaultParams=*/ randomBytes(100),
|
||||
challenge,
|
||||
LOCK_SCREEN_HASH_1,
|
||||
keyClaimant);
|
||||
|
||||
try {
|
||||
SecureBox.decrypt(
|
||||
keyPair.getPrivate(),
|
||||
/*sharedSecret=*/ null,
|
||||
/*header=*/ KeySyncUtils.concat(
|
||||
RECOVERY_CLAIM_HEADER, randomBytes(100), challenge),
|
||||
encryptedRecoveryClaim);
|
||||
fail("Should throw if vault params is incorrect.");
|
||||
} catch (AEADBadTagException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encryptRecoveryClaim_cannotBeDecryptedWithoutCorrectHeader() throws Exception {
|
||||
KeyPair keyPair = SecureBox.genKeyPair();
|
||||
byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
|
||||
byte[] challenge = randomBytes(32);
|
||||
byte[] vaultParams = randomBytes(100);
|
||||
|
||||
byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim(
|
||||
keyPair.getPublic(),
|
||||
vaultParams,
|
||||
challenge,
|
||||
LOCK_SCREEN_HASH_1,
|
||||
keyClaimant);
|
||||
|
||||
try {
|
||||
SecureBox.decrypt(
|
||||
keyPair.getPrivate(),
|
||||
/*sharedSecret=*/ null,
|
||||
/*header=*/ KeySyncUtils.concat(randomBytes(10), vaultParams, challenge),
|
||||
encryptedRecoveryClaim);
|
||||
fail("Should throw if header is incorrect.");
|
||||
} catch (AEADBadTagException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] randomBytes(int n) {
|
||||
byte[] bytes = new byte[n];
|
||||
new Random().nextBytes(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static byte[] utf8Bytes(String s) {
|
||||
return s.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
@@ -106,4 +313,10 @@ public class KeySyncUtilsTest {
|
||||
messageDigest.update(bytes);
|
||||
return messageDigest.digest();
|
||||
}
|
||||
|
||||
private static SecretKey generateApplicationKey() throws Exception {
|
||||
KeyGenerator keyGenerator = KeyGenerator.getInstance(APPLICATION_KEY_ALGORITHM);
|
||||
keyGenerator.init(/*keySize=*/ 256);
|
||||
return keyGenerator.generateKey();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user