Home | History | Annotate | Download | only in recoverablekeystore
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.locksettings.recoverablekeystore;
     18 
     19 import static junit.framework.Assert.fail;
     20 
     21 import static org.junit.Assert.assertArrayEquals;
     22 import static org.junit.Assert.assertEquals;
     23 import static org.junit.Assert.assertFalse;
     24 
     25 import android.support.test.filters.SmallTest;
     26 import android.support.test.runner.AndroidJUnit4;
     27 
     28 import com.google.common.collect.ImmutableMap;
     29 
     30 import org.junit.Test;
     31 import org.junit.runner.RunWith;
     32 
     33 import java.nio.ByteBuffer;
     34 import java.nio.ByteOrder;
     35 import java.nio.charset.StandardCharsets;
     36 import java.security.KeyPair;
     37 import java.security.MessageDigest;
     38 import java.security.PublicKey;
     39 import java.util.Arrays;
     40 import java.util.Map;
     41 import java.util.Random;
     42 
     43 import javax.crypto.AEADBadTagException;
     44 import javax.crypto.KeyGenerator;
     45 import javax.crypto.SecretKey;
     46 
     47 @SmallTest
     48 @RunWith(AndroidJUnit4.class)
     49 public class KeySyncUtilsTest {
     50     private static final int RECOVERY_KEY_LENGTH_BITS = 256;
     51     private static final int THM_KF_HASH_SIZE = 256;
     52     private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
     53     private static final byte[] TEST_VAULT_HANDLE =
     54             new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17};
     55     private static final int VAULT_PARAMS_LENGTH_BYTES = 94;
     56     private static final int VAULT_HANDLE_LENGTH_BYTES = 17;
     57     private static final String SHA_256_ALGORITHM = "SHA-256";
     58     private static final String APPLICATION_KEY_ALGORITHM = "AES";
     59     private static final byte[] LOCK_SCREEN_HASH_1 =
     60             utf8Bytes("g09TEvo6XqVdNaYdRggzn5w2C5rCeE1F");
     61     private static final byte[] LOCK_SCREEN_HASH_2 =
     62             utf8Bytes("snQzsbvclkSsG6PwasAp1oFLzbq3KtFe");
     63     private static final byte[] RECOVERY_CLAIM_HEADER =
     64             "V1 KF_claim".getBytes(StandardCharsets.UTF_8);
     65     private static final byte[] RECOVERY_RESPONSE_HEADER =
     66             "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
     67     private static final int PUBLIC_KEY_LENGTH_BYTES = 65;
     68 
     69 
     70     @Test
     71     public void calculateThmKfHash_isShaOfLockScreenHashWithPrefix() throws Exception {
     72         byte[] lockScreenHash = utf8Bytes("012345678910");
     73 
     74         byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(lockScreenHash);
     75 
     76         assertArrayEquals(calculateSha256(utf8Bytes("THM_KF_hash012345678910")), thmKfHash);
     77     }
     78 
     79     @Test
     80     public void calculateThmKfHash_is256BitsLong() throws Exception {
     81         byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(utf8Bytes("1234"));
     82 
     83         assertEquals(THM_KF_HASH_SIZE / Byte.SIZE, thmKfHash.length);
     84     }
     85 
     86     @Test
     87     public void generateRecoveryKey_returnsA256BitKey() throws Exception {
     88         SecretKey key = KeySyncUtils.generateRecoveryKey();
     89 
     90         assertEquals(RECOVERY_KEY_LENGTH_BITS / Byte.SIZE, key.getEncoded().length);
     91     }
     92 
     93     @Test
     94     public void generateRecoveryKey_generatesANewKeyEachTime() throws Exception {
     95         SecretKey a = KeySyncUtils.generateRecoveryKey();
     96         SecretKey b = KeySyncUtils.generateRecoveryKey();
     97 
     98         assertFalse(Arrays.equals(a.getEncoded(), b.getEncoded()));
     99     }
    100 
    101     @Test
    102     public void generateKeyClaimant_returns16Bytes() throws Exception {
    103         byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
    104 
    105         assertEquals(KEY_CLAIMANT_LENGTH_BYTES, keyClaimant.length);
    106     }
    107 
    108     @Test
    109     public void generateKeyClaimant_generatesANewClaimantEachTime() {
    110         byte[] a = KeySyncUtils.generateKeyClaimant();
    111         byte[] b = KeySyncUtils.generateKeyClaimant();
    112 
    113         assertFalse(Arrays.equals(a, b));
    114     }
    115 
    116     @Test
    117     public void concat_concatenatesArrays() {
    118         assertArrayEquals(
    119                 utf8Bytes("hello, world!"),
    120                 KeySyncUtils.concat(
    121                         utf8Bytes("hello"),
    122                         utf8Bytes(", "),
    123                         utf8Bytes("world"),
    124                         utf8Bytes("!")));
    125     }
    126 
    127     @Test
    128     public void decryptApplicationKey_decryptsAnApplicationKeyEncryptedWithSecureBox()
    129             throws Exception {
    130         String alias = "phoebe";
    131         SecretKey recoveryKey = KeySyncUtils.generateRecoveryKey();
    132         SecretKey applicationKey = generateApplicationKey();
    133         Map<String, byte[]> encryptedKeys =
    134                 KeySyncUtils.encryptKeysWithRecoveryKey(
    135                         recoveryKey, ImmutableMap.of(alias, applicationKey));
    136         byte[] encryptedKey = encryptedKeys.get(alias);
    137 
    138         byte[] keyMaterial =
    139                 KeySyncUtils.decryptApplicationKey(recoveryKey.getEncoded(), encryptedKey);
    140 
    141         assertArrayEquals(applicationKey.getEncoded(), keyMaterial);
    142     }
    143 
    144     @Test
    145     public void decryptApplicationKey_throwsIfUnableToDecrypt() throws Exception {
    146         String alias = "casper";
    147         Map<String, byte[]> encryptedKeys =
    148                 KeySyncUtils.encryptKeysWithRecoveryKey(
    149                         KeySyncUtils.generateRecoveryKey(),
    150                         ImmutableMap.of("casper", generateApplicationKey()));
    151         byte[] encryptedKey = encryptedKeys.get(alias);
    152 
    153         try {
    154             KeySyncUtils.decryptApplicationKey(
    155                     KeySyncUtils.generateRecoveryKey().getEncoded(), encryptedKey);
    156             fail("Did not throw decrypting with bad key.");
    157         } catch (AEADBadTagException error) {
    158             // expected
    159         }
    160     }
    161 
    162     @Test
    163     public void decryptRecoveryKey_decryptsALocallyEncryptedKey() throws Exception {
    164         SecretKey recoveryKey = KeySyncUtils.generateRecoveryKey();
    165         byte[] encrypted = KeySyncUtils.locallyEncryptRecoveryKey(
    166                 LOCK_SCREEN_HASH_1, recoveryKey);
    167 
    168         byte[] keyMaterial = KeySyncUtils.decryptRecoveryKey(LOCK_SCREEN_HASH_1, encrypted);
    169 
    170         assertArrayEquals(recoveryKey.getEncoded(), keyMaterial);
    171     }
    172 
    173     @Test
    174     public void decryptRecoveryKey_throwsIfCannotDecrypt() throws Exception {
    175         SecretKey recoveryKey = KeySyncUtils.generateRecoveryKey();
    176         byte[] encrypted = KeySyncUtils.locallyEncryptRecoveryKey(LOCK_SCREEN_HASH_1, recoveryKey);
    177 
    178         try {
    179             KeySyncUtils.decryptRecoveryKey(LOCK_SCREEN_HASH_2, encrypted);
    180             fail("Did not throw decrypting with bad key.");
    181         } catch (AEADBadTagException error) {
    182             // expected
    183         }
    184     }
    185 
    186     @Test
    187     public void decryptRecoveryClaimResponse_decryptsAValidResponse() throws Exception {
    188         byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
    189         byte[] vaultParams = randomBytes(100);
    190         byte[] recoveryKey = randomBytes(32);
    191         byte[] encryptedPayload = SecureBox.encrypt(
    192                 /*theirPublicKey=*/ null,
    193                 /*sharedSecret=*/ keyClaimant,
    194                 /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
    195                 /*payload=*/ recoveryKey);
    196 
    197         byte[] decrypted = KeySyncUtils.decryptRecoveryClaimResponse(
    198                 keyClaimant, vaultParams, encryptedPayload);
    199 
    200         assertArrayEquals(recoveryKey, decrypted);
    201     }
    202 
    203     @Test
    204     public void decryptRecoveryClaimResponse_throwsIfCannotDecrypt() throws Exception {
    205         byte[] vaultParams = randomBytes(100);
    206         byte[] recoveryKey = randomBytes(32);
    207         byte[] encryptedPayload = SecureBox.encrypt(
    208                 /*theirPublicKey=*/ null,
    209                 /*sharedSecret=*/ KeySyncUtils.generateKeyClaimant(),
    210                 /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams),
    211                 /*payload=*/ recoveryKey);
    212 
    213         try {
    214             KeySyncUtils.decryptRecoveryClaimResponse(
    215                     KeySyncUtils.generateKeyClaimant(), vaultParams, encryptedPayload);
    216             fail("Did not throw decrypting with bad keyClaimant");
    217         } catch (AEADBadTagException error) {
    218             // expected
    219         }
    220     }
    221 
    222     @Test
    223     public void encryptRecoveryClaim_encryptsLockScreenAndKeyClaimant() throws Exception {
    224         KeyPair keyPair = SecureBox.genKeyPair();
    225         byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
    226         byte[] challenge = randomBytes(32);
    227         byte[] vaultParams = randomBytes(100);
    228 
    229         byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim(
    230                 keyPair.getPublic(),
    231                 vaultParams,
    232                 challenge,
    233                 LOCK_SCREEN_HASH_1,
    234                 keyClaimant);
    235 
    236         byte[] decrypted = SecureBox.decrypt(
    237                 keyPair.getPrivate(),
    238                 /*sharedSecret=*/ null,
    239                 /*header=*/ KeySyncUtils.concat(RECOVERY_CLAIM_HEADER, vaultParams, challenge),
    240                 encryptedRecoveryClaim);
    241         assertArrayEquals(KeySyncUtils.concat(LOCK_SCREEN_HASH_1, keyClaimant), decrypted);
    242     }
    243 
    244     @Test
    245     public void encryptRecoveryClaim_cannotBeDecryptedWithoutChallenge() throws Exception {
    246         KeyPair keyPair = SecureBox.genKeyPair();
    247         byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
    248         byte[] vaultParams = randomBytes(100);
    249 
    250         byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim(
    251                 keyPair.getPublic(),
    252                 vaultParams,
    253                 /*challenge=*/ randomBytes(32),
    254                 LOCK_SCREEN_HASH_1,
    255                 keyClaimant);
    256 
    257         try {
    258             SecureBox.decrypt(
    259                     keyPair.getPrivate(),
    260                     /*sharedSecret=*/ null,
    261                     /*header=*/ KeySyncUtils.concat(
    262                             RECOVERY_CLAIM_HEADER, vaultParams, randomBytes(32)),
    263                     encryptedRecoveryClaim);
    264             fail("Should throw if challenge is incorrect.");
    265         } catch (AEADBadTagException e) {
    266             // expected
    267         }
    268     }
    269 
    270     @Test
    271     public void encryptRecoveryClaim_cannotBeDecryptedWithoutCorrectSecretKey() throws Exception {
    272         byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
    273         byte[] challenge = randomBytes(32);
    274         byte[] vaultParams = randomBytes(100);
    275 
    276         byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim(
    277                 SecureBox.genKeyPair().getPublic(),
    278                 vaultParams,
    279                 challenge,
    280                 LOCK_SCREEN_HASH_1,
    281                 keyClaimant);
    282 
    283         try {
    284             SecureBox.decrypt(
    285                     SecureBox.genKeyPair().getPrivate(),
    286                     /*sharedSecret=*/ null,
    287                     /*header=*/ KeySyncUtils.concat(
    288                             RECOVERY_CLAIM_HEADER, vaultParams, challenge),
    289                     encryptedRecoveryClaim);
    290             fail("Should throw if secret key is incorrect.");
    291         } catch (AEADBadTagException e) {
    292             // expected
    293         }
    294     }
    295 
    296     @Test
    297     public void encryptRecoveryClaim_cannotBeDecryptedWithoutCorrectVaultParams() throws Exception {
    298         KeyPair keyPair = SecureBox.genKeyPair();
    299         byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
    300         byte[] challenge = randomBytes(32);
    301 
    302         byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim(
    303                 keyPair.getPublic(),
    304                 /*vaultParams=*/ randomBytes(100),
    305                 challenge,
    306                 LOCK_SCREEN_HASH_1,
    307                 keyClaimant);
    308 
    309         try {
    310             SecureBox.decrypt(
    311                     keyPair.getPrivate(),
    312                     /*sharedSecret=*/ null,
    313                     /*header=*/ KeySyncUtils.concat(
    314                             RECOVERY_CLAIM_HEADER, randomBytes(100), challenge),
    315                     encryptedRecoveryClaim);
    316             fail("Should throw if vault params is incorrect.");
    317         } catch (AEADBadTagException e) {
    318             // expected
    319         }
    320     }
    321 
    322     @Test
    323     public void encryptRecoveryClaim_cannotBeDecryptedWithoutCorrectHeader() throws Exception {
    324         KeyPair keyPair = SecureBox.genKeyPair();
    325         byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
    326         byte[] challenge = randomBytes(32);
    327         byte[] vaultParams = randomBytes(100);
    328 
    329         byte[] encryptedRecoveryClaim = KeySyncUtils.encryptRecoveryClaim(
    330                 keyPair.getPublic(),
    331                 vaultParams,
    332                 challenge,
    333                 LOCK_SCREEN_HASH_1,
    334                 keyClaimant);
    335 
    336         try {
    337             SecureBox.decrypt(
    338                     keyPair.getPrivate(),
    339                     /*sharedSecret=*/ null,
    340                     /*header=*/ KeySyncUtils.concat(randomBytes(10), vaultParams, challenge),
    341                     encryptedRecoveryClaim);
    342             fail("Should throw if header is incorrect.");
    343         } catch (AEADBadTagException e) {
    344             // expected
    345         }
    346     }
    347 
    348     @Test
    349     public void packVaultParams_returnsCorrectSize() throws Exception {
    350         PublicKey thmPublicKey = SecureBox.genKeyPair().getPublic();
    351 
    352         byte[] packedForm = KeySyncUtils.packVaultParams(
    353                 thmPublicKey,
    354                 /*counterId=*/ 1001L,
    355                 /*maxAttempts=*/ 10,
    356                 TEST_VAULT_HANDLE);
    357 
    358         assertEquals(VAULT_PARAMS_LENGTH_BYTES, packedForm.length);
    359     }
    360 
    361     @Test
    362     public void packVaultParams_encodesPublicKeyInFirst65Bytes() throws Exception {
    363         PublicKey thmPublicKey = SecureBox.genKeyPair().getPublic();
    364 
    365         byte[] packedForm = KeySyncUtils.packVaultParams(
    366                 thmPublicKey,
    367                 /*counterId=*/ 1001L,
    368                 /*maxAttempts=*/ 10,
    369                 TEST_VAULT_HANDLE);
    370 
    371         assertArrayEquals(
    372                 SecureBox.encodePublicKey(thmPublicKey),
    373                 Arrays.copyOf(packedForm, PUBLIC_KEY_LENGTH_BYTES));
    374     }
    375 
    376     @Test
    377     public void packVaultParams_encodesCounterIdAsSecondParam() throws Exception {
    378         long counterId = 103502L;
    379 
    380         byte[] packedForm = KeySyncUtils.packVaultParams(
    381                 SecureBox.genKeyPair().getPublic(),
    382                 counterId,
    383                 /*maxAttempts=*/ 10,
    384                 TEST_VAULT_HANDLE);
    385 
    386         ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm)
    387                 .order(ByteOrder.LITTLE_ENDIAN);
    388         byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES);
    389         assertEquals(counterId, byteBuffer.getLong());
    390     }
    391 
    392     @Test
    393     public void packVaultParams_encodesMaxAttemptsAsThirdParam() throws Exception {
    394         int maxAttempts = 10;
    395 
    396         byte[] packedForm = KeySyncUtils.packVaultParams(
    397                 SecureBox.genKeyPair().getPublic(),
    398                 /*counterId=*/ 1001L,
    399                 maxAttempts,
    400                 TEST_VAULT_HANDLE);
    401 
    402         ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm)
    403                 .order(ByteOrder.LITTLE_ENDIAN);
    404         byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + Long.BYTES);
    405         assertEquals(maxAttempts, byteBuffer.getInt());
    406     }
    407 
    408     @Test
    409     public void packVaultParams_encodesVaultHandleAsLastParam() throws Exception {
    410         byte[] packedForm = KeySyncUtils.packVaultParams(
    411                 SecureBox.genKeyPair().getPublic(),
    412                 /*counterId=*/ 10021L,
    413                 /*maxAttempts=*/ 10,
    414                 TEST_VAULT_HANDLE);
    415 
    416         ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm)
    417                 .order(ByteOrder.LITTLE_ENDIAN);
    418         byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + Long.BYTES + Integer.BYTES);
    419         byte[] vaultHandle = new byte[VAULT_HANDLE_LENGTH_BYTES];
    420         byteBuffer.get(vaultHandle);
    421         assertArrayEquals(TEST_VAULT_HANDLE, vaultHandle);
    422     }
    423 
    424     @Test
    425     public void packVaultParams_encodesVaultHandleWithLength8AsLastParam() throws Exception {
    426         byte[] vaultHandleWithLenght8 = new byte[] {1, 2, 3, 4, 1, 2, 3, 4};
    427         byte[] packedForm = KeySyncUtils.packVaultParams(
    428                 SecureBox.genKeyPair().getPublic(),
    429                 /*counterId=*/ 10021L,
    430                 /*maxAttempts=*/ 10,
    431                 vaultHandleWithLenght8);
    432 
    433         ByteBuffer byteBuffer = ByteBuffer.wrap(packedForm)
    434                 .order(ByteOrder.LITTLE_ENDIAN);
    435         assertEquals(PUBLIC_KEY_LENGTH_BYTES + Long.BYTES + Integer.BYTES + 8, packedForm.length);
    436         byteBuffer.position(PUBLIC_KEY_LENGTH_BYTES + Long.BYTES + Integer.BYTES);
    437         byte[] vaultHandle = new byte[8];
    438         byteBuffer.get(vaultHandle);
    439         assertArrayEquals(vaultHandleWithLenght8, vaultHandle);
    440     }
    441 
    442     private static byte[] randomBytes(int n) {
    443         byte[] bytes = new byte[n];
    444         new Random().nextBytes(bytes);
    445         return bytes;
    446     }
    447 
    448     private static byte[] utf8Bytes(String s) {
    449         return s.getBytes(StandardCharsets.UTF_8);
    450     }
    451 
    452     private static byte[] calculateSha256(byte[] bytes) throws Exception {
    453         MessageDigest messageDigest = MessageDigest.getInstance(SHA_256_ALGORITHM);
    454         messageDigest.update(bytes);
    455         return messageDigest.digest();
    456     }
    457 
    458     private static SecretKey generateApplicationKey() throws Exception {
    459         KeyGenerator keyGenerator = KeyGenerator.getInstance(APPLICATION_KEY_ALGORITHM);
    460         keyGenerator.init(/*keySize=*/ 256);
    461         return keyGenerator.generateKey();
    462     }
    463 }
    464