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