Loading services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +26 −5 Original line number Diff line number Diff line Loading @@ -58,6 +58,7 @@ import java.security.PublicKey; import java.security.UnrecoverableKeyException; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; Loading Loading @@ -323,6 +324,23 @@ public class RecoverableKeyStoreManager { "Only a single KeyStoreRecoveryMetadata is supported"); } PublicKey publicKey; try { publicKey = KeySyncUtils.deserializePublicKey(verifierPublicKey); } catch (NoSuchAlgorithmException e) { // Should never happen throw new RuntimeException(e); } catch (InvalidKeySpecException e) { throw new ServiceSpecificException(ERROR_BAD_X509_CERTIFICATE, "Not a valid X509 key"); } // The raw public key bytes contained in vaultParams must match the ones given in // verifierPublicKey; otherwise, the user secret may be decrypted by a key that is not owned // by the original recovery service. if (!publicKeysMatch(publicKey, vaultParams)) { throw new ServiceSpecificException(ERROR_BAD_X509_CERTIFICATE, "The public keys given in verifierPublicKey and vaultParams do not match."); } byte[] keyClaimant = KeySyncUtils.generateKeyClaimant(); byte[] kfHash = secrets.get(0).getSecret(); mRecoverySessionStorage.add( Loading @@ -331,7 +349,6 @@ public class RecoverableKeyStoreManager { try { byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(kfHash); PublicKey publicKey = KeySyncUtils.deserializePublicKey(verifierPublicKey); return KeySyncUtils.encryptRecoveryClaim( publicKey, vaultParams, Loading @@ -341,9 +358,8 @@ public class RecoverableKeyStoreManager { } catch (NoSuchAlgorithmException e) { Log.wtf(TAG, "SecureBox algorithm missing. AOSP must support this.", e); throw new ServiceSpecificException(ERROR_UNEXPECTED_MISSING_ALGORITHM, e.getMessage()); } catch (InvalidKeySpecException | InvalidKeyException e) { throw new ServiceSpecificException(ERROR_BAD_X509_CERTIFICATE, "Not a valid X509 key"); } catch (InvalidKeyException e) { throw new ServiceSpecificException(ERROR_BAD_X509_CERTIFICATE, e.getMessage()); } } Loading @@ -352,7 +368,7 @@ public class RecoverableKeyStoreManager { * service. * * @param sessionId The session ID used to generate the claim. See * {@link #startRecoverySession(String, byte[], byte[], byte[], List, int)}. * {@link #startRecoverySession(String, byte[], byte[], byte[], List)}. * @param encryptedRecoveryKey The encrypted recovery key blob returned by the remote vault * service. * @param applicationKeys The encrypted key blobs returned by the remote vault service. These Loading Loading @@ -509,4 +525,9 @@ public class RecoverableKeyStoreManager { RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE, "Caller " + Binder.getCallingUid() + " doesn't have RecoverKeyStore permission."); } private boolean publicKeysMatch(PublicKey publicKey, byte[] vaultParams) { byte[] encodedPublicKey = SecureBox.encodePublicKey(publicKey); return Arrays.equals(encodedPublicKey, Arrays.copyOf(vaultParams, encodedPublicKey.length)); } } services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +42 −1 Original line number Diff line number Diff line Loading @@ -96,7 +96,26 @@ public class RecoverableKeyStoreManagerTest { private static final byte[] TEST_SALT = getUtf8Bytes("salt"); private static final byte[] TEST_SECRET = getUtf8Bytes("password1234"); private static final byte[] TEST_VAULT_CHALLENGE = getUtf8Bytes("vault_challenge"); private static final byte[] TEST_VAULT_PARAMS = getUtf8Bytes("vault_params"); private static final byte[] TEST_VAULT_PARAMS = new byte[] { // backend_key (byte) 0x04, (byte) 0xb8, (byte) 0x00, (byte) 0x11, (byte) 0x18, (byte) 0x98, (byte) 0x1d, (byte) 0xf0, (byte) 0x6e, (byte) 0xb4, (byte) 0x94, (byte) 0xfe, (byte) 0x86, (byte) 0xda, (byte) 0x1c, (byte) 0x07, (byte) 0x8d, (byte) 0x01, (byte) 0xb4, (byte) 0x3a, (byte) 0xf6, (byte) 0x8d, (byte) 0xdc, (byte) 0x61, (byte) 0xd0, (byte) 0x46, (byte) 0x49, (byte) 0x95, (byte) 0x0f, (byte) 0x10, (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0, (byte) 0x3f, (byte) 0xd2, (byte) 0xdf, (byte) 0xf3, (byte) 0x79, (byte) 0x20, (byte) 0x1d, (byte) 0x91, (byte) 0x55, (byte) 0xb0, (byte) 0xe5, (byte) 0xbd, (byte) 0x7a, (byte) 0x8b, (byte) 0x32, (byte) 0x7d, (byte) 0x25, (byte) 0x53, (byte) 0xa2, (byte) 0xfc, (byte) 0xa5, (byte) 0x65, (byte) 0xe1, (byte) 0xbd, (byte) 0x21, (byte) 0x44, (byte) 0x7e, (byte) 0x78, (byte) 0x52, (byte) 0xfa, // counter_id (byte) 0x31, (byte) 0x32, (byte) 0x33, (byte) 0x34, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, // device_parameter (byte) 0x78, (byte) 0x56, (byte) 0x34, (byte) 0x12, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x0, // max_attempts (byte) 0x0a, (byte) 0x00, (byte) 0x00, (byte) 0x00}; private static final int TEST_GENERATION_ID = 2; private static final int TEST_USER_ID = 10009; private static final int KEY_CLAIMANT_LENGTH_BYTES = 16; Loading Loading @@ -248,6 +267,28 @@ public class RecoverableKeyStoreManagerTest { } } @Test public void startRecoverySession_throwsIfPublicKeysMismatch() throws Exception { byte[] vaultParams = TEST_VAULT_PARAMS.clone(); vaultParams[1] ^= (byte) 1; // Flip 1 bit try { mRecoverableKeyStoreManager.startRecoverySession( TEST_SESSION_ID, TEST_PUBLIC_KEY, vaultParams, TEST_VAULT_CHALLENGE, ImmutableList.of( new KeyStoreRecoveryMetadata( TYPE_LOCKSCREEN, TYPE_PASSWORD, KeyDerivationParameters.createSHA256Parameters(TEST_SALT), TEST_SECRET))); fail("should have thrown"); } catch (ServiceSpecificException e) { assertThat(e.getMessage()).contains("do not match"); } } @Test public void recoverKeys_throwsIfNoSessionIsPresent() throws Exception { try { Loading Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +26 −5 Original line number Diff line number Diff line Loading @@ -58,6 +58,7 @@ import java.security.PublicKey; import java.security.UnrecoverableKeyException; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; Loading Loading @@ -323,6 +324,23 @@ public class RecoverableKeyStoreManager { "Only a single KeyStoreRecoveryMetadata is supported"); } PublicKey publicKey; try { publicKey = KeySyncUtils.deserializePublicKey(verifierPublicKey); } catch (NoSuchAlgorithmException e) { // Should never happen throw new RuntimeException(e); } catch (InvalidKeySpecException e) { throw new ServiceSpecificException(ERROR_BAD_X509_CERTIFICATE, "Not a valid X509 key"); } // The raw public key bytes contained in vaultParams must match the ones given in // verifierPublicKey; otherwise, the user secret may be decrypted by a key that is not owned // by the original recovery service. if (!publicKeysMatch(publicKey, vaultParams)) { throw new ServiceSpecificException(ERROR_BAD_X509_CERTIFICATE, "The public keys given in verifierPublicKey and vaultParams do not match."); } byte[] keyClaimant = KeySyncUtils.generateKeyClaimant(); byte[] kfHash = secrets.get(0).getSecret(); mRecoverySessionStorage.add( Loading @@ -331,7 +349,6 @@ public class RecoverableKeyStoreManager { try { byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(kfHash); PublicKey publicKey = KeySyncUtils.deserializePublicKey(verifierPublicKey); return KeySyncUtils.encryptRecoveryClaim( publicKey, vaultParams, Loading @@ -341,9 +358,8 @@ public class RecoverableKeyStoreManager { } catch (NoSuchAlgorithmException e) { Log.wtf(TAG, "SecureBox algorithm missing. AOSP must support this.", e); throw new ServiceSpecificException(ERROR_UNEXPECTED_MISSING_ALGORITHM, e.getMessage()); } catch (InvalidKeySpecException | InvalidKeyException e) { throw new ServiceSpecificException(ERROR_BAD_X509_CERTIFICATE, "Not a valid X509 key"); } catch (InvalidKeyException e) { throw new ServiceSpecificException(ERROR_BAD_X509_CERTIFICATE, e.getMessage()); } } Loading @@ -352,7 +368,7 @@ public class RecoverableKeyStoreManager { * service. * * @param sessionId The session ID used to generate the claim. See * {@link #startRecoverySession(String, byte[], byte[], byte[], List, int)}. * {@link #startRecoverySession(String, byte[], byte[], byte[], List)}. * @param encryptedRecoveryKey The encrypted recovery key blob returned by the remote vault * service. * @param applicationKeys The encrypted key blobs returned by the remote vault service. These Loading Loading @@ -509,4 +525,9 @@ public class RecoverableKeyStoreManager { RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE, "Caller " + Binder.getCallingUid() + " doesn't have RecoverKeyStore permission."); } private boolean publicKeysMatch(PublicKey publicKey, byte[] vaultParams) { byte[] encodedPublicKey = SecureBox.encodePublicKey(publicKey); return Arrays.equals(encodedPublicKey, Arrays.copyOf(vaultParams, encodedPublicKey.length)); } }
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +42 −1 Original line number Diff line number Diff line Loading @@ -96,7 +96,26 @@ public class RecoverableKeyStoreManagerTest { private static final byte[] TEST_SALT = getUtf8Bytes("salt"); private static final byte[] TEST_SECRET = getUtf8Bytes("password1234"); private static final byte[] TEST_VAULT_CHALLENGE = getUtf8Bytes("vault_challenge"); private static final byte[] TEST_VAULT_PARAMS = getUtf8Bytes("vault_params"); private static final byte[] TEST_VAULT_PARAMS = new byte[] { // backend_key (byte) 0x04, (byte) 0xb8, (byte) 0x00, (byte) 0x11, (byte) 0x18, (byte) 0x98, (byte) 0x1d, (byte) 0xf0, (byte) 0x6e, (byte) 0xb4, (byte) 0x94, (byte) 0xfe, (byte) 0x86, (byte) 0xda, (byte) 0x1c, (byte) 0x07, (byte) 0x8d, (byte) 0x01, (byte) 0xb4, (byte) 0x3a, (byte) 0xf6, (byte) 0x8d, (byte) 0xdc, (byte) 0x61, (byte) 0xd0, (byte) 0x46, (byte) 0x49, (byte) 0x95, (byte) 0x0f, (byte) 0x10, (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0, (byte) 0x3f, (byte) 0xd2, (byte) 0xdf, (byte) 0xf3, (byte) 0x79, (byte) 0x20, (byte) 0x1d, (byte) 0x91, (byte) 0x55, (byte) 0xb0, (byte) 0xe5, (byte) 0xbd, (byte) 0x7a, (byte) 0x8b, (byte) 0x32, (byte) 0x7d, (byte) 0x25, (byte) 0x53, (byte) 0xa2, (byte) 0xfc, (byte) 0xa5, (byte) 0x65, (byte) 0xe1, (byte) 0xbd, (byte) 0x21, (byte) 0x44, (byte) 0x7e, (byte) 0x78, (byte) 0x52, (byte) 0xfa, // counter_id (byte) 0x31, (byte) 0x32, (byte) 0x33, (byte) 0x34, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, // device_parameter (byte) 0x78, (byte) 0x56, (byte) 0x34, (byte) 0x12, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x0, // max_attempts (byte) 0x0a, (byte) 0x00, (byte) 0x00, (byte) 0x00}; private static final int TEST_GENERATION_ID = 2; private static final int TEST_USER_ID = 10009; private static final int KEY_CLAIMANT_LENGTH_BYTES = 16; Loading Loading @@ -248,6 +267,28 @@ public class RecoverableKeyStoreManagerTest { } } @Test public void startRecoverySession_throwsIfPublicKeysMismatch() throws Exception { byte[] vaultParams = TEST_VAULT_PARAMS.clone(); vaultParams[1] ^= (byte) 1; // Flip 1 bit try { mRecoverableKeyStoreManager.startRecoverySession( TEST_SESSION_ID, TEST_PUBLIC_KEY, vaultParams, TEST_VAULT_CHALLENGE, ImmutableList.of( new KeyStoreRecoveryMetadata( TYPE_LOCKSCREEN, TYPE_PASSWORD, KeyDerivationParameters.createSHA256Parameters(TEST_SALT), TEST_SECRET))); fail("should have thrown"); } catch (ServiceSpecificException e) { assertThat(e.getMessage()).contains("do not match"); } } @Test public void recoverKeys_throwsIfNoSessionIsPresent() throws Exception { try { Loading