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
17package com.android.server.locksettings.recoverablekeystore.serialization;
18
19import static com.google.common.truth.Truth.assertThat;
20
21import android.security.keystore.recovery.KeyChainProtectionParams;
22import android.security.keystore.recovery.KeyChainSnapshot;
23import android.security.keystore.recovery.KeyDerivationParams;
24import android.security.keystore.recovery.WrappedApplicationKey;
25import android.support.test.InstrumentationRegistry;
26import android.support.test.filters.SmallTest;
27import android.support.test.runner.AndroidJUnit4;
28
29import com.android.server.locksettings.recoverablekeystore.TestData;
30
31import org.junit.Test;
32import org.junit.runner.RunWith;
33
34import java.io.ByteArrayInputStream;
35import java.io.ByteArrayOutputStream;
36import java.security.cert.CertPath;
37import java.util.ArrayList;
38import java.util.List;
39
40@SmallTest
41@RunWith(AndroidJUnit4.class)
42public 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
59    private static final String TEST_KEY_2_ALIAS = "key2";
60    private static final byte[] TEST_KEY_2_BYTES = new byte[] { 99, 33, 11 };
61
62    private static final String TEST_KEY_3_ALIAS = "key3";
63    private static final byte[] TEST_KEY_3_BYTES = new byte[] { 2, 8, 100 };
64
65    @Test
66    public void roundTrip_persistsCounterId() throws Exception {
67        assertThat(roundTrip().getCounterId()).isEqualTo(COUNTER_ID);
68    }
69
70    @Test
71    public void roundTrip_persistsSnapshotVersion() throws Exception {
72        assertThat(roundTrip().getSnapshotVersion()).isEqualTo(SNAPSHOT_VERSION);
73    }
74
75    @Test
76    public void roundTrip_persistsMaxAttempts() throws Exception {
77        assertThat(roundTrip().getMaxAttempts()).isEqualTo(MAX_ATTEMPTS);
78    }
79
80    @Test
81    public void roundTrip_persistsRecoveryKey() throws Exception {
82        assertThat(roundTrip().getEncryptedRecoveryKeyBlob()).isEqualTo(KEY_BLOB);
83    }
84
85    @Test
86    public void roundTrip_persistsServerParams() throws Exception {
87        assertThat(roundTrip().getServerParams()).isEqualTo(SERVER_PARAMS);
88    }
89
90    @Test
91    public void roundTrip_persistsCertPath() throws Exception {
92        assertThat(roundTrip().getTrustedHardwareCertPath()).isEqualTo(CERT_PATH);
93    }
94
95    @Test
96    public void roundTrip_persistsParamsList() throws Exception {
97        assertThat(roundTrip().getKeyChainProtectionParams()).hasSize(1);
98    }
99
100    @Test
101    public void roundTripParams_persistsUserSecretType() throws Exception {
102        assertThat(roundTripParams().getUserSecretType()).isEqualTo(SECRET_TYPE);
103    }
104
105    @Test
106    public void roundTripParams_persistsLockScreenUi() throws Exception {
107        assertThat(roundTripParams().getLockScreenUiFormat()).isEqualTo(LOCK_SCREEN_UI);
108    }
109
110    @Test
111    public void roundTripParams_persistsSalt() throws Exception {
112        assertThat(roundTripParams().getKeyDerivationParams().getSalt()).isEqualTo(SALT);
113    }
114
115    @Test
116    public void roundTripParams_persistsAlgorithm() throws Exception {
117        assertThat(roundTripParams().getKeyDerivationParams().getAlgorithm()).isEqualTo(ALGORITHM);
118    }
119
120    @Test
121    public void roundTripParams_persistsMemoryDifficulty() throws Exception {
122        assertThat(roundTripParams().getKeyDerivationParams().getMemoryDifficulty())
123                .isEqualTo(MEMORY_DIFFICULTY);
124    }
125
126    @Test
127    public void roundTripParams_doesNotPersistSecret() throws Exception {
128        assertThat(roundTripParams().getSecret()).isEmpty();
129    }
130
131    @Test
132    public void roundTripKeys_hasCorrectLength() throws Exception {
133        assertThat(roundTripKeys()).hasSize(3);
134    }
135
136    @Test
137    public void roundTripKeys_0_persistsAlias() throws Exception {
138        assertThat(roundTripKeys().get(0).getAlias()).isEqualTo(TEST_KEY_1_ALIAS);
139    }
140
141    @Test
142    public void roundTripKeys_0_persistsKeyBytes() throws Exception {
143        assertThat(roundTripKeys().get(0).getEncryptedKeyMaterial()).isEqualTo(TEST_KEY_1_BYTES);
144    }
145
146    @Test
147    public void roundTripKeys_1_persistsAlias() throws Exception {
148        assertThat(roundTripKeys().get(1).getAlias()).isEqualTo(TEST_KEY_2_ALIAS);
149    }
150
151    @Test
152    public void roundTripKeys_1_persistsKeyBytes() throws Exception {
153        assertThat(roundTripKeys().get(1).getEncryptedKeyMaterial()).isEqualTo(TEST_KEY_2_BYTES);
154    }
155
156    @Test
157    public void roundTripKeys_2_persistsAlias() throws Exception {
158        assertThat(roundTripKeys().get(2).getAlias()).isEqualTo(TEST_KEY_3_ALIAS);
159    }
160
161    @Test
162    public void roundTripKeys_2_persistsKeyBytes() throws Exception {
163        assertThat(roundTripKeys().get(2).getEncryptedKeyMaterial()).isEqualTo(TEST_KEY_3_BYTES);
164    }
165
166    @Test
167    public void serialize_doesNotThrowForTestSnapshot() throws Exception {
168        KeyChainSnapshotSerializer.serialize(
169                createTestKeyChainSnapshot(), new ByteArrayOutputStream());
170    }
171
172    private static List<WrappedApplicationKey> roundTripKeys() throws Exception {
173        return roundTrip().getWrappedApplicationKeys();
174    }
175
176    private static KeyChainProtectionParams roundTripParams() throws Exception {
177        return roundTrip().getKeyChainProtectionParams().get(0);
178    }
179
180    public static KeyChainSnapshot roundTrip() throws Exception {
181        KeyChainSnapshot snapshot = createTestKeyChainSnapshot();
182        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
183        KeyChainSnapshotSerializer.serialize(snapshot, byteArrayOutputStream);
184        return KeyChainSnapshotDeserializer.deserialize(
185                new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
186    }
187
188    private static KeyChainSnapshot createTestKeyChainSnapshot() throws Exception {
189        return new KeyChainSnapshot.Builder()
190                .setCounterId(COUNTER_ID)
191                .setSnapshotVersion(SNAPSHOT_VERSION)
192                .setServerParams(SERVER_PARAMS)
193                .setMaxAttempts(MAX_ATTEMPTS)
194                .setEncryptedRecoveryKeyBlob(KEY_BLOB)
195                .setKeyChainProtectionParams(createKeyChainProtectionParamsList())
196                .setWrappedApplicationKeys(createKeys())
197                .setTrustedHardwareCertPath(CERT_PATH)
198                .build();
199    }
200
201    private static List<WrappedApplicationKey> createKeys() {
202        ArrayList<WrappedApplicationKey> keyList = new ArrayList<>();
203        keyList.add(createKey(TEST_KEY_1_ALIAS, TEST_KEY_1_BYTES));
204        keyList.add(createKey(TEST_KEY_2_ALIAS, TEST_KEY_2_BYTES));
205        keyList.add(createKey(TEST_KEY_3_ALIAS, TEST_KEY_3_BYTES));
206        return keyList;
207    }
208
209    private static List<KeyChainProtectionParams> createKeyChainProtectionParamsList() {
210        KeyDerivationParams keyDerivationParams =
211                KeyDerivationParams.createScryptParams(SALT, MEMORY_DIFFICULTY);
212        KeyChainProtectionParams keyChainProtectionParams = new KeyChainProtectionParams.Builder()
213                .setKeyDerivationParams(keyDerivationParams)
214                .setUserSecretType(SECRET_TYPE)
215                .setLockScreenUiFormat(LOCK_SCREEN_UI)
216                .setSecret(SECRET)
217                .build();
218        ArrayList<KeyChainProtectionParams> keyChainProtectionParamsList =
219                new ArrayList<>(1);
220        keyChainProtectionParamsList.add(keyChainProtectionParams);
221        return keyChainProtectionParamsList;
222    }
223
224    private static WrappedApplicationKey createKey(String alias, byte[] bytes) {
225        return new WrappedApplicationKey.Builder()
226                .setAlias(alias)
227                .setEncryptedKeyMaterial(bytes)
228                .build();
229    }
230}
231