Loading services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java 0 → 100644 +224 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.locksettings.recoverablekeystore; import android.annotation.NonNull; import android.content.Context; import android.security.recoverablekeystore.KeyStoreRecoveryMetadata; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.KeyStoreException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.SecureRandom; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; /** * Task to sync application keys to a remote vault service. * * TODO: implement fully */ public class KeySyncTask implements Runnable { private static final String TAG = "KeySyncTask"; private static final String RECOVERY_KEY_ALGORITHM = "AES"; private static final int RECOVERY_KEY_SIZE_BITS = 256; private static final int SALT_LENGTH_BYTES = 16; private static final int LENGTH_PREFIX_BYTES = Integer.BYTES; private static final String LOCK_SCREEN_HASH_ALGORITHM = "SHA-256"; private final Context mContext; private final RecoverableKeyStoreDb mRecoverableKeyStoreDb; private final int mUserId; private final int mCredentialType; private final String mCredential; public static KeySyncTask newInstance( Context context, RecoverableKeyStoreDb recoverableKeyStoreDb, int userId, int credentialType, String credential ) throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException { return new KeySyncTask( context.getApplicationContext(), recoverableKeyStoreDb, userId, credentialType, credential); } /** * A new task. * * @param recoverableKeyStoreDb Database where the keys are stored. * @param userId The uid of the user whose profile has been unlocked. * @param credentialType The type of credential - i.e., pattern or password. * @param credential The credential, encoded as a {@link String}. */ @VisibleForTesting KeySyncTask( Context context, RecoverableKeyStoreDb recoverableKeyStoreDb, int userId, int credentialType, String credential) { mContext = context; mRecoverableKeyStoreDb = recoverableKeyStoreDb; mUserId = userId; mCredentialType = credentialType; mCredential = credential; } @Override public void run() { try { syncKeys(); } catch (Exception e) { Log.e(TAG, "Unexpected exception thrown during KeySyncTask", e); } } private void syncKeys() { byte[] salt = generateSalt(); byte[] localLskfHash = hashCredentials(salt, mCredential); // TODO: decrypt local wrapped application keys, ready for sync SecretKey recoveryKey; try { recoveryKey = generateRecoveryKey(); } catch (NoSuchAlgorithmException e) { Log.wtf("AES should never be unavailable", e); return; } // TODO: encrypt each application key with recovery key PublicKey vaultKey = getVaultPublicKey(); // TODO: construct vault params and vault metadata byte[] vaultParams = {}; byte[] locallyEncryptedRecoveryKey; try { locallyEncryptedRecoveryKey = KeySyncUtils.thmEncryptRecoveryKey( vaultKey, localLskfHash, vaultParams, recoveryKey); } catch (NoSuchAlgorithmException e) { Log.wtf(TAG, "SecureBox encrypt algorithms unavailable", e); return; } catch (InvalidKeyException e) { Log.e(TAG,"Could not encrypt with recovery key", e); return; } // TODO: send RECOVERABLE_KEYSTORE_SNAPSHOT intent } private PublicKey getVaultPublicKey() { // TODO: fill this in throw new UnsupportedOperationException("TODO: get vault public key."); } /** * The UI best suited to entering the given lock screen. This is synced with the vault so the * user can be shown the same UI when recovering the vault on another device. * * @return The format - either pattern, pin, or password. */ @VisibleForTesting @KeyStoreRecoveryMetadata.LockScreenUiFormat static int getUiFormat( int credentialType, String credential) { if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) { return KeyStoreRecoveryMetadata.TYPE_PATTERN; } else if (isPin(credential)) { return KeyStoreRecoveryMetadata.TYPE_PIN; } else { return KeyStoreRecoveryMetadata.TYPE_PASSWORD; } } /** * Generates a salt to include with the lock screen hash. * * @return The salt. */ private byte[] generateSalt() { byte[] salt = new byte[SALT_LENGTH_BYTES]; new SecureRandom().nextBytes(salt); return salt; } /** * Returns {@code true} if {@code credential} looks like a pin. */ @VisibleForTesting static boolean isPin(@NonNull String credential) { int length = credential.length(); for (int i = 0; i < length; i++) { if (!Character.isDigit(credential.charAt(i))) { return false; } } return true; } /** * Hashes {@code credentials} with the given {@code salt}. * * @return The SHA-256 hash. */ @VisibleForTesting static byte[] hashCredentials(byte[] salt, String credentials) { byte[] credentialsBytes = credentials.getBytes(StandardCharsets.UTF_8); ByteBuffer byteBuffer = ByteBuffer.allocate( salt.length + credentialsBytes.length + LENGTH_PREFIX_BYTES * 2); byteBuffer.order(ByteOrder.LITTLE_ENDIAN); byteBuffer.putInt(salt.length); byteBuffer.put(salt); byteBuffer.putInt(credentialsBytes.length); byteBuffer.put(credentialsBytes); byte[] bytes = byteBuffer.array(); try { return MessageDigest.getInstance(LOCK_SCREEN_HASH_ALGORITHM).digest(bytes); } catch (NoSuchAlgorithmException e) { // Impossible, SHA-256 must be supported on Android. throw new RuntimeException(e); } } private static SecretKey generateRecoveryKey() throws NoSuchAlgorithmException { KeyGenerator keyGenerator = KeyGenerator.getInstance(RECOVERY_KEY_ALGORITHM); keyGenerator.init(RECOVERY_KEY_SIZE_BITS); return keyGenerator.generateKey(); } } services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java +1 −1 Original line number Diff line number Diff line Loading @@ -74,7 +74,7 @@ public class KeySyncUtils { * * @hide */ public byte[] thmEncryptRecoveryKey( public static byte[] thmEncryptRecoveryKey( PublicKey publicKey, byte[] lockScreenHash, byte[] vaultParams, Loading services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +21 −25 Original line number Diff line number Diff line Loading @@ -29,19 +29,22 @@ import android.security.recoverablekeystore.KeyEntryRecoveryData; import android.security.recoverablekeystore.KeyStoreRecoveryData; import android.security.recoverablekeystore.KeyStoreRecoveryMetadata; import android.security.recoverablekeystore.RecoverableKeyStoreLoader; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage; import java.security.InvalidKeyException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Class with {@link RecoverableKeyStoreLoader} API implementation and internal methods to interact Loading @@ -50,12 +53,13 @@ import java.util.Map; * @hide */ public class RecoverableKeyStoreManager { private static final String TAG = "RecoverableKeyStoreManager"; private static final String TAG = "RecoverableKeyStoreMgr"; private static RecoverableKeyStoreManager mInstance; private final Context mContext; private final RecoverableKeyStoreDb mDatabase; private final RecoverySessionStorage mRecoverySessionStorage; private final ExecutorService mExecutorService; /** * Returns a new or existing instance. Loading @@ -68,7 +72,8 @@ public class RecoverableKeyStoreManager { mInstance = new RecoverableKeyStoreManager( mContext.getApplicationContext(), db, new RecoverySessionStorage()); new RecoverySessionStorage(), Executors.newSingleThreadExecutor()); } return mInstance; } Loading @@ -77,10 +82,12 @@ public class RecoverableKeyStoreManager { RecoverableKeyStoreManager( Context context, RecoverableKeyStoreDb recoverableKeyStoreDb, RecoverySessionStorage recoverySessionStorage) { RecoverySessionStorage recoverySessionStorage, ExecutorService executorService) { mContext = context; mDatabase = recoverableKeyStoreDb; mRecoverySessionStorage = recoverySessionStorage; mExecutorService = executorService; } public int initRecoveryService( Loading Loading @@ -289,17 +296,17 @@ public class RecoverableKeyStoreManager { */ public void lockScreenSecretAvailable( int storedHashType, @NonNull String credential, int userId) { // Notify RecoverableKeystoreLoader about unlock @KeyStoreRecoveryMetadata.LockScreenUiFormat int uiFormat; if (storedHashType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) { uiFormat = KeyStoreRecoveryMetadata.TYPE_PATTERN; } else if (isPin(credential)) { uiFormat = KeyStoreRecoveryMetadata.TYPE_PIN; } else { uiFormat = KeyStoreRecoveryMetadata.TYPE_PASSWORD; // So as not to block the critical path unlocking the phone, defer to another thread. try { mExecutorService.execute(KeySyncTask.newInstance( mContext, mDatabase, userId, storedHashType, credential)); } catch (NoSuchAlgorithmException e) { Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e); } catch (KeyStoreException e) { Log.e(TAG, "Key store error encountered during recoverable key sync", e); } catch (InsecureUserException e) { Log.wtf(TAG, "Impossible - insecure user, but user just entered lock screen", e); } // TODO: check getPendingRecoverySecretTypes. // TODO: compute SHA256 or Argon2id depending on secret type. } /** This function can only be used inside LockSettingsService. */ Loading @@ -310,17 +317,6 @@ public class RecoverableKeyStoreManager { throw new UnsupportedOperationException(); } @VisibleForTesting boolean isPin(@NonNull String credential) { for (int i = 0; i < credential.length(); i++) { char c = credential.charAt(i); if (c < '0' || c > '9') { return false; } } return true; } private void checkRecoverKeyStorePermission() { mContext.enforceCallingOrSelfPermission( RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE, Loading services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java 0 → 100644 +126 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.locksettings.recoverablekeystore; import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PASSWORD; import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PATTERN; import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PIN; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Random; @SmallTest @RunWith(AndroidJUnit4.class) public class KeySyncTaskTest { @Test public void isPin_isTrueForNumericString() { assertTrue(KeySyncTask.isPin("3298432574398654376547")); } @Test public void isPin_isFalseForStringContainingLetters() { assertFalse(KeySyncTask.isPin("398i54369548654")); } @Test public void isPin_isFalseForStringContainingSymbols() { assertFalse(KeySyncTask.isPin("-3987543643")); } @Test public void hashCredentials_returnsSameHashForSameCredentialsAndSalt() { String credentials = "password1234"; byte[] salt = randomBytes(16); assertArrayEquals( KeySyncTask.hashCredentials(salt, credentials), KeySyncTask.hashCredentials(salt, credentials)); } @Test public void hashCredentials_returnsDifferentHashForDifferentCredentials() { byte[] salt = randomBytes(16); assertFalse( Arrays.equals( KeySyncTask.hashCredentials(salt, "password1234"), KeySyncTask.hashCredentials(salt, "password12345"))); } @Test public void hashCredentials_returnsDifferentHashForDifferentSalt() { String credentials = "wowmuch"; assertFalse( Arrays.equals( KeySyncTask.hashCredentials(randomBytes(64), credentials), KeySyncTask.hashCredentials(randomBytes(64), credentials))); } @Test public void hashCredentials_returnsDifferentHashEvenIfConcatIsSame() { assertFalse( Arrays.equals( KeySyncTask.hashCredentials(utf8Bytes("123"), "4567"), KeySyncTask.hashCredentials(utf8Bytes("1234"), "567"))); } @Test public void getUiFormat_returnsPinIfPin() { assertEquals(TYPE_PIN, KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234")); } @Test public void getUiFormat_returnsPasswordIfPassword() { assertEquals(TYPE_PASSWORD, KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234a")); } @Test public void getUiFormat_returnsPatternIfPattern() { assertEquals(TYPE_PATTERN, KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PATTERN, "1234")); } private static byte[] utf8Bytes(String s) { return s.getBytes(StandardCharsets.UTF_8); } private static byte[] randomBytes(int n) { byte[] bytes = new byte[n]; new Random().nextBytes(bytes); return bytes; } } services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +3 −1 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ import org.mockito.MockitoAnnotations; import java.io.File; import java.nio.charset.StandardCharsets; import java.util.concurrent.Executors; @SmallTest @RunWith(AndroidJUnit4.class) Loading Loading @@ -95,7 +96,8 @@ public class RecoverableKeyStoreManagerTest { mRecoverableKeyStoreManager = new RecoverableKeyStoreManager( mMockContext, mRecoverableKeyStoreDb, mRecoverySessionStorage); mRecoverySessionStorage, Executors.newSingleThreadExecutor()); } @After Loading Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java 0 → 100644 +224 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.locksettings.recoverablekeystore; import android.annotation.NonNull; import android.content.Context; import android.security.recoverablekeystore.KeyStoreRecoveryMetadata; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.KeyStoreException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.SecureRandom; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; /** * Task to sync application keys to a remote vault service. * * TODO: implement fully */ public class KeySyncTask implements Runnable { private static final String TAG = "KeySyncTask"; private static final String RECOVERY_KEY_ALGORITHM = "AES"; private static final int RECOVERY_KEY_SIZE_BITS = 256; private static final int SALT_LENGTH_BYTES = 16; private static final int LENGTH_PREFIX_BYTES = Integer.BYTES; private static final String LOCK_SCREEN_HASH_ALGORITHM = "SHA-256"; private final Context mContext; private final RecoverableKeyStoreDb mRecoverableKeyStoreDb; private final int mUserId; private final int mCredentialType; private final String mCredential; public static KeySyncTask newInstance( Context context, RecoverableKeyStoreDb recoverableKeyStoreDb, int userId, int credentialType, String credential ) throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException { return new KeySyncTask( context.getApplicationContext(), recoverableKeyStoreDb, userId, credentialType, credential); } /** * A new task. * * @param recoverableKeyStoreDb Database where the keys are stored. * @param userId The uid of the user whose profile has been unlocked. * @param credentialType The type of credential - i.e., pattern or password. * @param credential The credential, encoded as a {@link String}. */ @VisibleForTesting KeySyncTask( Context context, RecoverableKeyStoreDb recoverableKeyStoreDb, int userId, int credentialType, String credential) { mContext = context; mRecoverableKeyStoreDb = recoverableKeyStoreDb; mUserId = userId; mCredentialType = credentialType; mCredential = credential; } @Override public void run() { try { syncKeys(); } catch (Exception e) { Log.e(TAG, "Unexpected exception thrown during KeySyncTask", e); } } private void syncKeys() { byte[] salt = generateSalt(); byte[] localLskfHash = hashCredentials(salt, mCredential); // TODO: decrypt local wrapped application keys, ready for sync SecretKey recoveryKey; try { recoveryKey = generateRecoveryKey(); } catch (NoSuchAlgorithmException e) { Log.wtf("AES should never be unavailable", e); return; } // TODO: encrypt each application key with recovery key PublicKey vaultKey = getVaultPublicKey(); // TODO: construct vault params and vault metadata byte[] vaultParams = {}; byte[] locallyEncryptedRecoveryKey; try { locallyEncryptedRecoveryKey = KeySyncUtils.thmEncryptRecoveryKey( vaultKey, localLskfHash, vaultParams, recoveryKey); } catch (NoSuchAlgorithmException e) { Log.wtf(TAG, "SecureBox encrypt algorithms unavailable", e); return; } catch (InvalidKeyException e) { Log.e(TAG,"Could not encrypt with recovery key", e); return; } // TODO: send RECOVERABLE_KEYSTORE_SNAPSHOT intent } private PublicKey getVaultPublicKey() { // TODO: fill this in throw new UnsupportedOperationException("TODO: get vault public key."); } /** * The UI best suited to entering the given lock screen. This is synced with the vault so the * user can be shown the same UI when recovering the vault on another device. * * @return The format - either pattern, pin, or password. */ @VisibleForTesting @KeyStoreRecoveryMetadata.LockScreenUiFormat static int getUiFormat( int credentialType, String credential) { if (credentialType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) { return KeyStoreRecoveryMetadata.TYPE_PATTERN; } else if (isPin(credential)) { return KeyStoreRecoveryMetadata.TYPE_PIN; } else { return KeyStoreRecoveryMetadata.TYPE_PASSWORD; } } /** * Generates a salt to include with the lock screen hash. * * @return The salt. */ private byte[] generateSalt() { byte[] salt = new byte[SALT_LENGTH_BYTES]; new SecureRandom().nextBytes(salt); return salt; } /** * Returns {@code true} if {@code credential} looks like a pin. */ @VisibleForTesting static boolean isPin(@NonNull String credential) { int length = credential.length(); for (int i = 0; i < length; i++) { if (!Character.isDigit(credential.charAt(i))) { return false; } } return true; } /** * Hashes {@code credentials} with the given {@code salt}. * * @return The SHA-256 hash. */ @VisibleForTesting static byte[] hashCredentials(byte[] salt, String credentials) { byte[] credentialsBytes = credentials.getBytes(StandardCharsets.UTF_8); ByteBuffer byteBuffer = ByteBuffer.allocate( salt.length + credentialsBytes.length + LENGTH_PREFIX_BYTES * 2); byteBuffer.order(ByteOrder.LITTLE_ENDIAN); byteBuffer.putInt(salt.length); byteBuffer.put(salt); byteBuffer.putInt(credentialsBytes.length); byteBuffer.put(credentialsBytes); byte[] bytes = byteBuffer.array(); try { return MessageDigest.getInstance(LOCK_SCREEN_HASH_ALGORITHM).digest(bytes); } catch (NoSuchAlgorithmException e) { // Impossible, SHA-256 must be supported on Android. throw new RuntimeException(e); } } private static SecretKey generateRecoveryKey() throws NoSuchAlgorithmException { KeyGenerator keyGenerator = KeyGenerator.getInstance(RECOVERY_KEY_ALGORITHM); keyGenerator.init(RECOVERY_KEY_SIZE_BITS); return keyGenerator.generateKey(); } }
services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java +1 −1 Original line number Diff line number Diff line Loading @@ -74,7 +74,7 @@ public class KeySyncUtils { * * @hide */ public byte[] thmEncryptRecoveryKey( public static byte[] thmEncryptRecoveryKey( PublicKey publicKey, byte[] lockScreenHash, byte[] vaultParams, Loading
services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java +21 −25 Original line number Diff line number Diff line Loading @@ -29,19 +29,22 @@ import android.security.recoverablekeystore.KeyEntryRecoveryData; import android.security.recoverablekeystore.KeyStoreRecoveryData; import android.security.recoverablekeystore.KeyStoreRecoveryMetadata; import android.security.recoverablekeystore.RecoverableKeyStoreLoader; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage; import java.security.InvalidKeyException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Class with {@link RecoverableKeyStoreLoader} API implementation and internal methods to interact Loading @@ -50,12 +53,13 @@ import java.util.Map; * @hide */ public class RecoverableKeyStoreManager { private static final String TAG = "RecoverableKeyStoreManager"; private static final String TAG = "RecoverableKeyStoreMgr"; private static RecoverableKeyStoreManager mInstance; private final Context mContext; private final RecoverableKeyStoreDb mDatabase; private final RecoverySessionStorage mRecoverySessionStorage; private final ExecutorService mExecutorService; /** * Returns a new or existing instance. Loading @@ -68,7 +72,8 @@ public class RecoverableKeyStoreManager { mInstance = new RecoverableKeyStoreManager( mContext.getApplicationContext(), db, new RecoverySessionStorage()); new RecoverySessionStorage(), Executors.newSingleThreadExecutor()); } return mInstance; } Loading @@ -77,10 +82,12 @@ public class RecoverableKeyStoreManager { RecoverableKeyStoreManager( Context context, RecoverableKeyStoreDb recoverableKeyStoreDb, RecoverySessionStorage recoverySessionStorage) { RecoverySessionStorage recoverySessionStorage, ExecutorService executorService) { mContext = context; mDatabase = recoverableKeyStoreDb; mRecoverySessionStorage = recoverySessionStorage; mExecutorService = executorService; } public int initRecoveryService( Loading Loading @@ -289,17 +296,17 @@ public class RecoverableKeyStoreManager { */ public void lockScreenSecretAvailable( int storedHashType, @NonNull String credential, int userId) { // Notify RecoverableKeystoreLoader about unlock @KeyStoreRecoveryMetadata.LockScreenUiFormat int uiFormat; if (storedHashType == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) { uiFormat = KeyStoreRecoveryMetadata.TYPE_PATTERN; } else if (isPin(credential)) { uiFormat = KeyStoreRecoveryMetadata.TYPE_PIN; } else { uiFormat = KeyStoreRecoveryMetadata.TYPE_PASSWORD; // So as not to block the critical path unlocking the phone, defer to another thread. try { mExecutorService.execute(KeySyncTask.newInstance( mContext, mDatabase, userId, storedHashType, credential)); } catch (NoSuchAlgorithmException e) { Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e); } catch (KeyStoreException e) { Log.e(TAG, "Key store error encountered during recoverable key sync", e); } catch (InsecureUserException e) { Log.wtf(TAG, "Impossible - insecure user, but user just entered lock screen", e); } // TODO: check getPendingRecoverySecretTypes. // TODO: compute SHA256 or Argon2id depending on secret type. } /** This function can only be used inside LockSettingsService. */ Loading @@ -310,17 +317,6 @@ public class RecoverableKeyStoreManager { throw new UnsupportedOperationException(); } @VisibleForTesting boolean isPin(@NonNull String credential) { for (int i = 0; i < credential.length(); i++) { char c = credential.charAt(i); if (c < '0' || c > '9') { return false; } } return true; } private void checkRecoverKeyStorePermission() { mContext.enforceCallingOrSelfPermission( RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE, Loading
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java 0 → 100644 +126 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.locksettings.recoverablekeystore; import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PASSWORD; import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PATTERN; import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PIN; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Random; @SmallTest @RunWith(AndroidJUnit4.class) public class KeySyncTaskTest { @Test public void isPin_isTrueForNumericString() { assertTrue(KeySyncTask.isPin("3298432574398654376547")); } @Test public void isPin_isFalseForStringContainingLetters() { assertFalse(KeySyncTask.isPin("398i54369548654")); } @Test public void isPin_isFalseForStringContainingSymbols() { assertFalse(KeySyncTask.isPin("-3987543643")); } @Test public void hashCredentials_returnsSameHashForSameCredentialsAndSalt() { String credentials = "password1234"; byte[] salt = randomBytes(16); assertArrayEquals( KeySyncTask.hashCredentials(salt, credentials), KeySyncTask.hashCredentials(salt, credentials)); } @Test public void hashCredentials_returnsDifferentHashForDifferentCredentials() { byte[] salt = randomBytes(16); assertFalse( Arrays.equals( KeySyncTask.hashCredentials(salt, "password1234"), KeySyncTask.hashCredentials(salt, "password12345"))); } @Test public void hashCredentials_returnsDifferentHashForDifferentSalt() { String credentials = "wowmuch"; assertFalse( Arrays.equals( KeySyncTask.hashCredentials(randomBytes(64), credentials), KeySyncTask.hashCredentials(randomBytes(64), credentials))); } @Test public void hashCredentials_returnsDifferentHashEvenIfConcatIsSame() { assertFalse( Arrays.equals( KeySyncTask.hashCredentials(utf8Bytes("123"), "4567"), KeySyncTask.hashCredentials(utf8Bytes("1234"), "567"))); } @Test public void getUiFormat_returnsPinIfPin() { assertEquals(TYPE_PIN, KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234")); } @Test public void getUiFormat_returnsPasswordIfPassword() { assertEquals(TYPE_PASSWORD, KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PASSWORD, "1234a")); } @Test public void getUiFormat_returnsPatternIfPattern() { assertEquals(TYPE_PATTERN, KeySyncTask.getUiFormat(CREDENTIAL_TYPE_PATTERN, "1234")); } private static byte[] utf8Bytes(String s) { return s.getBytes(StandardCharsets.UTF_8); } private static byte[] randomBytes(int n) { byte[] bytes = new byte[n]; new Random().nextBytes(bytes); return bytes; } }
services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +3 −1 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ import org.mockito.MockitoAnnotations; import java.io.File; import java.nio.charset.StandardCharsets; import java.util.concurrent.Executors; @SmallTest @RunWith(AndroidJUnit4.class) Loading Loading @@ -95,7 +96,8 @@ public class RecoverableKeyStoreManagerTest { mRecoverableKeyStoreManager = new RecoverableKeyStoreManager( mMockContext, mRecoverableKeyStoreDb, mRecoverySessionStorage); mRecoverySessionStorage, Executors.newSingleThreadExecutor()); } @After Loading