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