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