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