Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 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 | |
Dmitry Dementyev | 0916e7c | 2018-01-23 13:02:08 -0800 | [diff] [blame] | 19 | import static android.security.keystore.recovery.KeyChainProtectionParams.TYPE_LOCKSCREEN; |
| 20 | import static android.security.keystore.recovery.KeyChainProtectionParams.UI_FORMAT_PASSWORD; |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 21 | |
Dmitry Dementyev | ad88471 | 2017-12-20 12:38:36 -0800 | [diff] [blame] | 22 | import static com.google.common.truth.Truth.assertThat; |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 23 | import static org.junit.Assert.assertArrayEquals; |
| 24 | import static org.junit.Assert.assertEquals; |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 25 | import static org.junit.Assert.fail; |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 26 | import static org.mockito.ArgumentMatchers.any; |
Robert Berry | cfc990a | 2017-12-22 15:54:30 +0000 | [diff] [blame] | 27 | import static org.mockito.ArgumentMatchers.anyInt; |
| 28 | import static org.mockito.ArgumentMatchers.anyString; |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 29 | import static org.mockito.ArgumentMatchers.eq; |
| 30 | import static org.mockito.Mockito.times; |
| 31 | import static org.mockito.Mockito.verify; |
Robert Berry | cfc990a | 2017-12-22 15:54:30 +0000 | [diff] [blame] | 32 | import static org.mockito.Mockito.when; |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 33 | |
Robert Berry | cfc990a | 2017-12-22 15:54:30 +0000 | [diff] [blame] | 34 | import android.app.KeyguardManager; |
Dmitry Dementyev | 3b17c63 | 2017-12-21 17:30:48 -0800 | [diff] [blame] | 35 | import android.app.PendingIntent; |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 36 | import android.content.Context; |
Dmitry Dementyev | 3b17c63 | 2017-12-21 17:30:48 -0800 | [diff] [blame] | 37 | import android.content.Intent; |
Dmitry Dementyev | ed89ea0 | 2018-01-11 13:53:52 -0800 | [diff] [blame] | 38 | import android.Manifest; |
Dmitry Dementyev | 3b17c63 | 2017-12-21 17:30:48 -0800 | [diff] [blame] | 39 | import android.os.Binder; |
Dmitry Dementyev | 1429831 | 2018-01-04 15:19:19 -0800 | [diff] [blame] | 40 | import android.os.ServiceSpecificException; |
Dmitry Dementyev | ad88471 | 2017-12-20 12:38:36 -0800 | [diff] [blame] | 41 | import android.os.UserHandle; |
Dmitry Dementyev | 29b9de5 | 2018-01-31 16:09:32 -0800 | [diff] [blame] | 42 | import android.security.KeyStore; |
Robert Berry | 4a5c87d | 2018-03-19 18:00:46 +0000 | [diff] [blame] | 43 | import android.security.keystore.AndroidKeyStoreProvider; |
Bo Zhu | 3462c83 | 2018-01-04 22:42:36 -0800 | [diff] [blame] | 44 | import android.security.keystore.AndroidKeyStoreSecretKey; |
| 45 | import android.security.keystore.KeyGenParameterSpec; |
| 46 | import android.security.keystore.KeyProperties; |
Dmitry Dementyev | 0916e7c | 2018-01-23 13:02:08 -0800 | [diff] [blame] | 47 | import android.security.keystore.recovery.KeyChainProtectionParams; |
Dmitry Dementyev | f34fc7e | 2018-03-26 17:31:29 -0700 | [diff] [blame] | 48 | import android.security.keystore.recovery.KeyDerivationParams; |
Bo Zhu | 7c1972f | 2018-02-22 21:43:52 -0800 | [diff] [blame] | 49 | import android.security.keystore.recovery.RecoveryCertPath; |
Dmitry Dementyev | f34fc7e | 2018-03-26 17:31:29 -0700 | [diff] [blame] | 50 | import android.security.keystore.recovery.TrustedRootCertificates; |
Robert Berry | 81ee34b | 2018-01-23 11:59:59 +0000 | [diff] [blame] | 51 | import android.security.keystore.recovery.WrappedApplicationKey; |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 52 | import android.support.test.filters.SmallTest; |
Dmitry Dementyev | ed89ea0 | 2018-01-11 13:53:52 -0800 | [diff] [blame] | 53 | import android.support.test.InstrumentationRegistry; |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 54 | import android.support.test.runner.AndroidJUnit4; |
| 55 | |
Dmitry Dementyev | 29b9de5 | 2018-01-31 16:09:32 -0800 | [diff] [blame] | 56 | import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage; |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 57 | import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; |
| 58 | import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage; |
Robert Berry | bd086f1 | 2017-12-27 13:29:39 +0000 | [diff] [blame] | 59 | import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage; |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 60 | |
| 61 | import com.google.common.collect.ImmutableList; |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 62 | import com.google.common.collect.ImmutableMap; |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 63 | |
| 64 | import org.junit.After; |
| 65 | import org.junit.Before; |
| 66 | import org.junit.Test; |
| 67 | import org.junit.runner.RunWith; |
| 68 | import org.mockito.Mock; |
| 69 | import org.mockito.MockitoAnnotations; |
| 70 | |
| 71 | import java.io.File; |
| 72 | import java.nio.charset.StandardCharsets; |
Robert Berry | 4a5c87d | 2018-03-19 18:00:46 +0000 | [diff] [blame] | 73 | import java.security.UnrecoverableKeyException; |
Bo Zhu | 7c1972f | 2018-02-22 21:43:52 -0800 | [diff] [blame] | 74 | import java.security.cert.CertPath; |
| 75 | import java.security.cert.CertificateFactory; |
| 76 | import java.security.cert.X509Certificate; |
| 77 | import java.util.ArrayList; |
Robert Berry | 4a534ec | 2017-12-21 15:44:02 +0000 | [diff] [blame] | 78 | import java.util.concurrent.Executors; |
Dmitry Dementyev | ad88471 | 2017-12-20 12:38:36 -0800 | [diff] [blame] | 79 | import java.util.Map; |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 80 | import java.util.Random; |
| 81 | |
Robert Berry | 4a5c87d | 2018-03-19 18:00:46 +0000 | [diff] [blame] | 82 | import javax.crypto.Cipher; |
Bo Zhu | 3462c83 | 2018-01-04 22:42:36 -0800 | [diff] [blame] | 83 | import javax.crypto.KeyGenerator; |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 84 | import javax.crypto.SecretKey; |
Robert Berry | 4a5c87d | 2018-03-19 18:00:46 +0000 | [diff] [blame] | 85 | import javax.crypto.spec.GCMParameterSpec; |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 86 | import javax.crypto.spec.SecretKeySpec; |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 87 | |
| 88 | @SmallTest |
| 89 | @RunWith(AndroidJUnit4.class) |
| 90 | public class RecoverableKeyStoreManagerTest { |
| 91 | private static final String DATABASE_FILE_NAME = "recoverablekeystore.db"; |
| 92 | |
Bo Zhu | b31ab67 | 2018-03-20 22:44:18 -0700 | [diff] [blame] | 93 | private static final String ROOT_CERTIFICATE_ALIAS = ""; |
Dmitry Dementyev | f34fc7e | 2018-03-26 17:31:29 -0700 | [diff] [blame] | 94 | private static final String DEFAULT_ROOT_CERT_ALIAS = |
| 95 | TrustedRootCertificates.GOOGLE_CLOUD_KEY_VAULT_SERVICE_V1_ALIAS; |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 96 | private static final String TEST_SESSION_ID = "karlin"; |
| 97 | private static final byte[] TEST_PUBLIC_KEY = new byte[] { |
| 98 | (byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a, |
| 99 | (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06, |
| 100 | (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x03, |
| 101 | (byte) 0x01, (byte) 0x07, (byte) 0x03, (byte) 0x42, (byte) 0x00, (byte) 0x04, (byte) 0xb8, |
| 102 | (byte) 0x00, (byte) 0x11, (byte) 0x18, (byte) 0x98, (byte) 0x1d, (byte) 0xf0, (byte) 0x6e, |
| 103 | (byte) 0xb4, (byte) 0x94, (byte) 0xfe, (byte) 0x86, (byte) 0xda, (byte) 0x1c, (byte) 0x07, |
| 104 | (byte) 0x8d, (byte) 0x01, (byte) 0xb4, (byte) 0x3a, (byte) 0xf6, (byte) 0x8d, (byte) 0xdc, |
| 105 | (byte) 0x61, (byte) 0xd0, (byte) 0x46, (byte) 0x49, (byte) 0x95, (byte) 0x0f, (byte) 0x10, |
| 106 | (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0, (byte) 0x3f, (byte) 0xd2, |
| 107 | (byte) 0xdf, (byte) 0xf3, (byte) 0x79, (byte) 0x20, (byte) 0x1d, (byte) 0x91, (byte) 0x55, |
| 108 | (byte) 0xb0, (byte) 0xe5, (byte) 0xbd, (byte) 0x7a, (byte) 0x8b, (byte) 0x32, (byte) 0x7d, |
| 109 | (byte) 0x25, (byte) 0x53, (byte) 0xa2, (byte) 0xfc, (byte) 0xa5, (byte) 0x65, (byte) 0xe1, |
| 110 | (byte) 0xbd, (byte) 0x21, (byte) 0x44, (byte) 0x7e, (byte) 0x78, (byte) 0x52, (byte) 0xfa}; |
| 111 | private static final byte[] TEST_SALT = getUtf8Bytes("salt"); |
| 112 | private static final byte[] TEST_SECRET = getUtf8Bytes("password1234"); |
| 113 | private static final byte[] TEST_VAULT_CHALLENGE = getUtf8Bytes("vault_challenge"); |
Bo Zhu | def7ffd | 2018-01-05 14:50:52 -0800 | [diff] [blame] | 114 | private static final byte[] TEST_VAULT_PARAMS = new byte[] { |
| 115 | // backend_key |
| 116 | (byte) 0x04, (byte) 0xb8, (byte) 0x00, (byte) 0x11, (byte) 0x18, (byte) 0x98, (byte) 0x1d, |
| 117 | (byte) 0xf0, (byte) 0x6e, (byte) 0xb4, (byte) 0x94, (byte) 0xfe, (byte) 0x86, (byte) 0xda, |
| 118 | (byte) 0x1c, (byte) 0x07, (byte) 0x8d, (byte) 0x01, (byte) 0xb4, (byte) 0x3a, (byte) 0xf6, |
| 119 | (byte) 0x8d, (byte) 0xdc, (byte) 0x61, (byte) 0xd0, (byte) 0x46, (byte) 0x49, (byte) 0x95, |
| 120 | (byte) 0x0f, (byte) 0x10, (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0, |
| 121 | (byte) 0x3f, (byte) 0xd2, (byte) 0xdf, (byte) 0xf3, (byte) 0x79, (byte) 0x20, (byte) 0x1d, |
| 122 | (byte) 0x91, (byte) 0x55, (byte) 0xb0, (byte) 0xe5, (byte) 0xbd, (byte) 0x7a, (byte) 0x8b, |
| 123 | (byte) 0x32, (byte) 0x7d, (byte) 0x25, (byte) 0x53, (byte) 0xa2, (byte) 0xfc, (byte) 0xa5, |
| 124 | (byte) 0x65, (byte) 0xe1, (byte) 0xbd, (byte) 0x21, (byte) 0x44, (byte) 0x7e, (byte) 0x78, |
| 125 | (byte) 0x52, (byte) 0xfa, |
| 126 | // counter_id |
| 127 | (byte) 0x31, (byte) 0x32, (byte) 0x33, (byte) 0x34, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| 128 | (byte) 0x00, |
| 129 | // device_parameter |
| 130 | (byte) 0x78, (byte) 0x56, (byte) 0x34, (byte) 0x12, (byte) 0x00, (byte) 0x00, (byte) 0x00, |
| 131 | (byte) 0x0, |
| 132 | // max_attempts |
| 133 | (byte) 0x0a, (byte) 0x00, (byte) 0x00, (byte) 0x00}; |
Bo Zhu | 3462c83 | 2018-01-04 22:42:36 -0800 | [diff] [blame] | 134 | private static final int TEST_GENERATION_ID = 2; |
| 135 | private static final int TEST_USER_ID = 10009; |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 136 | private static final int KEY_CLAIMANT_LENGTH_BYTES = 16; |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 137 | private static final byte[] RECOVERY_RESPONSE_HEADER = |
| 138 | "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8); |
| 139 | private static final String TEST_ALIAS = "nick"; |
Bo Zhu | 4857cb5 | 2018-02-06 14:34:48 -0800 | [diff] [blame] | 140 | private static final String TEST_ALIAS2 = "bob"; |
Robert Berry | cfc990a | 2017-12-22 15:54:30 +0000 | [diff] [blame] | 141 | private static final int RECOVERABLE_KEY_SIZE_BYTES = 32; |
Bo Zhu | 2c8e538 | 2018-02-26 15:54:25 -0800 | [diff] [blame] | 142 | private static final int APPLICATION_KEY_SIZE_BYTES = 32; |
Dmitry Dementyev | ad88471 | 2017-12-20 12:38:36 -0800 | [diff] [blame] | 143 | private static final int GENERATION_ID = 1; |
| 144 | private static final byte[] NONCE = getUtf8Bytes("nonce"); |
| 145 | private static final byte[] KEY_MATERIAL = getUtf8Bytes("keymaterial"); |
Bo Zhu | 3462c83 | 2018-01-04 22:42:36 -0800 | [diff] [blame] | 146 | private static final String KEY_ALGORITHM = "AES"; |
| 147 | private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore"; |
| 148 | private static final String WRAPPING_KEY_ALIAS = "RecoverableKeyStoreManagerTest/WrappingKey"; |
Dmitry Dementyev | f34fc7e | 2018-03-26 17:31:29 -0700 | [diff] [blame] | 149 | private static final String TEST_DEFAULT_ROOT_CERT_ALIAS = ""; |
Dmitry Dementyev | 16d9db5 | 2018-03-26 11:31:46 -0700 | [diff] [blame] | 150 | private static final KeyChainProtectionParams TEST_PROTECTION_PARAMS = |
| 151 | new KeyChainProtectionParams.Builder() |
| 152 | .setUserSecretType(TYPE_LOCKSCREEN) |
| 153 | .setLockScreenUiFormat(UI_FORMAT_PASSWORD) |
| 154 | .setKeyDerivationParams(KeyDerivationParams.createSha256Params(TEST_SALT)) |
| 155 | .setSecret(TEST_SECRET) |
| 156 | .build(); |
Dmitry Dementyev | ad88471 | 2017-12-20 12:38:36 -0800 | [diff] [blame] | 157 | |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 158 | @Mock private Context mMockContext; |
Robert Berry | 9104404 | 2017-12-27 12:05:58 +0000 | [diff] [blame] | 159 | @Mock private RecoverySnapshotListenersStorage mMockListenersStorage; |
Robert Berry | cfc990a | 2017-12-22 15:54:30 +0000 | [diff] [blame] | 160 | @Mock private KeyguardManager mKeyguardManager; |
Bo Zhu | 3462c83 | 2018-01-04 22:42:36 -0800 | [diff] [blame] | 161 | @Mock private PlatformKeyManager mPlatformKeyManager; |
Dmitry Dementyev | 29b9de5 | 2018-01-31 16:09:32 -0800 | [diff] [blame] | 162 | @Mock private ApplicationKeyStorage mApplicationKeyStorage; |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 163 | |
| 164 | private RecoverableKeyStoreDb mRecoverableKeyStoreDb; |
| 165 | private File mDatabaseFile; |
| 166 | private RecoverableKeyStoreManager mRecoverableKeyStoreManager; |
| 167 | private RecoverySessionStorage mRecoverySessionStorage; |
Robert Berry | bd086f1 | 2017-12-27 13:29:39 +0000 | [diff] [blame] | 168 | private RecoverySnapshotStorage mRecoverySnapshotStorage; |
Bo Zhu | 3462c83 | 2018-01-04 22:42:36 -0800 | [diff] [blame] | 169 | private PlatformEncryptionKey mPlatformEncryptionKey; |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 170 | |
| 171 | @Before |
Bo Zhu | 3462c83 | 2018-01-04 22:42:36 -0800 | [diff] [blame] | 172 | public void setUp() throws Exception { |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 173 | MockitoAnnotations.initMocks(this); |
| 174 | |
| 175 | Context context = InstrumentationRegistry.getTargetContext(); |
| 176 | mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME); |
| 177 | mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context); |
Robert Berry | cfc990a | 2017-12-22 15:54:30 +0000 | [diff] [blame] | 178 | |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 179 | mRecoverySessionStorage = new RecoverySessionStorage(); |
Robert Berry | cfc990a | 2017-12-22 15:54:30 +0000 | [diff] [blame] | 180 | |
| 181 | when(mMockContext.getSystemService(anyString())).thenReturn(mKeyguardManager); |
| 182 | when(mMockContext.getSystemServiceName(any())).thenReturn("test"); |
Robert Berry | 3ae5bea | 2017-12-27 10:58:03 +0000 | [diff] [blame] | 183 | when(mMockContext.getApplicationContext()).thenReturn(mMockContext); |
Bo Zhu | 3462c83 | 2018-01-04 22:42:36 -0800 | [diff] [blame] | 184 | when(mKeyguardManager.isDeviceSecure(TEST_USER_ID)).thenReturn(true); |
| 185 | |
| 186 | mPlatformEncryptionKey = |
| 187 | new PlatformEncryptionKey(TEST_GENERATION_ID, generateAndroidKeyStoreKey()); |
| 188 | when(mPlatformKeyManager.getEncryptKey(anyInt())).thenReturn(mPlatformEncryptionKey); |
Robert Berry | cfc990a | 2017-12-22 15:54:30 +0000 | [diff] [blame] | 189 | |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 190 | mRecoverableKeyStoreManager = new RecoverableKeyStoreManager( |
| 191 | mMockContext, |
| 192 | mRecoverableKeyStoreDb, |
Robert Berry | 4a534ec | 2017-12-21 15:44:02 +0000 | [diff] [blame] | 193 | mRecoverySessionStorage, |
Dmitry Dementyev | 3b17c63 | 2017-12-21 17:30:48 -0800 | [diff] [blame] | 194 | Executors.newSingleThreadExecutor(), |
Robert Berry | 9104404 | 2017-12-27 12:05:58 +0000 | [diff] [blame] | 195 | mRecoverySnapshotStorage, |
Bo Zhu | 3462c83 | 2018-01-04 22:42:36 -0800 | [diff] [blame] | 196 | mMockListenersStorage, |
Dmitry Dementyev | 29b9de5 | 2018-01-31 16:09:32 -0800 | [diff] [blame] | 197 | mPlatformKeyManager, |
| 198 | mApplicationKeyStorage); |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 199 | } |
| 200 | |
| 201 | @After |
| 202 | public void tearDown() { |
| 203 | mRecoverableKeyStoreDb.close(); |
| 204 | mDatabaseFile.delete(); |
| 205 | } |
| 206 | |
| 207 | @Test |
Robert Berry | cfc990a | 2017-12-22 15:54:30 +0000 | [diff] [blame] | 208 | public void generateAndStoreKey_storesTheKey() throws Exception { |
| 209 | int uid = Binder.getCallingUid(); |
Dmitry Dementyev | 77183ef | 2018-01-05 15:46:00 -0800 | [diff] [blame] | 210 | int userId = UserHandle.getCallingUserId(); |
Robert Berry | cfc990a | 2017-12-22 15:54:30 +0000 | [diff] [blame] | 211 | |
| 212 | mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS); |
| 213 | |
| 214 | assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNotNull(); |
Dmitry Dementyev | 77183ef | 2018-01-05 15:46:00 -0800 | [diff] [blame] | 215 | |
| 216 | assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue(); |
Robert Berry | cfc990a | 2017-12-22 15:54:30 +0000 | [diff] [blame] | 217 | } |
| 218 | |
| 219 | @Test |
| 220 | public void generateAndStoreKey_returnsAKeyOfAppropriateSize() throws Exception { |
| 221 | assertThat(mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS)) |
| 222 | .hasLength(RECOVERABLE_KEY_SIZE_BYTES); |
| 223 | } |
| 224 | |
| 225 | @Test |
Bo Zhu | 2c8e538 | 2018-02-26 15:54:25 -0800 | [diff] [blame] | 226 | public void importKey_storesTheKey() throws Exception { |
| 227 | int uid = Binder.getCallingUid(); |
| 228 | int userId = UserHandle.getCallingUserId(); |
| 229 | byte[] keyMaterial = randomBytes(APPLICATION_KEY_SIZE_BYTES); |
| 230 | |
| 231 | mRecoverableKeyStoreManager.importKey(TEST_ALIAS, keyMaterial); |
| 232 | |
| 233 | assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNotNull(); |
| 234 | assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue(); |
Robert Berry | 4a5c87d | 2018-03-19 18:00:46 +0000 | [diff] [blame] | 235 | // TODO(76083050) Test the grant mechanism for the keys. |
Bo Zhu | 2c8e538 | 2018-02-26 15:54:25 -0800 | [diff] [blame] | 236 | } |
| 237 | |
| 238 | @Test |
| 239 | public void importKey_throwsIfInvalidLength() throws Exception { |
| 240 | byte[] keyMaterial = randomBytes(APPLICATION_KEY_SIZE_BYTES - 1); |
| 241 | try { |
| 242 | mRecoverableKeyStoreManager.importKey(TEST_ALIAS, keyMaterial); |
| 243 | fail("should have thrown"); |
| 244 | } catch (ServiceSpecificException e) { |
| 245 | assertThat(e.getMessage()).contains("not contain 256 bits"); |
| 246 | } |
| 247 | } |
| 248 | |
| 249 | @Test |
| 250 | public void importKey_throwsIfNullKey() throws Exception { |
| 251 | try { |
| 252 | mRecoverableKeyStoreManager.importKey(TEST_ALIAS, /*keyBytes=*/ null); |
| 253 | fail("should have thrown"); |
Dmitry Dementyev | 1e6a9dc | 2018-03-21 13:52:00 -0700 | [diff] [blame] | 254 | } catch (NullPointerException e) { |
| 255 | assertThat(e.getMessage()).contains("is null"); |
Bo Zhu | 2c8e538 | 2018-02-26 15:54:25 -0800 | [diff] [blame] | 256 | } |
| 257 | } |
| 258 | |
| 259 | @Test |
Robert Berry | 5daccec | 2018-01-06 19:16:25 +0000 | [diff] [blame] | 260 | public void removeKey_removesAKey() throws Exception { |
| 261 | int uid = Binder.getCallingUid(); |
| 262 | mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS); |
| 263 | |
| 264 | mRecoverableKeyStoreManager.removeKey(TEST_ALIAS); |
| 265 | |
| 266 | assertThat(mRecoverableKeyStoreDb.getKey(uid, TEST_ALIAS)).isNull(); |
| 267 | } |
| 268 | |
| 269 | @Test |
Dmitry Dementyev | 40dadb0 | 2018-01-10 18:03:37 -0800 | [diff] [blame] | 270 | public void removeKey_updatesShouldCreateSnapshot() throws Exception { |
Dmitry Dementyev | 77183ef | 2018-01-05 15:46:00 -0800 | [diff] [blame] | 271 | int uid = Binder.getCallingUid(); |
| 272 | int userId = UserHandle.getCallingUserId(); |
Dmitry Dementyev | 77183ef | 2018-01-05 15:46:00 -0800 | [diff] [blame] | 273 | mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS); |
| 274 | // Pretend that key was synced |
| 275 | mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); |
| 276 | |
| 277 | mRecoverableKeyStoreManager.removeKey(TEST_ALIAS); |
| 278 | |
| 279 | assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue(); |
| 280 | } |
| 281 | |
| 282 | @Test |
Dmitry Dementyev | 40dadb0 | 2018-01-10 18:03:37 -0800 | [diff] [blame] | 283 | public void removeKey_failureDoesNotUpdateShouldCreateSnapshot() throws Exception { |
| 284 | int uid = Binder.getCallingUid(); |
| 285 | int userId = UserHandle.getCallingUserId(); |
| 286 | mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); |
| 287 | // Key did not exist |
| 288 | mRecoverableKeyStoreManager.removeKey(TEST_ALIAS); |
| 289 | |
| 290 | assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); |
| 291 | } |
| 292 | |
| 293 | @Test |
Bo Zhu | 7ce4ea5 | 2018-02-27 23:52:19 -0800 | [diff] [blame] | 294 | public void initRecoveryService_succeedsWithCertFile() throws Exception { |
Dmitry Dementyev | 40dadb0 | 2018-01-10 18:03:37 -0800 | [diff] [blame] | 295 | int uid = Binder.getCallingUid(); |
| 296 | int userId = UserHandle.getCallingUserId(); |
Bo Zhu | 14d993d | 2018-02-03 21:38:48 -0800 | [diff] [blame] | 297 | long certSerial = 1000L; |
| 298 | mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); |
| 299 | |
| 300 | mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, |
| 301 | TestData.getCertXmlWithSerial(certSerial)); |
| 302 | |
Robert Berry | e8edf97 | 2018-03-27 11:45:11 +0100 | [diff] [blame] | 303 | assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); |
Dmitry Dementyev | f34fc7e | 2018-03-26 17:31:29 -0700 | [diff] [blame] | 304 | assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid, |
| 305 | DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(TestData.CERT_PATH_1); |
| 306 | assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid, |
| 307 | DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(certSerial); |
Bo Zhu | 14d993d | 2018-02-03 21:38:48 -0800 | [diff] [blame] | 308 | assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull(); |
| 309 | } |
| 310 | |
| 311 | @Test |
Bo Zhu | 8d6861e | 2018-03-21 20:45:09 -0700 | [diff] [blame] | 312 | public void initRecoveryService_regeneratesCounterId() throws Exception { |
| 313 | int uid = Binder.getCallingUid(); |
| 314 | int userId = UserHandle.getCallingUserId(); |
| 315 | long certSerial = 1000L; |
| 316 | |
| 317 | Long counterId0 = mRecoverableKeyStoreDb.getCounterId(userId, uid); |
| 318 | mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, |
| 319 | TestData.getCertXmlWithSerial(certSerial)); |
| 320 | Long counterId1 = mRecoverableKeyStoreDb.getCounterId(userId, uid); |
| 321 | mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, |
| 322 | TestData.getCertXmlWithSerial(certSerial + 1)); |
| 323 | Long counterId2 = mRecoverableKeyStoreDb.getCounterId(userId, uid); |
| 324 | |
| 325 | assertThat(!counterId1.equals(counterId0) || !counterId2.equals(counterId1)).isTrue(); |
| 326 | } |
| 327 | |
| 328 | @Test |
Bo Zhu | 7f414d9 | 2018-02-28 09:28:19 -0800 | [diff] [blame] | 329 | public void initRecoveryService_throwsIfInvalidCert() throws Exception { |
| 330 | byte[] modifiedCertXml = TestData.getCertXml(); |
| 331 | modifiedCertXml[modifiedCertXml.length - 50] ^= 1; // Flip a bit in the certificate |
| 332 | try { |
Dmitry Dementyev | f34fc7e | 2018-03-26 17:31:29 -0700 | [diff] [blame] | 333 | mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, |
| 334 | modifiedCertXml); |
Bo Zhu | 7f414d9 | 2018-02-28 09:28:19 -0800 | [diff] [blame] | 335 | fail("should have thrown"); |
| 336 | } catch (ServiceSpecificException e) { |
| 337 | assertThat(e.getMessage()).contains("validate cert"); |
| 338 | } |
| 339 | } |
| 340 | |
| 341 | @Test |
Bo Zhu | 14d993d | 2018-02-03 21:38:48 -0800 | [diff] [blame] | 342 | public void initRecoveryService_updatesWithLargerSerial() throws Exception { |
| 343 | int uid = Binder.getCallingUid(); |
| 344 | int userId = UserHandle.getCallingUserId(); |
| 345 | long certSerial = 1000L; |
| 346 | |
| 347 | mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, |
| 348 | TestData.getCertXmlWithSerial(certSerial)); |
| 349 | mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, |
| 350 | TestData.getCertXmlWithSerial(certSerial + 1)); |
| 351 | |
Dmitry Dementyev | f34fc7e | 2018-03-26 17:31:29 -0700 | [diff] [blame] | 352 | assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid, |
| 353 | DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(certSerial + 1); |
Robert Berry | e8edf97 | 2018-03-27 11:45:11 +0100 | [diff] [blame] | 354 | assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue(); |
Bo Zhu | 14d993d | 2018-02-03 21:38:48 -0800 | [diff] [blame] | 355 | } |
| 356 | |
| 357 | @Test |
| 358 | public void initRecoveryService_ignoresSmallerSerial() throws Exception { |
| 359 | int uid = Binder.getCallingUid(); |
| 360 | int userId = UserHandle.getCallingUserId(); |
| 361 | long certSerial = 1000L; |
| 362 | |
| 363 | mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, |
| 364 | TestData.getCertXmlWithSerial(certSerial)); |
| 365 | mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, |
| 366 | TestData.getCertXmlWithSerial(certSerial - 1)); |
| 367 | |
Dmitry Dementyev | f34fc7e | 2018-03-26 17:31:29 -0700 | [diff] [blame] | 368 | assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid, |
| 369 | DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(certSerial); |
Robert Berry | e8edf97 | 2018-03-27 11:45:11 +0100 | [diff] [blame] | 370 | assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); |
Bo Zhu | 14d993d | 2018-02-03 21:38:48 -0800 | [diff] [blame] | 371 | } |
| 372 | |
| 373 | @Test |
| 374 | public void initRecoveryService_ignoresTheSameSerial() throws Exception { |
| 375 | int uid = Binder.getCallingUid(); |
| 376 | int userId = UserHandle.getCallingUserId(); |
| 377 | long certSerial = 1000L; |
| 378 | |
| 379 | mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, |
| 380 | TestData.getCertXmlWithSerial(certSerial)); |
Bo Zhu | 14d993d | 2018-02-03 21:38:48 -0800 | [diff] [blame] | 381 | mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, |
| 382 | TestData.getCertXmlWithSerial(certSerial)); |
| 383 | |
| 384 | // If the second update succeeds, getShouldCreateSnapshot() will return true. |
| 385 | assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); |
| 386 | } |
| 387 | |
| 388 | @Test |
| 389 | public void initRecoveryService_succeedsWithRawPublicKey() throws Exception { |
| 390 | int uid = Binder.getCallingUid(); |
| 391 | int userId = UserHandle.getCallingUserId(); |
Dmitry Dementyev | 40dadb0 | 2018-01-10 18:03:37 -0800 | [diff] [blame] | 392 | mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); |
| 393 | |
| 394 | mRecoverableKeyStoreManager.initRecoveryService(ROOT_CERTIFICATE_ALIAS, TEST_PUBLIC_KEY); |
| 395 | |
| 396 | assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue(); |
Dmitry Dementyev | f34fc7e | 2018-03-26 17:31:29 -0700 | [diff] [blame] | 397 | assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid, |
| 398 | DEFAULT_ROOT_CERT_ALIAS)).isNull(); |
| 399 | assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertSerial(userId, uid, |
| 400 | DEFAULT_ROOT_CERT_ALIAS)).isNull(); |
Bo Zhu | 14d993d | 2018-02-03 21:38:48 -0800 | [diff] [blame] | 401 | assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNotNull(); |
Dmitry Dementyev | 40dadb0 | 2018-01-10 18:03:37 -0800 | [diff] [blame] | 402 | } |
| 403 | |
| 404 | @Test |
Bo Zhu | 7f414d9 | 2018-02-28 09:28:19 -0800 | [diff] [blame] | 405 | public void initRecoveryServiceWithSigFile_succeeds() throws Exception { |
| 406 | int uid = Binder.getCallingUid(); |
| 407 | int userId = UserHandle.getCallingUserId(); |
| 408 | mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); |
| 409 | |
| 410 | mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile( |
| 411 | ROOT_CERTIFICATE_ALIAS, TestData.getCertXml(), TestData.getSigXml()); |
| 412 | |
Robert Berry | e8edf97 | 2018-03-27 11:45:11 +0100 | [diff] [blame] | 413 | assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); |
Dmitry Dementyev | f34fc7e | 2018-03-26 17:31:29 -0700 | [diff] [blame] | 414 | assertThat(mRecoverableKeyStoreDb.getRecoveryServiceCertPath(userId, uid, |
| 415 | DEFAULT_ROOT_CERT_ALIAS)).isEqualTo(TestData.CERT_PATH_1); |
Bo Zhu | 7f414d9 | 2018-02-28 09:28:19 -0800 | [diff] [blame] | 416 | assertThat(mRecoverableKeyStoreDb.getRecoveryServicePublicKey(userId, uid)).isNull(); |
| 417 | } |
| 418 | |
| 419 | @Test |
| 420 | public void initRecoveryServiceWithSigFile_throwsIfNullCertFile() throws Exception { |
| 421 | try { |
| 422 | mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile( |
| 423 | ROOT_CERTIFICATE_ALIAS, /*recoveryServiceCertFile=*/ null, |
| 424 | TestData.getSigXml()); |
| 425 | fail("should have thrown"); |
Dmitry Dementyev | 1e6a9dc | 2018-03-21 13:52:00 -0700 | [diff] [blame] | 426 | } catch (NullPointerException e) { |
Bo Zhu | 7f414d9 | 2018-02-28 09:28:19 -0800 | [diff] [blame] | 427 | assertThat(e.getMessage()).contains("is null"); |
| 428 | } |
| 429 | } |
| 430 | |
| 431 | @Test |
| 432 | public void initRecoveryServiceWithSigFile_throwsIfNullSigFile() throws Exception { |
| 433 | try { |
| 434 | mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile( |
| 435 | ROOT_CERTIFICATE_ALIAS, TestData.getCertXml(), |
| 436 | /*recoveryServiceSigFile=*/ null); |
| 437 | fail("should have thrown"); |
Dmitry Dementyev | 1e6a9dc | 2018-03-21 13:52:00 -0700 | [diff] [blame] | 438 | } catch (NullPointerException e) { |
Bo Zhu | 7f414d9 | 2018-02-28 09:28:19 -0800 | [diff] [blame] | 439 | assertThat(e.getMessage()).contains("is null"); |
| 440 | } |
| 441 | } |
| 442 | |
| 443 | @Test |
| 444 | public void initRecoveryServiceWithSigFile_throwsIfWrongSigFileFormat() throws Exception { |
| 445 | try { |
| 446 | mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile( |
| 447 | ROOT_CERTIFICATE_ALIAS, TestData.getCertXml(), |
| 448 | getUtf8Bytes("wrong-sig-file-format")); |
| 449 | fail("should have thrown"); |
| 450 | } catch (ServiceSpecificException e) { |
| 451 | assertThat(e.getMessage()).contains("parse the sig file"); |
| 452 | } |
| 453 | } |
| 454 | |
| 455 | @Test |
| 456 | public void initRecoveryServiceWithSigFile_throwsIfInvalidFileSignature() throws Exception { |
| 457 | byte[] modifiedCertXml = TestData.getCertXml(); |
| 458 | modifiedCertXml[modifiedCertXml.length - 1] = 0; // Change the last new line char to a zero |
| 459 | try { |
| 460 | mRecoverableKeyStoreManager.initRecoveryServiceWithSigFile( |
| 461 | ROOT_CERTIFICATE_ALIAS, modifiedCertXml, TestData.getSigXml()); |
| 462 | fail("should have thrown"); |
| 463 | } catch (ServiceSpecificException e) { |
| 464 | assertThat(e.getMessage()).contains("is invalid"); |
| 465 | } |
| 466 | } |
| 467 | |
| 468 | @Test |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 469 | public void startRecoverySession_checksPermissionFirst() throws Exception { |
| 470 | mRecoverableKeyStoreManager.startRecoverySession( |
| 471 | TEST_SESSION_ID, |
| 472 | TEST_PUBLIC_KEY, |
| 473 | TEST_VAULT_PARAMS, |
| 474 | TEST_VAULT_CHALLENGE, |
Dmitry Dementyev | 16d9db5 | 2018-03-26 11:31:46 -0700 | [diff] [blame] | 475 | ImmutableList.of(TEST_PROTECTION_PARAMS)); |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 476 | |
Dmitry Dementyev | ad88471 | 2017-12-20 12:38:36 -0800 | [diff] [blame] | 477 | verify(mMockContext, times(1)) |
| 478 | .enforceCallingOrSelfPermission( |
Dmitry Dementyev | ed89ea0 | 2018-01-11 13:53:52 -0800 | [diff] [blame] | 479 | eq(Manifest.permission.RECOVER_KEYSTORE), any()); |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 480 | } |
| 481 | |
Bo Zhu | b31ab67 | 2018-03-20 22:44:18 -0700 | [diff] [blame] | 482 | // TODO: Add tests for non-existing cert alias |
| 483 | |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 484 | @Test |
Bo Zhu | 7c1972f | 2018-02-22 21:43:52 -0800 | [diff] [blame] | 485 | public void startRecoverySessionWithCertPath_storesTheSessionInfo() throws Exception { |
| 486 | mRecoverableKeyStoreManager.startRecoverySessionWithCertPath( |
| 487 | TEST_SESSION_ID, |
Dmitry Dementyev | f34fc7e | 2018-03-26 17:31:29 -0700 | [diff] [blame] | 488 | TEST_DEFAULT_ROOT_CERT_ALIAS, |
Bo Zhu | 7c1972f | 2018-02-22 21:43:52 -0800 | [diff] [blame] | 489 | RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1), |
| 490 | TEST_VAULT_PARAMS, |
| 491 | TEST_VAULT_CHALLENGE, |
Dmitry Dementyev | 16d9db5 | 2018-03-26 11:31:46 -0700 | [diff] [blame] | 492 | ImmutableList.of(TEST_PROTECTION_PARAMS)); |
Bo Zhu | 7c1972f | 2018-02-22 21:43:52 -0800 | [diff] [blame] | 493 | |
| 494 | assertEquals(1, mRecoverySessionStorage.size()); |
| 495 | RecoverySessionStorage.Entry entry = |
| 496 | mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID); |
| 497 | assertArrayEquals(TEST_SECRET, entry.getLskfHash()); |
| 498 | assertEquals(KEY_CLAIMANT_LENGTH_BYTES, entry.getKeyClaimant().length); |
| 499 | } |
| 500 | |
| 501 | @Test |
| 502 | public void startRecoverySessionWithCertPath_checksPermissionFirst() throws Exception { |
| 503 | mRecoverableKeyStoreManager.startRecoverySessionWithCertPath( |
| 504 | TEST_SESSION_ID, |
Dmitry Dementyev | f34fc7e | 2018-03-26 17:31:29 -0700 | [diff] [blame] | 505 | TEST_DEFAULT_ROOT_CERT_ALIAS, |
Bo Zhu | 7c1972f | 2018-02-22 21:43:52 -0800 | [diff] [blame] | 506 | RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1), |
| 507 | TEST_VAULT_PARAMS, |
| 508 | TEST_VAULT_CHALLENGE, |
Dmitry Dementyev | 16d9db5 | 2018-03-26 11:31:46 -0700 | [diff] [blame] | 509 | ImmutableList.of(TEST_PROTECTION_PARAMS)); |
Bo Zhu | 7c1972f | 2018-02-22 21:43:52 -0800 | [diff] [blame] | 510 | |
| 511 | verify(mMockContext, times(2)) |
| 512 | .enforceCallingOrSelfPermission( |
| 513 | eq(Manifest.permission.RECOVER_KEYSTORE), any()); |
| 514 | } |
| 515 | |
| 516 | @Test |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 517 | public void startRecoverySession_storesTheSessionInfo() throws Exception { |
| 518 | mRecoverableKeyStoreManager.startRecoverySession( |
| 519 | TEST_SESSION_ID, |
| 520 | TEST_PUBLIC_KEY, |
| 521 | TEST_VAULT_PARAMS, |
| 522 | TEST_VAULT_CHALLENGE, |
Dmitry Dementyev | 16d9db5 | 2018-03-26 11:31:46 -0700 | [diff] [blame] | 523 | ImmutableList.of(TEST_PROTECTION_PARAMS)); |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 524 | |
| 525 | assertEquals(1, mRecoverySessionStorage.size()); |
Dmitry Dementyev | ad88471 | 2017-12-20 12:38:36 -0800 | [diff] [blame] | 526 | RecoverySessionStorage.Entry entry = |
Dmitry Dementyev | 1429831 | 2018-01-04 15:19:19 -0800 | [diff] [blame] | 527 | mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID); |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 528 | assertArrayEquals(TEST_SECRET, entry.getLskfHash()); |
| 529 | assertEquals(KEY_CLAIMANT_LENGTH_BYTES, entry.getKeyClaimant().length); |
| 530 | } |
| 531 | |
| 532 | @Test |
Robert Berry | 2bcdad9 | 2018-01-18 12:53:29 +0000 | [diff] [blame] | 533 | public void closeSession_closesASession() throws Exception { |
| 534 | mRecoverableKeyStoreManager.startRecoverySession( |
| 535 | TEST_SESSION_ID, |
| 536 | TEST_PUBLIC_KEY, |
| 537 | TEST_VAULT_PARAMS, |
| 538 | TEST_VAULT_CHALLENGE, |
Dmitry Dementyev | 16d9db5 | 2018-03-26 11:31:46 -0700 | [diff] [blame] | 539 | ImmutableList.of(TEST_PROTECTION_PARAMS)); |
Robert Berry | 2bcdad9 | 2018-01-18 12:53:29 +0000 | [diff] [blame] | 540 | |
| 541 | mRecoverableKeyStoreManager.closeSession(TEST_SESSION_ID); |
| 542 | |
| 543 | assertEquals(0, mRecoverySessionStorage.size()); |
| 544 | } |
| 545 | |
| 546 | @Test |
| 547 | public void closeSession_doesNotCloseUnrelatedSessions() throws Exception { |
| 548 | mRecoverableKeyStoreManager.startRecoverySession( |
| 549 | TEST_SESSION_ID, |
| 550 | TEST_PUBLIC_KEY, |
| 551 | TEST_VAULT_PARAMS, |
| 552 | TEST_VAULT_CHALLENGE, |
Dmitry Dementyev | 16d9db5 | 2018-03-26 11:31:46 -0700 | [diff] [blame] | 553 | ImmutableList.of(TEST_PROTECTION_PARAMS)); |
Robert Berry | 2bcdad9 | 2018-01-18 12:53:29 +0000 | [diff] [blame] | 554 | |
| 555 | mRecoverableKeyStoreManager.closeSession("some random session"); |
| 556 | |
| 557 | assertEquals(1, mRecoverySessionStorage.size()); |
| 558 | } |
| 559 | |
| 560 | @Test |
Dmitry Dementyev | 1e6a9dc | 2018-03-21 13:52:00 -0700 | [diff] [blame] | 561 | public void closeSession_throwsIfNullSession() throws Exception { |
| 562 | try { |
| 563 | mRecoverableKeyStoreManager.closeSession(/*sessionId=*/ null); |
| 564 | fail("should have thrown"); |
| 565 | } catch (NullPointerException e) { |
| 566 | assertThat(e.getMessage()).contains("invalid"); |
| 567 | } |
| 568 | } |
| 569 | |
| 570 | @Test |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 571 | public void startRecoverySession_throwsIfBadNumberOfSecrets() throws Exception { |
| 572 | try { |
| 573 | mRecoverableKeyStoreManager.startRecoverySession( |
| 574 | TEST_SESSION_ID, |
| 575 | TEST_PUBLIC_KEY, |
| 576 | TEST_VAULT_PARAMS, |
| 577 | TEST_VAULT_CHALLENGE, |
Dmitry Dementyev | 1429831 | 2018-01-04 15:19:19 -0800 | [diff] [blame] | 578 | ImmutableList.of()); |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 579 | fail("should have thrown"); |
Dmitry Dementyev | ae6ec6d | 2018-01-18 14:29:49 -0800 | [diff] [blame] | 580 | } catch (UnsupportedOperationException e) { |
Dmitry Dementyev | 77183ef | 2018-01-05 15:46:00 -0800 | [diff] [blame] | 581 | assertThat(e.getMessage()).startsWith( |
Dmitry Dementyev | 0916e7c | 2018-01-23 13:02:08 -0800 | [diff] [blame] | 582 | "Only a single KeyChainProtectionParams is supported"); |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 583 | } |
| 584 | } |
| 585 | |
| 586 | @Test |
Bo Zhu | def7ffd | 2018-01-05 14:50:52 -0800 | [diff] [blame] | 587 | public void startRecoverySession_throwsIfPublicKeysMismatch() throws Exception { |
| 588 | byte[] vaultParams = TEST_VAULT_PARAMS.clone(); |
| 589 | vaultParams[1] ^= (byte) 1; // Flip 1 bit |
Dmitry Dementyev | 16d9db5 | 2018-03-26 11:31:46 -0700 | [diff] [blame] | 590 | |
Bo Zhu | def7ffd | 2018-01-05 14:50:52 -0800 | [diff] [blame] | 591 | try { |
| 592 | mRecoverableKeyStoreManager.startRecoverySession( |
| 593 | TEST_SESSION_ID, |
| 594 | TEST_PUBLIC_KEY, |
| 595 | vaultParams, |
| 596 | TEST_VAULT_CHALLENGE, |
Dmitry Dementyev | 16d9db5 | 2018-03-26 11:31:46 -0700 | [diff] [blame] | 597 | ImmutableList.of(TEST_PROTECTION_PARAMS)); |
Bo Zhu | def7ffd | 2018-01-05 14:50:52 -0800 | [diff] [blame] | 598 | fail("should have thrown"); |
| 599 | } catch (ServiceSpecificException e) { |
| 600 | assertThat(e.getMessage()).contains("do not match"); |
| 601 | } |
| 602 | } |
| 603 | |
| 604 | @Test |
Bo Zhu | 7c1972f | 2018-02-22 21:43:52 -0800 | [diff] [blame] | 605 | public void startRecoverySessionWithCertPath_throwsIfBadNumberOfSecrets() throws Exception { |
| 606 | try { |
| 607 | mRecoverableKeyStoreManager.startRecoverySessionWithCertPath( |
| 608 | TEST_SESSION_ID, |
Dmitry Dementyev | f34fc7e | 2018-03-26 17:31:29 -0700 | [diff] [blame] | 609 | TEST_DEFAULT_ROOT_CERT_ALIAS, |
Bo Zhu | 7c1972f | 2018-02-22 21:43:52 -0800 | [diff] [blame] | 610 | RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1), |
| 611 | TEST_VAULT_PARAMS, |
| 612 | TEST_VAULT_CHALLENGE, |
| 613 | ImmutableList.of()); |
| 614 | fail("should have thrown"); |
| 615 | } catch (UnsupportedOperationException e) { |
| 616 | assertThat(e.getMessage()).startsWith( |
| 617 | "Only a single KeyChainProtectionParams is supported"); |
| 618 | } |
| 619 | } |
| 620 | |
| 621 | @Test |
| 622 | public void startRecoverySessionWithCertPath_throwsIfPublicKeysMismatch() throws Exception { |
| 623 | byte[] vaultParams = TEST_VAULT_PARAMS.clone(); |
| 624 | vaultParams[1] ^= (byte) 1; // Flip 1 bit |
| 625 | try { |
| 626 | mRecoverableKeyStoreManager.startRecoverySessionWithCertPath( |
| 627 | TEST_SESSION_ID, |
Dmitry Dementyev | f34fc7e | 2018-03-26 17:31:29 -0700 | [diff] [blame] | 628 | TEST_DEFAULT_ROOT_CERT_ALIAS, |
Bo Zhu | 7c1972f | 2018-02-22 21:43:52 -0800 | [diff] [blame] | 629 | RecoveryCertPath.createRecoveryCertPath(TestData.CERT_PATH_1), |
| 630 | vaultParams, |
| 631 | TEST_VAULT_CHALLENGE, |
Dmitry Dementyev | 16d9db5 | 2018-03-26 11:31:46 -0700 | [diff] [blame] | 632 | ImmutableList.of(TEST_PROTECTION_PARAMS)); |
Bo Zhu | 7c1972f | 2018-02-22 21:43:52 -0800 | [diff] [blame] | 633 | fail("should have thrown"); |
| 634 | } catch (ServiceSpecificException e) { |
| 635 | assertThat(e.getMessage()).contains("do not match"); |
| 636 | } |
| 637 | } |
| 638 | |
| 639 | @Test |
| 640 | public void startRecoverySessionWithCertPath_throwsIfEmptyCertPath() throws Exception { |
| 641 | CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); |
| 642 | CertPath emptyCertPath = certFactory.generateCertPath(new ArrayList<X509Certificate>()); |
| 643 | try { |
| 644 | mRecoverableKeyStoreManager.startRecoverySessionWithCertPath( |
| 645 | TEST_SESSION_ID, |
Dmitry Dementyev | f34fc7e | 2018-03-26 17:31:29 -0700 | [diff] [blame] | 646 | TEST_DEFAULT_ROOT_CERT_ALIAS, |
Bo Zhu | 7c1972f | 2018-02-22 21:43:52 -0800 | [diff] [blame] | 647 | RecoveryCertPath.createRecoveryCertPath(emptyCertPath), |
| 648 | TEST_VAULT_PARAMS, |
| 649 | TEST_VAULT_CHALLENGE, |
Dmitry Dementyev | 16d9db5 | 2018-03-26 11:31:46 -0700 | [diff] [blame] | 650 | ImmutableList.of(TEST_PROTECTION_PARAMS)); |
Bo Zhu | 7c1972f | 2018-02-22 21:43:52 -0800 | [diff] [blame] | 651 | fail("should have thrown"); |
| 652 | } catch (ServiceSpecificException e) { |
Bo Zhu | 7ce4ea5 | 2018-02-27 23:52:19 -0800 | [diff] [blame] | 653 | assertThat(e.getMessage()).contains("empty"); |
| 654 | } |
| 655 | } |
| 656 | |
| 657 | @Test |
| 658 | public void startRecoverySessionWithCertPath_throwsIfInvalidCertPath() throws Exception { |
| 659 | CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); |
| 660 | CertPath shortCertPath = certFactory.generateCertPath( |
| 661 | TestData.CERT_PATH_1.getCertificates() |
| 662 | .subList(0, TestData.CERT_PATH_1.getCertificates().size() - 1)); |
| 663 | try { |
| 664 | mRecoverableKeyStoreManager.startRecoverySessionWithCertPath( |
| 665 | TEST_SESSION_ID, |
Dmitry Dementyev | f34fc7e | 2018-03-26 17:31:29 -0700 | [diff] [blame] | 666 | TEST_DEFAULT_ROOT_CERT_ALIAS, |
Bo Zhu | 7ce4ea5 | 2018-02-27 23:52:19 -0800 | [diff] [blame] | 667 | RecoveryCertPath.createRecoveryCertPath(shortCertPath), |
| 668 | TEST_VAULT_PARAMS, |
| 669 | TEST_VAULT_CHALLENGE, |
Dmitry Dementyev | 16d9db5 | 2018-03-26 11:31:46 -0700 | [diff] [blame] | 670 | ImmutableList.of(TEST_PROTECTION_PARAMS)); |
Bo Zhu | 7ce4ea5 | 2018-02-27 23:52:19 -0800 | [diff] [blame] | 671 | fail("should have thrown"); |
| 672 | } catch (ServiceSpecificException e) { |
| 673 | // expected |
Bo Zhu | 7c1972f | 2018-02-22 21:43:52 -0800 | [diff] [blame] | 674 | } |
| 675 | } |
| 676 | |
| 677 | @Test |
Robert Berry | 4a5c87d | 2018-03-19 18:00:46 +0000 | [diff] [blame] | 678 | public void recoverKeyChainSnapshot_throwsIfNoSessionIsPresent() throws Exception { |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 679 | try { |
Dmitry Dementyev | fd4ae0b | 2018-03-23 11:06:24 -0700 | [diff] [blame] | 680 | WrappedApplicationKey applicationKey = new WrappedApplicationKey.Builder() |
| 681 | .setAlias(TEST_ALIAS) |
| 682 | .setEncryptedKeyMaterial(randomBytes(32)) |
| 683 | .build(); |
Robert Berry | 4a5c87d | 2018-03-19 18:00:46 +0000 | [diff] [blame] | 684 | mRecoverableKeyStoreManager.recoverKeyChainSnapshot( |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 685 | TEST_SESSION_ID, |
| 686 | /*recoveryKeyBlob=*/ randomBytes(32), |
Dmitry Dementyev | fd4ae0b | 2018-03-23 11:06:24 -0700 | [diff] [blame] | 687 | /*applicationKeys=*/ ImmutableList.of(applicationKey)); |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 688 | fail("should have thrown"); |
Dmitry Dementyev | 1429831 | 2018-01-04 15:19:19 -0800 | [diff] [blame] | 689 | } catch (ServiceSpecificException e) { |
| 690 | // expected |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 691 | } |
| 692 | } |
| 693 | |
| 694 | @Test |
Robert Berry | 4a5c87d | 2018-03-19 18:00:46 +0000 | [diff] [blame] | 695 | public void recoverKeyChainSnapshot_throwsIfRecoveryClaimCannotBeDecrypted() throws Exception { |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 696 | mRecoverableKeyStoreManager.startRecoverySession( |
| 697 | TEST_SESSION_ID, |
| 698 | TEST_PUBLIC_KEY, |
| 699 | TEST_VAULT_PARAMS, |
| 700 | TEST_VAULT_CHALLENGE, |
Dmitry Dementyev | 16d9db5 | 2018-03-26 11:31:46 -0700 | [diff] [blame] | 701 | ImmutableList.of(TEST_PROTECTION_PARAMS)); |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 702 | |
| 703 | try { |
Robert Berry | 4a5c87d | 2018-03-19 18:00:46 +0000 | [diff] [blame] | 704 | mRecoverableKeyStoreManager.recoverKeyChainSnapshot( |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 705 | TEST_SESSION_ID, |
| 706 | /*encryptedRecoveryKey=*/ randomBytes(60), |
Dmitry Dementyev | 1429831 | 2018-01-04 15:19:19 -0800 | [diff] [blame] | 707 | /*applicationKeys=*/ ImmutableList.of()); |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 708 | fail("should have thrown"); |
Dmitry Dementyev | 1429831 | 2018-01-04 15:19:19 -0800 | [diff] [blame] | 709 | } catch (ServiceSpecificException e) { |
| 710 | assertThat(e.getMessage()).startsWith("Failed to decrypt recovery key"); |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 711 | } |
| 712 | } |
| 713 | |
| 714 | @Test |
Robert Berry | 4a5c87d | 2018-03-19 18:00:46 +0000 | [diff] [blame] | 715 | public void recoverKeyChainSnapshot_throwsIfFailedToDecryptAllApplicationKeys() throws Exception { |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 716 | mRecoverableKeyStoreManager.startRecoverySession( |
| 717 | TEST_SESSION_ID, |
| 718 | TEST_PUBLIC_KEY, |
| 719 | TEST_VAULT_PARAMS, |
| 720 | TEST_VAULT_CHALLENGE, |
Dmitry Dementyev | 16d9db5 | 2018-03-26 11:31:46 -0700 | [diff] [blame] | 721 | ImmutableList.of(TEST_PROTECTION_PARAMS)); |
Dmitry Dementyev | 1429831 | 2018-01-04 15:19:19 -0800 | [diff] [blame] | 722 | byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID) |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 723 | .getKeyClaimant(); |
| 724 | SecretKey recoveryKey = randomRecoveryKey(); |
| 725 | byte[] encryptedClaimResponse = encryptClaimResponse( |
| 726 | keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey); |
Dmitry Dementyev | fd4ae0b | 2018-03-23 11:06:24 -0700 | [diff] [blame] | 727 | WrappedApplicationKey badApplicationKey = new WrappedApplicationKey.Builder() |
| 728 | .setAlias(TEST_ALIAS) |
| 729 | .setEncryptedKeyMaterial( |
| 730 | encryptedApplicationKey(randomRecoveryKey(), randomBytes(32))) |
| 731 | .build(); |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 732 | try { |
Robert Berry | 4a5c87d | 2018-03-19 18:00:46 +0000 | [diff] [blame] | 733 | mRecoverableKeyStoreManager.recoverKeyChainSnapshot( |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 734 | TEST_SESSION_ID, |
| 735 | /*encryptedRecoveryKey=*/ encryptedClaimResponse, |
Dmitry Dementyev | 1429831 | 2018-01-04 15:19:19 -0800 | [diff] [blame] | 736 | /*applicationKeys=*/ ImmutableList.of(badApplicationKey)); |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 737 | fail("should have thrown"); |
Bo Zhu | 3462c83 | 2018-01-04 22:42:36 -0800 | [diff] [blame] | 738 | } catch (ServiceSpecificException e) { |
Bo Zhu | 4857cb5 | 2018-02-06 14:34:48 -0800 | [diff] [blame] | 739 | assertThat(e.getMessage()).startsWith("Failed to recover any of the application keys"); |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 740 | } |
| 741 | } |
| 742 | |
| 743 | @Test |
Robert Berry | 4a5c87d | 2018-03-19 18:00:46 +0000 | [diff] [blame] | 744 | public void recoverKeyChainSnapshot_doesNotThrowIfNoApplicationKeysToBeDecrypted() |
| 745 | throws Exception { |
Bo Zhu | ae0682d | 2018-02-13 10:23:39 -0800 | [diff] [blame] | 746 | mRecoverableKeyStoreManager.startRecoverySession( |
| 747 | TEST_SESSION_ID, |
| 748 | TEST_PUBLIC_KEY, |
| 749 | TEST_VAULT_PARAMS, |
| 750 | TEST_VAULT_CHALLENGE, |
Dmitry Dementyev | 16d9db5 | 2018-03-26 11:31:46 -0700 | [diff] [blame] | 751 | ImmutableList.of(TEST_PROTECTION_PARAMS)); |
Bo Zhu | ae0682d | 2018-02-13 10:23:39 -0800 | [diff] [blame] | 752 | byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID) |
| 753 | .getKeyClaimant(); |
| 754 | SecretKey recoveryKey = randomRecoveryKey(); |
| 755 | byte[] encryptedClaimResponse = encryptClaimResponse( |
| 756 | keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey); |
| 757 | |
Robert Berry | 4a5c87d | 2018-03-19 18:00:46 +0000 | [diff] [blame] | 758 | mRecoverableKeyStoreManager.recoverKeyChainSnapshot( |
Bo Zhu | ae0682d | 2018-02-13 10:23:39 -0800 | [diff] [blame] | 759 | TEST_SESSION_ID, |
| 760 | /*encryptedRecoveryKey=*/ encryptedClaimResponse, |
| 761 | /*applicationKeys=*/ ImmutableList.of()); |
| 762 | } |
| 763 | |
| 764 | @Test |
Robert Berry | 4a5c87d | 2018-03-19 18:00:46 +0000 | [diff] [blame] | 765 | public void recoverKeyChainSnapshot_returnsDecryptedKeys() throws Exception { |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 766 | mRecoverableKeyStoreManager.startRecoverySession( |
| 767 | TEST_SESSION_ID, |
| 768 | TEST_PUBLIC_KEY, |
| 769 | TEST_VAULT_PARAMS, |
| 770 | TEST_VAULT_CHALLENGE, |
Dmitry Dementyev | 16d9db5 | 2018-03-26 11:31:46 -0700 | [diff] [blame] | 771 | ImmutableList.of(TEST_PROTECTION_PARAMS)); |
Dmitry Dementyev | 1429831 | 2018-01-04 15:19:19 -0800 | [diff] [blame] | 772 | byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID) |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 773 | .getKeyClaimant(); |
| 774 | SecretKey recoveryKey = randomRecoveryKey(); |
| 775 | byte[] encryptedClaimResponse = encryptClaimResponse( |
| 776 | keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey); |
Robert Berry | bd4c43c | 2017-12-22 11:35:14 +0000 | [diff] [blame] | 777 | byte[] applicationKeyBytes = randomBytes(32); |
Dmitry Dementyev | fd4ae0b | 2018-03-23 11:06:24 -0700 | [diff] [blame] | 778 | WrappedApplicationKey applicationKey = new WrappedApplicationKey.Builder() |
| 779 | .setAlias(TEST_ALIAS) |
| 780 | .setEncryptedKeyMaterial( |
| 781 | encryptedApplicationKey(recoveryKey, applicationKeyBytes)) |
| 782 | .build(); |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 783 | |
Robert Berry | 4a5c87d | 2018-03-19 18:00:46 +0000 | [diff] [blame] | 784 | Map<String, String> recoveredKeys = mRecoverableKeyStoreManager.recoverKeyChainSnapshot( |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 785 | TEST_SESSION_ID, |
| 786 | encryptedClaimResponse, |
Dmitry Dementyev | 1429831 | 2018-01-04 15:19:19 -0800 | [diff] [blame] | 787 | ImmutableList.of(applicationKey)); |
Robert Berry | bd4c43c | 2017-12-22 11:35:14 +0000 | [diff] [blame] | 788 | |
| 789 | assertThat(recoveredKeys).hasSize(1); |
Robert Berry | 4a5c87d | 2018-03-19 18:00:46 +0000 | [diff] [blame] | 790 | assertThat(recoveredKeys).containsKey(TEST_ALIAS); |
| 791 | // TODO(76083050) Test the grant mechanism for the keys. |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 792 | } |
| 793 | |
Dmitry Dementyev | 3b17c63 | 2017-12-21 17:30:48 -0800 | [diff] [blame] | 794 | @Test |
Robert Berry | 4a5c87d | 2018-03-19 18:00:46 +0000 | [diff] [blame] | 795 | public void recoverKeyChainSnapshot_worksOnOtherApplicationKeysIfOneDecryptionFails() throws Exception { |
Bo Zhu | 4857cb5 | 2018-02-06 14:34:48 -0800 | [diff] [blame] | 796 | mRecoverableKeyStoreManager.startRecoverySession( |
| 797 | TEST_SESSION_ID, |
| 798 | TEST_PUBLIC_KEY, |
| 799 | TEST_VAULT_PARAMS, |
| 800 | TEST_VAULT_CHALLENGE, |
Dmitry Dementyev | 16d9db5 | 2018-03-26 11:31:46 -0700 | [diff] [blame] | 801 | ImmutableList.of(TEST_PROTECTION_PARAMS)); |
Bo Zhu | 4857cb5 | 2018-02-06 14:34:48 -0800 | [diff] [blame] | 802 | byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID) |
| 803 | .getKeyClaimant(); |
| 804 | SecretKey recoveryKey = randomRecoveryKey(); |
| 805 | byte[] encryptedClaimResponse = encryptClaimResponse( |
| 806 | keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey); |
| 807 | |
| 808 | byte[] applicationKeyBytes1 = randomBytes(32); |
| 809 | byte[] applicationKeyBytes2 = randomBytes(32); |
Dmitry Dementyev | fd4ae0b | 2018-03-23 11:06:24 -0700 | [diff] [blame] | 810 | WrappedApplicationKey applicationKey1 = new WrappedApplicationKey.Builder() |
| 811 | .setAlias(TEST_ALIAS) |
| 812 | // Use a different recovery key here, so the decryption will fail |
| 813 | .setEncryptedKeyMaterial( |
| 814 | encryptedApplicationKey(randomRecoveryKey(), applicationKeyBytes1)) |
| 815 | .build(); |
| 816 | WrappedApplicationKey applicationKey2 = new WrappedApplicationKey.Builder() |
| 817 | .setAlias(TEST_ALIAS2) |
| 818 | .setEncryptedKeyMaterial( |
| 819 | encryptedApplicationKey(recoveryKey, applicationKeyBytes2)) |
| 820 | .build(); |
Bo Zhu | 4857cb5 | 2018-02-06 14:34:48 -0800 | [diff] [blame] | 821 | |
Robert Berry | 4a5c87d | 2018-03-19 18:00:46 +0000 | [diff] [blame] | 822 | Map<String, String> recoveredKeys = mRecoverableKeyStoreManager.recoverKeyChainSnapshot( |
Bo Zhu | 4857cb5 | 2018-02-06 14:34:48 -0800 | [diff] [blame] | 823 | TEST_SESSION_ID, |
| 824 | encryptedClaimResponse, |
| 825 | ImmutableList.of(applicationKey1, applicationKey2)); |
| 826 | |
| 827 | assertThat(recoveredKeys).hasSize(1); |
Robert Berry | 4a5c87d | 2018-03-19 18:00:46 +0000 | [diff] [blame] | 828 | assertThat(recoveredKeys).containsKey(TEST_ALIAS2); |
| 829 | // TODO(76083050) Test the grant mechanism for the keys. |
Bo Zhu | 4857cb5 | 2018-02-06 14:34:48 -0800 | [diff] [blame] | 830 | } |
| 831 | |
| 832 | @Test |
Dmitry Dementyev | 3b17c63 | 2017-12-21 17:30:48 -0800 | [diff] [blame] | 833 | public void setSnapshotCreatedPendingIntent() throws Exception { |
| 834 | int uid = Binder.getCallingUid(); |
| 835 | PendingIntent intent = PendingIntent.getBroadcast( |
| 836 | InstrumentationRegistry.getTargetContext(), /*requestCode=*/1, |
| 837 | new Intent(), /*flags=*/ 0); |
Dmitry Dementyev | 1429831 | 2018-01-04 15:19:19 -0800 | [diff] [blame] | 838 | mRecoverableKeyStoreManager.setSnapshotCreatedPendingIntent(intent); |
Dmitry Dementyev | 3b17c63 | 2017-12-21 17:30:48 -0800 | [diff] [blame] | 839 | verify(mMockListenersStorage).setSnapshotListener(eq(uid), any(PendingIntent.class)); |
| 840 | } |
| 841 | |
Dmitry Dementyev | ad88471 | 2017-12-20 12:38:36 -0800 | [diff] [blame] | 842 | @Test |
Robert Berry | 8f9038c | 2018-03-26 11:36:40 +0100 | [diff] [blame] | 843 | public void setServerParams_updatesServerParams() throws Exception { |
| 844 | int uid = Binder.getCallingUid(); |
| 845 | int userId = UserHandle.getCallingUserId(); |
| 846 | byte[] serverParams = new byte[] { 1 }; |
| 847 | |
| 848 | mRecoverableKeyStoreManager.setServerParams(serverParams); |
| 849 | |
| 850 | assertThat(mRecoverableKeyStoreDb.getServerParams(userId, uid)).isEqualTo(serverParams); |
| 851 | } |
| 852 | |
| 853 | @Test |
| 854 | public void setServerParams_doesNotSetSnapshotPendingIfInitializing() throws Exception { |
| 855 | int uid = Binder.getCallingUid(); |
| 856 | int userId = UserHandle.getCallingUserId(); |
| 857 | byte[] serverParams = new byte[] { 1 }; |
| 858 | |
| 859 | mRecoverableKeyStoreManager.setServerParams(serverParams); |
| 860 | |
| 861 | assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); |
| 862 | } |
| 863 | |
| 864 | @Test |
| 865 | public void setServerParams_doesNotSetSnapshotPendingIfSettingSameValue() throws Exception { |
| 866 | int uid = Binder.getCallingUid(); |
| 867 | int userId = UserHandle.getCallingUserId(); |
| 868 | byte[] serverParams = new byte[] { 1 }; |
| 869 | |
| 870 | mRecoverableKeyStoreManager.setServerParams(serverParams); |
| 871 | mRecoverableKeyStoreManager.setServerParams(serverParams); |
| 872 | |
| 873 | assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); |
| 874 | } |
| 875 | |
| 876 | @Test |
| 877 | public void setServerParams_setsSnapshotPendingIfUpdatingValue() throws Exception { |
| 878 | int uid = Binder.getCallingUid(); |
| 879 | int userId = UserHandle.getCallingUserId(); |
| 880 | |
| 881 | mRecoverableKeyStoreManager.setServerParams(new byte[] { 1 }); |
| 882 | mRecoverableKeyStoreManager.setServerParams(new byte[] { 2 }); |
| 883 | |
| 884 | assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue(); |
| 885 | } |
| 886 | |
| 887 | @Test |
Robert Berry | 5a1acefb | 2018-03-26 14:41:30 +0100 | [diff] [blame] | 888 | public void setRecoverySecretTypes_updatesSecretTypes() throws Exception { |
Dmitry Dementyev | bdfdf53 | 2017-12-27 11:58:45 -0800 | [diff] [blame] | 889 | int[] types1 = new int[]{11, 2000}; |
| 890 | int[] types2 = new int[]{1, 2, 3}; |
| 891 | int[] types3 = new int[]{}; |
| 892 | |
Dmitry Dementyev | 1429831 | 2018-01-04 15:19:19 -0800 | [diff] [blame] | 893 | mRecoverableKeyStoreManager.setRecoverySecretTypes(types1); |
| 894 | assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes()).isEqualTo( |
Dmitry Dementyev | bdfdf53 | 2017-12-27 11:58:45 -0800 | [diff] [blame] | 895 | types1); |
| 896 | |
Dmitry Dementyev | 1429831 | 2018-01-04 15:19:19 -0800 | [diff] [blame] | 897 | mRecoverableKeyStoreManager.setRecoverySecretTypes(types2); |
| 898 | assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes()).isEqualTo( |
Dmitry Dementyev | bdfdf53 | 2017-12-27 11:58:45 -0800 | [diff] [blame] | 899 | types2); |
| 900 | |
Dmitry Dementyev | 1429831 | 2018-01-04 15:19:19 -0800 | [diff] [blame] | 901 | mRecoverableKeyStoreManager.setRecoverySecretTypes(types3); |
| 902 | assertThat(mRecoverableKeyStoreManager.getRecoverySecretTypes()).isEqualTo( |
Dmitry Dementyev | bdfdf53 | 2017-12-27 11:58:45 -0800 | [diff] [blame] | 903 | types3); |
| 904 | } |
| 905 | |
| 906 | @Test |
Robert Berry | 5a1acefb | 2018-03-26 14:41:30 +0100 | [diff] [blame] | 907 | public void setRecoverySecretTypes_doesNotSetSnapshotPendingIfIniting() throws Exception { |
| 908 | int uid = Binder.getCallingUid(); |
| 909 | int userId = UserHandle.getCallingUserId(); |
| 910 | int[] secretTypes = new int[] { 101 }; |
| 911 | |
| 912 | mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes); |
| 913 | |
| 914 | assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); |
| 915 | } |
| 916 | |
| 917 | @Test |
| 918 | public void setRecoverySecretTypes_doesNotSetSnapshotPendingIfSettingSameValue() |
| 919 | throws Exception { |
| 920 | int uid = Binder.getCallingUid(); |
| 921 | int userId = UserHandle.getCallingUserId(); |
| 922 | int[] secretTypes = new int[] { 101 }; |
| 923 | |
| 924 | mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes); |
| 925 | mRecoverableKeyStoreManager.setRecoverySecretTypes(secretTypes); |
| 926 | |
| 927 | assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isFalse(); |
| 928 | } |
| 929 | |
| 930 | @Test |
| 931 | public void setRecoverySecretTypes_setsSnapshotPendingIfUpdatingValue() throws Exception { |
| 932 | int uid = Binder.getCallingUid(); |
| 933 | int userId = UserHandle.getCallingUserId(); |
| 934 | |
| 935 | mRecoverableKeyStoreManager.setRecoverySecretTypes(new int[] { 101 }); |
| 936 | mRecoverableKeyStoreManager.setRecoverySecretTypes(new int[] { 102 }); |
| 937 | |
| 938 | assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue(); |
| 939 | } |
| 940 | |
| 941 | @Test |
Dmitry Dementyev | 1e6a9dc | 2018-03-21 13:52:00 -0700 | [diff] [blame] | 942 | public void setRecoverySecretTypes_throwsIfNullTypes() throws Exception { |
| 943 | try { |
| 944 | mRecoverableKeyStoreManager.setRecoverySecretTypes(/*types=*/ null); |
| 945 | fail("should have thrown"); |
| 946 | } catch (NullPointerException e) { |
| 947 | assertThat(e.getMessage()).contains("is null"); |
| 948 | } |
| 949 | } |
| 950 | |
| 951 | @Test |
Dmitry Dementyev | 40dadb0 | 2018-01-10 18:03:37 -0800 | [diff] [blame] | 952 | public void setRecoverySecretTypes_updatesShouldCreateSnapshot() throws Exception { |
| 953 | int uid = Binder.getCallingUid(); |
| 954 | int userId = UserHandle.getCallingUserId(); |
Robert Berry | e8edf97 | 2018-03-27 11:45:11 +0100 | [diff] [blame] | 955 | mRecoverableKeyStoreManager.setRecoverySecretTypes(new int[] { 1 }); |
Dmitry Dementyev | 40dadb0 | 2018-01-10 18:03:37 -0800 | [diff] [blame] | 956 | |
| 957 | mRecoverableKeyStoreManager.generateAndStoreKey(TEST_ALIAS); |
| 958 | // Pretend that key was synced |
| 959 | mRecoverableKeyStoreDb.setShouldCreateSnapshot(userId, uid, false); |
Robert Berry | e8edf97 | 2018-03-27 11:45:11 +0100 | [diff] [blame] | 960 | mRecoverableKeyStoreManager.setRecoverySecretTypes(new int[] { 2 }); |
Dmitry Dementyev | 40dadb0 | 2018-01-10 18:03:37 -0800 | [diff] [blame] | 961 | |
| 962 | assertThat(mRecoverableKeyStoreDb.getShouldCreateSnapshot(userId, uid)).isTrue(); |
| 963 | } |
| 964 | |
| 965 | @Test |
Robert Berry | bbe02ae | 2018-02-20 19:47:43 +0000 | [diff] [blame] | 966 | public void setRecoveryStatus() throws Exception { |
Dmitry Dementyev | ad88471 | 2017-12-20 12:38:36 -0800 | [diff] [blame] | 967 | int userId = UserHandle.getCallingUserId(); |
| 968 | int uid = Binder.getCallingUid(); |
| 969 | int status = 100; |
| 970 | int status2 = 200; |
| 971 | String alias = "key1"; |
| 972 | WrappedKey wrappedKey = new WrappedKey(NONCE, KEY_MATERIAL, GENERATION_ID, status); |
| 973 | mRecoverableKeyStoreDb.insertKey(userId, uid, alias, wrappedKey); |
| 974 | Map<String, Integer> statuses = |
Robert Berry | 56f06b4 | 2018-02-23 13:31:32 +0000 | [diff] [blame] | 975 | mRecoverableKeyStoreManager.getRecoveryStatus(); |
Dmitry Dementyev | ad88471 | 2017-12-20 12:38:36 -0800 | [diff] [blame] | 976 | assertThat(statuses).hasSize(1); |
| 977 | assertThat(statuses).containsEntry(alias, status); |
| 978 | |
Robert Berry | bbe02ae | 2018-02-20 19:47:43 +0000 | [diff] [blame] | 979 | mRecoverableKeyStoreManager.setRecoveryStatus(alias, status2); |
Robert Berry | 56f06b4 | 2018-02-23 13:31:32 +0000 | [diff] [blame] | 980 | statuses = mRecoverableKeyStoreManager.getRecoveryStatus(); |
Dmitry Dementyev | ad88471 | 2017-12-20 12:38:36 -0800 | [diff] [blame] | 981 | assertThat(statuses).hasSize(1); |
| 982 | assertThat(statuses).containsEntry(alias, status2); // updated |
| 983 | } |
| 984 | |
Dmitry Dementyev | 1e6a9dc | 2018-03-21 13:52:00 -0700 | [diff] [blame] | 985 | @Test |
| 986 | public void setRecoveryStatus_throwsIfNullAlias() throws Exception { |
| 987 | try { |
| 988 | mRecoverableKeyStoreManager.setRecoveryStatus(/*alias=*/ null, /*status=*/ 100); |
| 989 | fail("should have thrown"); |
| 990 | } catch (NullPointerException e) { |
| 991 | assertThat(e.getMessage()).contains("is null"); |
| 992 | } |
| 993 | } |
| 994 | |
Robert Berry | bd4c43c | 2017-12-22 11:35:14 +0000 | [diff] [blame] | 995 | private static byte[] encryptedApplicationKey( |
| 996 | SecretKey recoveryKey, byte[] applicationKey) throws Exception { |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 997 | return KeySyncUtils.encryptKeysWithRecoveryKey(recoveryKey, ImmutableMap.of( |
Dmitry Dementyev | fd4ae0b | 2018-03-23 11:06:24 -0700 | [diff] [blame] | 998 | TEST_ALIAS, new SecretKeySpec(applicationKey, "AES") |
| 999 | )).get(TEST_ALIAS); |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 1000 | } |
| 1001 | |
| 1002 | private static byte[] encryptClaimResponse( |
| 1003 | byte[] keyClaimant, |
| 1004 | byte[] lskfHash, |
| 1005 | byte[] vaultParams, |
| 1006 | SecretKey recoveryKey) throws Exception { |
| 1007 | byte[] locallyEncryptedRecoveryKey = KeySyncUtils.locallyEncryptRecoveryKey( |
| 1008 | lskfHash, recoveryKey); |
| 1009 | return SecureBox.encrypt( |
| 1010 | /*theirPublicKey=*/ null, |
| 1011 | /*sharedSecret=*/ keyClaimant, |
| 1012 | /*header=*/ KeySyncUtils.concat(RECOVERY_RESPONSE_HEADER, vaultParams), |
| 1013 | /*payload=*/ locallyEncryptedRecoveryKey); |
| 1014 | } |
| 1015 | |
| 1016 | private static SecretKey randomRecoveryKey() { |
| 1017 | return new SecretKeySpec(randomBytes(32), "AES"); |
| 1018 | } |
| 1019 | |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 1020 | private static byte[] getUtf8Bytes(String s) { |
| 1021 | return s.getBytes(StandardCharsets.UTF_8); |
| 1022 | } |
Robert Berry | b9a220b | 2017-12-21 12:41:01 +0000 | [diff] [blame] | 1023 | |
| 1024 | private static byte[] randomBytes(int n) { |
| 1025 | byte[] bytes = new byte[n]; |
| 1026 | new Random().nextBytes(bytes); |
| 1027 | return bytes; |
| 1028 | } |
Bo Zhu | 3462c83 | 2018-01-04 22:42:36 -0800 | [diff] [blame] | 1029 | |
| 1030 | private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception { |
| 1031 | KeyGenerator keyGenerator = KeyGenerator.getInstance( |
| 1032 | KEY_ALGORITHM, |
| 1033 | ANDROID_KEY_STORE_PROVIDER); |
| 1034 | keyGenerator.init(new KeyGenParameterSpec.Builder( |
| 1035 | WRAPPING_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) |
| 1036 | .setBlockModes(KeyProperties.BLOCK_MODE_GCM) |
| 1037 | .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) |
| 1038 | .build()); |
| 1039 | return (AndroidKeyStoreSecretKey) keyGenerator.generateKey(); |
| 1040 | } |
Robert Berry | e16fa98 | 2017-12-20 15:59:37 +0000 | [diff] [blame] | 1041 | } |