1 /* 2 * Copyright (C) 2018 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.serialization; 18 19 import static com.google.common.truth.Truth.assertThat; 20 21 import android.security.keystore.recovery.KeyChainProtectionParams; 22 import android.security.keystore.recovery.KeyChainSnapshot; 23 import android.security.keystore.recovery.KeyDerivationParams; 24 import android.security.keystore.recovery.WrappedApplicationKey; 25 26 import androidx.test.filters.SmallTest; 27 import androidx.test.runner.AndroidJUnit4; 28 29 import com.android.server.locksettings.recoverablekeystore.TestData; 30 31 import org.junit.Test; 32 import org.junit.runner.RunWith; 33 34 import java.io.ByteArrayInputStream; 35 import java.io.ByteArrayOutputStream; 36 import java.security.cert.CertPath; 37 import java.util.ArrayList; 38 import java.util.List; 39 40 @SmallTest 41 @RunWith(AndroidJUnit4.class) 42 public class KeyChainSnapshotSerializerTest { 43 private static final int COUNTER_ID = 2134; 44 private static final int SNAPSHOT_VERSION = 125; 45 private static final int MAX_ATTEMPTS = 21; 46 private static final byte[] SERVER_PARAMS = new byte[] { 8, 2, 4 }; 47 private static final byte[] KEY_BLOB = new byte[] { 124, 53, 53, 53 }; 48 private static final CertPath CERT_PATH = TestData.CERT_PATH_1; 49 private static final int SECRET_TYPE = KeyChainProtectionParams.TYPE_LOCKSCREEN; 50 private static final int LOCK_SCREEN_UI = KeyChainProtectionParams.UI_FORMAT_PASSWORD; 51 private static final byte[] SALT = new byte[] { 5, 4, 3, 2, 1 }; 52 private static final int MEMORY_DIFFICULTY = 45; 53 private static final int ALGORITHM = KeyDerivationParams.ALGORITHM_SCRYPT; 54 private static final byte[] SECRET = new byte[] { 1, 2, 3, 4 }; 55 56 private static final String TEST_KEY_1_ALIAS = "key1"; 57 private static final byte[] TEST_KEY_1_BYTES = new byte[] { 66, 77, 88 }; 58 private static final byte[] TEST_KEY_1_METADATA = new byte[] { 89, 87 }; 59 60 private static final String TEST_KEY_2_ALIAS = "key2"; 61 private static final byte[] TEST_KEY_2_BYTES = new byte[] { 99, 33, 11 }; 62 private static final byte[] TEST_KEY_2_METADATA = new byte[] {}; 63 64 private static final String TEST_KEY_3_ALIAS = "key3"; 65 private static final byte[] TEST_KEY_3_BYTES = new byte[] { 2, 8, 100 }; 66 private static final byte[] TEST_KEY_3_METADATA = new byte[] { 121 }; 67 68 @Test roundTrip_persistsCounterId()69 public void roundTrip_persistsCounterId() throws Exception { 70 assertThat(roundTrip().getCounterId()).isEqualTo(COUNTER_ID); 71 } 72 73 @Test roundTrip_persistsSnapshotVersion()74 public void roundTrip_persistsSnapshotVersion() throws Exception { 75 assertThat(roundTrip().getSnapshotVersion()).isEqualTo(SNAPSHOT_VERSION); 76 } 77 78 @Test roundTrip_persistsMaxAttempts()79 public void roundTrip_persistsMaxAttempts() throws Exception { 80 assertThat(roundTrip().getMaxAttempts()).isEqualTo(MAX_ATTEMPTS); 81 } 82 83 @Test roundTrip_persistsRecoveryKey()84 public void roundTrip_persistsRecoveryKey() throws Exception { 85 assertThat(roundTrip().getEncryptedRecoveryKeyBlob()).isEqualTo(KEY_BLOB); 86 } 87 88 @Test roundTrip_persistsServerParams()89 public void roundTrip_persistsServerParams() throws Exception { 90 assertThat(roundTrip().getServerParams()).isEqualTo(SERVER_PARAMS); 91 } 92 93 @Test roundTrip_persistsCertPath()94 public void roundTrip_persistsCertPath() throws Exception { 95 assertThat(roundTrip().getTrustedHardwareCertPath()).isEqualTo(CERT_PATH); 96 } 97 98 @Test roundTrip_persistsParamsList()99 public void roundTrip_persistsParamsList() throws Exception { 100 assertThat(roundTrip().getKeyChainProtectionParams()).hasSize(1); 101 } 102 103 @Test roundTripParams_persistsUserSecretType()104 public void roundTripParams_persistsUserSecretType() throws Exception { 105 assertThat(roundTripParams().getUserSecretType()).isEqualTo(SECRET_TYPE); 106 } 107 108 @Test roundTripParams_persistsLockScreenUi()109 public void roundTripParams_persistsLockScreenUi() throws Exception { 110 assertThat(roundTripParams().getLockScreenUiFormat()).isEqualTo(LOCK_SCREEN_UI); 111 } 112 113 @Test roundTripParams_persistsSalt()114 public void roundTripParams_persistsSalt() throws Exception { 115 assertThat(roundTripParams().getKeyDerivationParams().getSalt()).isEqualTo(SALT); 116 } 117 118 @Test roundTripParams_persistsAlgorithm()119 public void roundTripParams_persistsAlgorithm() throws Exception { 120 assertThat(roundTripParams().getKeyDerivationParams().getAlgorithm()).isEqualTo(ALGORITHM); 121 } 122 123 @Test roundTripParams_persistsMemoryDifficulty()124 public void roundTripParams_persistsMemoryDifficulty() throws Exception { 125 assertThat(roundTripParams().getKeyDerivationParams().getMemoryDifficulty()) 126 .isEqualTo(MEMORY_DIFFICULTY); 127 } 128 129 @Test roundTripParams_doesNotPersistSecret()130 public void roundTripParams_doesNotPersistSecret() throws Exception { 131 assertThat(roundTripParams().getSecret()).isEmpty(); 132 } 133 134 @Test roundTripKeys_hasCorrectLength()135 public void roundTripKeys_hasCorrectLength() throws Exception { 136 assertThat(roundTripKeys()).hasSize(3); 137 } 138 139 @Test roundTripKeys_0_persistsAlias()140 public void roundTripKeys_0_persistsAlias() throws Exception { 141 assertThat(roundTripKeys().get(0).getAlias()).isEqualTo(TEST_KEY_1_ALIAS); 142 } 143 144 @Test roundTripKeys_0_persistsKeyBytes()145 public void roundTripKeys_0_persistsKeyBytes() throws Exception { 146 assertThat(roundTripKeys().get(0).getEncryptedKeyMaterial()).isEqualTo(TEST_KEY_1_BYTES); 147 } 148 149 @Test roundTripKeys_0_persistsKeyMetadata_absent()150 public void roundTripKeys_0_persistsKeyMetadata_absent() throws Exception { 151 assertThat(roundTripKeys(/*withKeyMetadata=*/ false).get(0).getMetadata()).isEqualTo(null); 152 } 153 154 @Test roundTripKeys_0_persistsKeyMetadata_present()155 public void roundTripKeys_0_persistsKeyMetadata_present() throws Exception { 156 assertThat(roundTripKeys(/*withKeyMetadata=*/ true).get(0).getMetadata()) 157 .isEqualTo(TEST_KEY_1_METADATA); 158 } 159 160 @Test roundTripKeys_1_persistsAlias()161 public void roundTripKeys_1_persistsAlias() throws Exception { 162 assertThat(roundTripKeys().get(1).getAlias()).isEqualTo(TEST_KEY_2_ALIAS); 163 } 164 165 @Test roundTripKeys_1_persistsKeyBytes()166 public void roundTripKeys_1_persistsKeyBytes() throws Exception { 167 assertThat(roundTripKeys().get(1).getEncryptedKeyMaterial()).isEqualTo(TEST_KEY_2_BYTES); 168 } 169 170 @Test roundTripKeys_1_persistsKeyMetadata_absent()171 public void roundTripKeys_1_persistsKeyMetadata_absent() throws Exception { 172 assertThat(roundTripKeys(/*withKeyMetadata=*/ false).get(1).getMetadata()).isEqualTo(null); 173 } 174 175 @Test roundTripKeys_1_persistsKeyMetadata_present()176 public void roundTripKeys_1_persistsKeyMetadata_present() throws Exception { 177 assertThat(roundTripKeys(/*withKeyMetadata=*/ true).get(1).getMetadata()) 178 .isEqualTo(TEST_KEY_2_METADATA); 179 } 180 181 @Test roundTripKeys_2_persistsAlias()182 public void roundTripKeys_2_persistsAlias() throws Exception { 183 assertThat(roundTripKeys().get(2).getAlias()).isEqualTo(TEST_KEY_3_ALIAS); 184 } 185 186 @Test roundTripKeys_2_persistsKeyBytes()187 public void roundTripKeys_2_persistsKeyBytes() throws Exception { 188 assertThat(roundTripKeys().get(2).getEncryptedKeyMaterial()).isEqualTo(TEST_KEY_3_BYTES); 189 } 190 191 @Test roundTripKeys_2_persistsKeyMetadata_absent()192 public void roundTripKeys_2_persistsKeyMetadata_absent() throws Exception { 193 assertThat(roundTripKeys(/*withKeyMetadata=*/ false).get(2).getMetadata()).isEqualTo(null); 194 } 195 196 @Test roundTripKeys_2_persistsKeyMetadata_present()197 public void roundTripKeys_2_persistsKeyMetadata_present() throws Exception { 198 assertThat(roundTripKeys(/*withKeyMetadata=*/ true).get(2).getMetadata()) 199 .isEqualTo(TEST_KEY_3_METADATA); 200 } 201 202 @Test serialize_doesNotThrowForTestSnapshotWithoutKeyMetadata()203 public void serialize_doesNotThrowForTestSnapshotWithoutKeyMetadata() throws Exception { 204 KeyChainSnapshotSerializer.serialize( 205 createTestKeyChainSnapshot(/*withKeyMetadata=*/ false), 206 new ByteArrayOutputStream()); 207 } 208 209 @Test serialize_doesNotThrowForTestSnapshotWithKeyMetadata()210 public void serialize_doesNotThrowForTestSnapshotWithKeyMetadata() throws Exception { 211 KeyChainSnapshotSerializer.serialize( 212 createTestKeyChainSnapshotWithKeyMetadata(), new ByteArrayOutputStream()); 213 } 214 roundTripKeys()215 private static List<WrappedApplicationKey> roundTripKeys() throws Exception { 216 return roundTrip().getWrappedApplicationKeys(); 217 } 218 roundTripKeys(boolean withKeyMetadata)219 private static List<WrappedApplicationKey> roundTripKeys(boolean withKeyMetadata) 220 throws Exception { 221 return roundTrip(withKeyMetadata).getWrappedApplicationKeys(); 222 } 223 roundTripParams()224 private static KeyChainProtectionParams roundTripParams() throws Exception { 225 return roundTrip(/*withKeyMetadata=*/ false).getKeyChainProtectionParams().get(0); 226 } 227 roundTrip()228 public static KeyChainSnapshot roundTrip() throws Exception { 229 return roundTrip(/*withKeyMetadata=*/ false); 230 } 231 roundTrip(boolean withKeyMetadata)232 public static KeyChainSnapshot roundTrip(boolean withKeyMetadata) throws Exception { 233 KeyChainSnapshot snapshot = createTestKeyChainSnapshot(withKeyMetadata); 234 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 235 KeyChainSnapshotSerializer.serialize(snapshot, byteArrayOutputStream); 236 return KeyChainSnapshotDeserializer.deserialize( 237 new ByteArrayInputStream(byteArrayOutputStream.toByteArray())); 238 } 239 createTestKeyChainSnapshot(boolean withKeyMetadata)240 private static KeyChainSnapshot createTestKeyChainSnapshot(boolean withKeyMetadata) 241 throws Exception { 242 KeyChainSnapshot.Builder builder = new KeyChainSnapshot.Builder() 243 .setCounterId(COUNTER_ID) 244 .setSnapshotVersion(SNAPSHOT_VERSION) 245 .setServerParams(SERVER_PARAMS) 246 .setMaxAttempts(MAX_ATTEMPTS) 247 .setEncryptedRecoveryKeyBlob(KEY_BLOB) 248 .setKeyChainProtectionParams(createKeyChainProtectionParamsList()) 249 .setTrustedHardwareCertPath(CERT_PATH); 250 if (withKeyMetadata) { 251 builder.setWrappedApplicationKeys(createKeysWithMetadata()); 252 } else { 253 builder.setWrappedApplicationKeys(createKeysWithoutMetadata()); 254 } 255 return builder.build(); 256 } 257 createTestKeyChainSnapshotWithKeyMetadata()258 private static KeyChainSnapshot createTestKeyChainSnapshotWithKeyMetadata() 259 throws Exception { 260 return new KeyChainSnapshot.Builder() 261 .setCounterId(COUNTER_ID) 262 .setSnapshotVersion(SNAPSHOT_VERSION) 263 .setServerParams(SERVER_PARAMS) 264 .setMaxAttempts(MAX_ATTEMPTS) 265 .setEncryptedRecoveryKeyBlob(KEY_BLOB) 266 .setKeyChainProtectionParams(createKeyChainProtectionParamsList()) 267 .setWrappedApplicationKeys(createKeysWithMetadata()) 268 .setTrustedHardwareCertPath(CERT_PATH) 269 .build(); 270 } 271 createKeysWithoutMetadata()272 private static List<WrappedApplicationKey> createKeysWithoutMetadata() { 273 ArrayList<WrappedApplicationKey> keyList = new ArrayList<>(); 274 keyList.add(createKey(TEST_KEY_1_ALIAS, TEST_KEY_1_BYTES, /*metadata=*/ null)); 275 keyList.add(createKey(TEST_KEY_2_ALIAS, TEST_KEY_2_BYTES, /*metadata=*/ null)); 276 keyList.add(createKey(TEST_KEY_3_ALIAS, TEST_KEY_3_BYTES, /*metadata=*/ null)); 277 return keyList; 278 } 279 createKeysWithMetadata()280 private static List<WrappedApplicationKey> createKeysWithMetadata() { 281 ArrayList<WrappedApplicationKey> keyList = new ArrayList<>(); 282 keyList.add(createKey(TEST_KEY_1_ALIAS, TEST_KEY_1_BYTES, TEST_KEY_1_METADATA)); 283 keyList.add(createKey(TEST_KEY_2_ALIAS, TEST_KEY_2_BYTES, TEST_KEY_2_METADATA)); 284 keyList.add(createKey(TEST_KEY_3_ALIAS, TEST_KEY_3_BYTES, TEST_KEY_3_METADATA)); 285 return keyList; 286 } 287 createKeyChainProtectionParamsList()288 private static List<KeyChainProtectionParams> createKeyChainProtectionParamsList() { 289 KeyDerivationParams keyDerivationParams = 290 KeyDerivationParams.createScryptParams(SALT, MEMORY_DIFFICULTY); 291 KeyChainProtectionParams keyChainProtectionParams = new KeyChainProtectionParams.Builder() 292 .setKeyDerivationParams(keyDerivationParams) 293 .setUserSecretType(SECRET_TYPE) 294 .setLockScreenUiFormat(LOCK_SCREEN_UI) 295 .setSecret(SECRET) 296 .build(); 297 ArrayList<KeyChainProtectionParams> keyChainProtectionParamsList = 298 new ArrayList<>(1); 299 keyChainProtectionParamsList.add(keyChainProtectionParams); 300 return keyChainProtectionParamsList; 301 } 302 createKey(String alias, byte[] bytes, byte[] metadata)303 private static WrappedApplicationKey createKey(String alias, byte[] bytes, byte[] metadata) { 304 WrappedApplicationKey.Builder builder = new WrappedApplicationKey.Builder() 305 .setAlias(alias) 306 .setEncryptedKeyMaterial(bytes); 307 if (metadata != null) { 308 builder.setMetadata(metadata); 309 } 310 return builder.build(); 311 } 312 } 313