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
17package com.android.server.locksettings.recoverablekeystore;
18
19import static com.google.common.truth.Truth.assertThat;
20import static org.testng.Assert.assertThrows;
21import static org.testng.Assert.expectThrows;
22
23import android.support.test.filters.SmallTest;
24import android.support.test.runner.AndroidJUnit4;
25import java.math.BigInteger;
26import java.nio.charset.StandardCharsets;
27import java.security.InvalidKeyException;
28import java.security.KeyFactory;
29import java.security.KeyPair;
30import java.security.KeyPairGenerator;
31import java.security.PrivateKey;
32import java.security.PublicKey;
33import java.security.spec.ECPrivateKeySpec;
34import javax.crypto.AEADBadTagException;
35import org.junit.Test;
36import org.junit.runner.RunWith;
37
38@SmallTest
39@RunWith(AndroidJUnit4.class)
40public class SecureBoxTest {
41
42    private static final int EC_PUBLIC_KEY_LEN_BYTES = 65;
43    private static final int NUM_TEST_ITERATIONS = 100;
44    private static final int VERSION_LEN_BYTES = 2;
45
46    // The following fixtures were produced by the C implementation of SecureBox v2. We use these to
47    // cross-verify the two implementations.
48    private static final byte[] VAULT_PARAMS =
49            new byte[] {
50                (byte) 0x04, (byte) 0xb8, (byte) 0x00, (byte) 0x11, (byte) 0x18, (byte) 0x98,
51                (byte) 0x1d, (byte) 0xf0, (byte) 0x6e, (byte) 0xb4, (byte) 0x94, (byte) 0xfe,
52                (byte) 0x86, (byte) 0xda, (byte) 0x1c, (byte) 0x07, (byte) 0x8d, (byte) 0x01,
53                (byte) 0xb4, (byte) 0x3a, (byte) 0xf6, (byte) 0x8d, (byte) 0xdc, (byte) 0x61,
54                (byte) 0xd0, (byte) 0x46, (byte) 0x49, (byte) 0x95, (byte) 0x0f, (byte) 0x10,
55                (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0, (byte) 0x3f,
56                (byte) 0xd2, (byte) 0xdf, (byte) 0xf3, (byte) 0x79, (byte) 0x20, (byte) 0x1d,
57                (byte) 0x91, (byte) 0x55, (byte) 0xb0, (byte) 0xe5, (byte) 0xbd, (byte) 0x7a,
58                (byte) 0x8b, (byte) 0x32, (byte) 0x7d, (byte) 0x25, (byte) 0x53, (byte) 0xa2,
59                (byte) 0xfc, (byte) 0xa5, (byte) 0x65, (byte) 0xe1, (byte) 0xbd, (byte) 0x21,
60                (byte) 0x44, (byte) 0x7e, (byte) 0x78, (byte) 0x52, (byte) 0xfa, (byte) 0x31,
61                (byte) 0x32, (byte) 0x33, (byte) 0x34, (byte) 0x00, (byte) 0x00, (byte) 0x00,
62                (byte) 0x00, (byte) 0x78, (byte) 0x56, (byte) 0x34, (byte) 0x12, (byte) 0x00,
63                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x0a, (byte) 0x00, (byte) 0x00,
64                (byte) 0x00
65            };
66    private static final byte[] VAULT_CHALLENGE = getBytes("Not a real vault challenge");
67    private static final byte[] THM_KF_HASH = getBytes("12345678901234567890123456789012");
68    private static final byte[] ENCRYPTED_RECOVERY_KEY =
69            new byte[] {
70                (byte) 0x02, (byte) 0x00, (byte) 0x04, (byte) 0xe3, (byte) 0xa8, (byte) 0xd0,
71                (byte) 0x32, (byte) 0x3c, (byte) 0xc7, (byte) 0xe5, (byte) 0xe8, (byte) 0xc1,
72                (byte) 0x73, (byte) 0x4c, (byte) 0x75, (byte) 0x20, (byte) 0x2e, (byte) 0xb7,
73                (byte) 0xba, (byte) 0xef, (byte) 0x3e, (byte) 0x3e, (byte) 0xa6, (byte) 0x93,
74                (byte) 0xe9, (byte) 0xde, (byte) 0xa7, (byte) 0x00, (byte) 0x09, (byte) 0xba,
75                (byte) 0xa8, (byte) 0x9c, (byte) 0xac, (byte) 0x72, (byte) 0xff, (byte) 0xf6,
76                (byte) 0x84, (byte) 0x16, (byte) 0xb0, (byte) 0xff, (byte) 0x47, (byte) 0x98,
77                (byte) 0x53, (byte) 0xc4, (byte) 0xa3, (byte) 0x4a, (byte) 0x54, (byte) 0x21,
78                (byte) 0x8e, (byte) 0x00, (byte) 0x4b, (byte) 0xfa, (byte) 0xce, (byte) 0xe3,
79                (byte) 0x79, (byte) 0x8e, (byte) 0x20, (byte) 0x7c, (byte) 0x9b, (byte) 0xc4,
80                (byte) 0x7c, (byte) 0xd5, (byte) 0x33, (byte) 0x70, (byte) 0x96, (byte) 0xdc,
81                (byte) 0xa0, (byte) 0x1f, (byte) 0x6e, (byte) 0xbb, (byte) 0x5d, (byte) 0x0c,
82                (byte) 0x64, (byte) 0x5f, (byte) 0xed, (byte) 0xbf, (byte) 0x79, (byte) 0x8a,
83                (byte) 0x0e, (byte) 0xd6, (byte) 0x4b, (byte) 0x93, (byte) 0xc9, (byte) 0xcd,
84                (byte) 0x25, (byte) 0x06, (byte) 0x73, (byte) 0x5e, (byte) 0xdb, (byte) 0xac,
85                (byte) 0xa8, (byte) 0xeb, (byte) 0x6e, (byte) 0x26, (byte) 0x77, (byte) 0x56,
86                (byte) 0xd1, (byte) 0x23, (byte) 0x48, (byte) 0xb6, (byte) 0x6a, (byte) 0x15,
87                (byte) 0xd4, (byte) 0x3e, (byte) 0x38, (byte) 0x7d, (byte) 0x6f, (byte) 0x6f,
88                (byte) 0x7c, (byte) 0x0b, (byte) 0x93, (byte) 0x4e, (byte) 0xb3, (byte) 0x21,
89                (byte) 0x44, (byte) 0x86, (byte) 0xf3, (byte) 0x2e
90            };
91    private static final byte[] KEY_CLAIMANT = getBytes("asdfasdfasdfasdf");
92    private static final byte[] RECOVERY_CLAIM =
93            new byte[] {
94                (byte) 0x02, (byte) 0x00, (byte) 0x04, (byte) 0x16, (byte) 0x75, (byte) 0x5b,
95                (byte) 0xa2, (byte) 0xdc, (byte) 0x2b, (byte) 0x58, (byte) 0xb9, (byte) 0x66,
96                (byte) 0xcb, (byte) 0x6f, (byte) 0xb1, (byte) 0xc1, (byte) 0xb0, (byte) 0x1d,
97                (byte) 0x82, (byte) 0x29, (byte) 0x97, (byte) 0xec, (byte) 0x65, (byte) 0x5e,
98                (byte) 0xef, (byte) 0x14, (byte) 0xc7, (byte) 0xf0, (byte) 0xf1, (byte) 0x83,
99                (byte) 0x15, (byte) 0x0b, (byte) 0xcb, (byte) 0x33, (byte) 0x2d, (byte) 0x05,
100                (byte) 0x20, (byte) 0xdc, (byte) 0xc7, (byte) 0x0d, (byte) 0xc8, (byte) 0xc0,
101                (byte) 0xc9, (byte) 0xa8, (byte) 0x67, (byte) 0xc8, (byte) 0x16, (byte) 0xfe,
102                (byte) 0xfb, (byte) 0xb0, (byte) 0x28, (byte) 0x8e, (byte) 0x4f, (byte) 0xd5,
103                (byte) 0x31, (byte) 0xa7, (byte) 0x94, (byte) 0x33, (byte) 0x23, (byte) 0x15,
104                (byte) 0x04, (byte) 0xbf, (byte) 0x13, (byte) 0x6a, (byte) 0x28, (byte) 0x8f,
105                (byte) 0xa6, (byte) 0xfc, (byte) 0x01, (byte) 0xd5, (byte) 0x69, (byte) 0x3d,
106                (byte) 0x96, (byte) 0x0c, (byte) 0x37, (byte) 0xb4, (byte) 0x1e, (byte) 0x13,
107                (byte) 0x40, (byte) 0xcc, (byte) 0x44, (byte) 0x19, (byte) 0xf2, (byte) 0xdb,
108                (byte) 0x49, (byte) 0x80, (byte) 0x9f, (byte) 0xef, (byte) 0xee, (byte) 0x41,
109                (byte) 0xe6, (byte) 0x3f, (byte) 0xa8, (byte) 0xea, (byte) 0x89, (byte) 0xfe,
110                (byte) 0x56, (byte) 0x20, (byte) 0xba, (byte) 0x90, (byte) 0x9a, (byte) 0xba,
111                (byte) 0x0e, (byte) 0x30, (byte) 0xa7, (byte) 0x2b, (byte) 0x0a, (byte) 0x12,
112                (byte) 0x0b, (byte) 0x03, (byte) 0xd1, (byte) 0x0c, (byte) 0x8e, (byte) 0x82,
113                (byte) 0x03, (byte) 0xa1, (byte) 0x7f, (byte) 0xc8, (byte) 0xd0, (byte) 0xa9,
114                (byte) 0x86, (byte) 0x55, (byte) 0x63, (byte) 0xdc, (byte) 0x70, (byte) 0x34,
115                (byte) 0x21, (byte) 0x2a, (byte) 0x41, (byte) 0x3f, (byte) 0xbb, (byte) 0x82,
116                (byte) 0x82, (byte) 0xf9, (byte) 0x2b, (byte) 0xd2, (byte) 0x33, (byte) 0x03,
117                (byte) 0x50, (byte) 0xd2, (byte) 0x27, (byte) 0xeb, (byte) 0x1a
118            };
119
120    private static final byte[] TEST_SHARED_SECRET = getBytes("TEST_SHARED_SECRET");
121    private static final byte[] TEST_HEADER = getBytes("TEST_HEADER");
122    private static final byte[] TEST_PAYLOAD = getBytes("TEST_PAYLOAD");
123
124    private static final PublicKey THM_PUBLIC_KEY;
125    private static final PrivateKey THM_PRIVATE_KEY;
126
127    static {
128        try {
129            THM_PUBLIC_KEY =
130                    SecureBox.decodePublicKey(
131                            new byte[] {
132                                (byte) 0x04, (byte) 0xb8, (byte) 0x00, (byte) 0x11, (byte) 0x18,
133                                (byte) 0x98, (byte) 0x1d, (byte) 0xf0, (byte) 0x6e, (byte) 0xb4,
134                                (byte) 0x94, (byte) 0xfe, (byte) 0x86, (byte) 0xda, (byte) 0x1c,
135                                (byte) 0x07, (byte) 0x8d, (byte) 0x01, (byte) 0xb4, (byte) 0x3a,
136                                (byte) 0xf6, (byte) 0x8d, (byte) 0xdc, (byte) 0x61, (byte) 0xd0,
137                                (byte) 0x46, (byte) 0x49, (byte) 0x95, (byte) 0x0f, (byte) 0x10,
138                                (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0,
139                                (byte) 0x3f, (byte) 0xd2, (byte) 0xdf, (byte) 0xf3, (byte) 0x79,
140                                (byte) 0x20, (byte) 0x1d, (byte) 0x91, (byte) 0x55, (byte) 0xb0,
141                                (byte) 0xe5, (byte) 0xbd, (byte) 0x7a, (byte) 0x8b, (byte) 0x32,
142                                (byte) 0x7d, (byte) 0x25, (byte) 0x53, (byte) 0xa2, (byte) 0xfc,
143                                (byte) 0xa5, (byte) 0x65, (byte) 0xe1, (byte) 0xbd, (byte) 0x21,
144                                (byte) 0x44, (byte) 0x7e, (byte) 0x78, (byte) 0x52, (byte) 0xfa
145                            });
146            THM_PRIVATE_KEY =
147                    decodePrivateKey(
148                            new byte[] {
149                                (byte) 0x70, (byte) 0x01, (byte) 0xc7, (byte) 0x87, (byte) 0x32,
150                                (byte) 0x2f, (byte) 0x1c, (byte) 0x9a, (byte) 0x6e, (byte) 0xb1,
151                                (byte) 0x91, (byte) 0xca, (byte) 0x4e, (byte) 0xb5, (byte) 0x44,
152                                (byte) 0xba, (byte) 0xc8, (byte) 0x68, (byte) 0xc6, (byte) 0x0a,
153                                (byte) 0x76, (byte) 0xcb, (byte) 0xd3, (byte) 0x63, (byte) 0x67,
154                                (byte) 0x7c, (byte) 0xb0, (byte) 0x11, (byte) 0x82, (byte) 0x65,
155                                (byte) 0x77, (byte) 0x01
156                            });
157        } catch (Exception ex) {
158            throw new RuntimeException(ex);
159        }
160    }
161
162    @Test
163    public void genKeyPair_alwaysReturnsANewKeyPair() throws Exception {
164        KeyPair keyPair1 = SecureBox.genKeyPair();
165        KeyPair keyPair2 = SecureBox.genKeyPair();
166        assertThat(keyPair1).isNotEqualTo(keyPair2);
167    }
168
169    @Test
170    public void decryptRecoveryClaim() throws Exception {
171        byte[] claimContent =
172                SecureBox.decrypt(
173                        THM_PRIVATE_KEY,
174                        /*sharedSecret=*/ null,
175                        SecureBox.concat(getBytes("V1 KF_claim"), VAULT_PARAMS, VAULT_CHALLENGE),
176                        RECOVERY_CLAIM);
177        assertThat(claimContent).isEqualTo(SecureBox.concat(THM_KF_HASH, KEY_CLAIMANT));
178    }
179
180    @Test
181    public void decryptRecoveryKey_doesNotThrowForValidAuthenticationTag() throws Exception {
182        SecureBox.decrypt(
183                THM_PRIVATE_KEY,
184                THM_KF_HASH,
185                SecureBox.concat(getBytes("V1 THM_encrypted_recovery_key"), VAULT_PARAMS),
186                ENCRYPTED_RECOVERY_KEY);
187    }
188
189    @Test
190    public void encryptThenDecrypt() throws Exception {
191        byte[] state = TEST_PAYLOAD;
192        // Iterate multiple times to amplify any errors
193        for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
194            state = SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, state);
195        }
196        for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
197            state = SecureBox.decrypt(THM_PRIVATE_KEY, TEST_SHARED_SECRET, TEST_HEADER, state);
198        }
199        assertThat(state).isEqualTo(TEST_PAYLOAD);
200    }
201
202    @Test
203    public void encryptThenDecrypt_nullPublicPrivateKeys() throws Exception {
204        byte[] encrypted =
205                SecureBox.encrypt(
206                        /*theirPublicKey=*/ null, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
207        byte[] decrypted =
208                SecureBox.decrypt(
209                        /*ourPrivateKey=*/ null, TEST_SHARED_SECRET, TEST_HEADER, encrypted);
210        assertThat(decrypted).isEqualTo(TEST_PAYLOAD);
211    }
212
213    @Test
214    public void encryptThenDecrypt_nullSharedSecret() throws Exception {
215        byte[] encrypted =
216                SecureBox.encrypt(
217                        THM_PUBLIC_KEY, /*sharedSecret=*/ null, TEST_HEADER, TEST_PAYLOAD);
218        byte[] decrypted =
219                SecureBox.decrypt(THM_PRIVATE_KEY, /*sharedSecret=*/ null, TEST_HEADER, encrypted);
220        assertThat(decrypted).isEqualTo(TEST_PAYLOAD);
221    }
222
223    @Test
224    public void encryptThenDecrypt_nullHeader() throws Exception {
225        byte[] encrypted =
226                SecureBox.encrypt(
227                        THM_PUBLIC_KEY, TEST_SHARED_SECRET, /*header=*/ null, TEST_PAYLOAD);
228        byte[] decrypted =
229                SecureBox.decrypt(THM_PRIVATE_KEY, TEST_SHARED_SECRET, /*header=*/ null, encrypted);
230        assertThat(decrypted).isEqualTo(TEST_PAYLOAD);
231    }
232
233    @Test
234    public void encryptThenDecrypt_nullPayload() throws Exception {
235        byte[] encrypted =
236                SecureBox.encrypt(
237                        THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, /*payload=*/ null);
238        byte[] decrypted =
239                SecureBox.decrypt(
240                        THM_PRIVATE_KEY,
241                        TEST_SHARED_SECRET,
242                        TEST_HEADER,
243                        /*encryptedPayload=*/ encrypted);
244        assertThat(decrypted.length).isEqualTo(0);
245    }
246
247    @Test
248    public void encrypt_nullPublicKeyAndSharedSecret() throws Exception {
249        IllegalArgumentException expected =
250                expectThrows(
251                        IllegalArgumentException.class,
252                        () ->
253                                SecureBox.encrypt(
254                                        /*theirPublicKey=*/ null,
255                                        /*sharedSecret=*/ null,
256                                        TEST_HEADER,
257                                        TEST_PAYLOAD));
258        assertThat(expected.getMessage()).contains("public key and shared secret");
259    }
260
261    @Test
262    public void decrypt_nullPrivateKeyAndSharedSecret() throws Exception {
263        IllegalArgumentException expected =
264                expectThrows(
265                        IllegalArgumentException.class,
266                        () ->
267                                SecureBox.decrypt(
268                                        /*ourPrivateKey=*/ null,
269                                        /*sharedSecret=*/ null,
270                                        TEST_HEADER,
271                                        TEST_PAYLOAD));
272        assertThat(expected.getMessage()).contains("private key and shared secret");
273    }
274
275    @Test
276    public void decrypt_nullEncryptedPayload() throws Exception {
277        NullPointerException expected =
278                expectThrows(
279                        NullPointerException.class,
280                        () ->
281                                SecureBox.decrypt(
282                                        THM_PRIVATE_KEY,
283                                        TEST_SHARED_SECRET,
284                                        TEST_HEADER,
285                                        /*encryptedPayload=*/ null));
286        assertThat(expected.getMessage()).contains("payload");
287    }
288
289    @Test
290    public void decrypt_badAuthenticationTag() throws Exception {
291        byte[] encrypted =
292                SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
293        encrypted[encrypted.length - 1] ^= (byte) 1;
294
295        assertThrows(
296                AEADBadTagException.class,
297                () ->
298                        SecureBox.decrypt(
299                                THM_PRIVATE_KEY, TEST_SHARED_SECRET, TEST_HEADER, encrypted));
300    }
301
302    @Test
303    public void encrypt_invalidPublicKey() throws Exception {
304        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
305        keyGen.initialize(2048);
306        PublicKey publicKey = keyGen.genKeyPair().getPublic();
307
308        assertThrows(
309                InvalidKeyException.class,
310                () -> SecureBox.encrypt(publicKey, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD));
311    }
312
313    @Test
314    public void decrypt_invalidPrivateKey() throws Exception {
315        byte[] encrypted =
316                SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
317        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
318        keyGen.initialize(2048);
319        PrivateKey privateKey = keyGen.genKeyPair().getPrivate();
320
321        assertThrows(
322                InvalidKeyException.class,
323                () -> SecureBox.decrypt(privateKey, TEST_SHARED_SECRET, TEST_HEADER, encrypted));
324    }
325
326    @Test
327    public void decrypt_publicKeyOutsideCurve() throws Exception {
328        byte[] encrypted =
329                SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
330        // Flip the least significant bit of the encoded public key
331        encrypted[VERSION_LEN_BYTES + EC_PUBLIC_KEY_LEN_BYTES - 1] ^= (byte) 1;
332
333        InvalidKeyException expected =
334                expectThrows(
335                        InvalidKeyException.class,
336                        () ->
337                                SecureBox.decrypt(
338                                        THM_PRIVATE_KEY,
339                                        TEST_SHARED_SECRET,
340                                        TEST_HEADER,
341                                        encrypted));
342        assertThat(expected.getMessage()).contains("expected curve");
343    }
344
345    @Test
346    public void encodeThenDecodePublicKey() throws Exception {
347        for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
348            PublicKey originalKey = SecureBox.genKeyPair().getPublic();
349            byte[] encodedKey = SecureBox.encodePublicKey(originalKey);
350            PublicKey decodedKey = SecureBox.decodePublicKey(encodedKey);
351            assertThat(originalKey).isEqualTo(decodedKey);
352        }
353    }
354
355    private static byte[] getBytes(String str) {
356        return str.getBytes(StandardCharsets.UTF_8);
357    }
358
359    private static PrivateKey decodePrivateKey(byte[] keyBytes) throws Exception {
360        assertThat(keyBytes.length).isEqualTo(32);
361        BigInteger priv = new BigInteger(/*signum=*/ 1, keyBytes);
362        KeyFactory keyFactory = KeyFactory.getInstance("EC");
363        return keyFactory.generatePrivate(new ECPrivateKeySpec(priv, SecureBox.EC_PARAM_SPEC));
364    }
365}
366