Keymaster init for work profile am: dc283a8
am: e28ff22
* commit 'e28ff22bc4b213fe78dc50c0cf620569925392c7':
Keymaster init for work profile
Change-Id: Ic3febb501b2929dea1411de2f2bdb690f7117572
This commit is contained in:
@@ -36,6 +36,8 @@ interface ILockSettings {
|
||||
boolean checkVoldPassword(int userId);
|
||||
boolean havePattern(int userId);
|
||||
boolean havePassword(int userId);
|
||||
void setSeparateProfileChallengeEnabled(int userId, boolean enabled, String managedUserPassword);
|
||||
boolean getSeparateProfileChallengeEnabled(int userId);
|
||||
void registerStrongAuthTracker(in IStrongAuthTracker tracker);
|
||||
void unregisterStrongAuthTracker(in IStrongAuthTracker tracker);
|
||||
void requireStrongAuth(int strongAuthReason, int userId);
|
||||
|
||||
@@ -137,8 +137,6 @@ public class LockPatternUtils {
|
||||
private static final String ENABLED_TRUST_AGENTS = "lockscreen.enabledtrustagents";
|
||||
private static final String IS_TRUST_USUALLY_MANAGED = "lockscreen.istrustusuallymanaged";
|
||||
|
||||
private static final String SEPARATE_PROFILE_CHALLENGE_KEY = "lockscreen.profilechallenge";
|
||||
|
||||
// Maximum allowed number of repeated or ordered characters in a sequence before we'll
|
||||
// consider it a complex PIN/password.
|
||||
public static final int MAX_ALLOWED_SEQUENCE = 3;
|
||||
@@ -785,6 +783,7 @@ public class LockPatternUtils {
|
||||
}
|
||||
|
||||
getLockSettings().setLockPassword(password, savedPassword, userHandle);
|
||||
getLockSettings().setSeparateProfileChallengeEnabled(userHandle, true, null);
|
||||
int computedQuality = computePasswordQuality(password);
|
||||
|
||||
// Update the device encryption password.
|
||||
@@ -919,11 +918,23 @@ public class LockPatternUtils {
|
||||
/**
|
||||
* Enables/disables the Separate Profile Challenge for this {@param userHandle}. This is a no-op
|
||||
* for user handles that do not belong to a managed profile.
|
||||
*
|
||||
* @param userHandle Managed profile user id
|
||||
* @param enabled True if separate challenge is enabled
|
||||
* @param managedUserPassword Managed profile previous password. Null when {@param enabled} is
|
||||
* true
|
||||
*/
|
||||
public void setSeparateProfileChallengeEnabled(int userHandle, boolean enabled) {
|
||||
public void setSeparateProfileChallengeEnabled(int userHandle, boolean enabled,
|
||||
String managedUserPassword) {
|
||||
UserInfo info = getUserManager().getUserInfo(userHandle);
|
||||
if (info.isManagedProfile()) {
|
||||
setBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, enabled, userHandle);
|
||||
try {
|
||||
getLockSettings().setSeparateProfileChallengeEnabled(userHandle, enabled,
|
||||
managedUserPassword);
|
||||
onAfterChangingPassword(userHandle);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Couldn't update work profile challenge enabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -935,7 +946,13 @@ public class LockPatternUtils {
|
||||
if (info == null || !info.isManagedProfile()) {
|
||||
return false;
|
||||
}
|
||||
return getBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, false, userHandle);
|
||||
try {
|
||||
return getLockSettings().getSeparateProfileChallengeEnabled(userHandle);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Couldn't get separate profile challenge enabled");
|
||||
// Default value is false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -56,6 +56,9 @@ import android.provider.Settings;
|
||||
import android.provider.Settings.Secure;
|
||||
import android.provider.Settings.SettingNotFoundException;
|
||||
import android.security.KeyStore;
|
||||
import android.security.keystore.AndroidKeyStoreProvider;
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.security.keystore.KeyProtection;
|
||||
import android.service.gatekeeper.GateKeeperResponse;
|
||||
import android.service.gatekeeper.IGateKeeperService;
|
||||
import android.text.TextUtils;
|
||||
@@ -68,15 +71,33 @@ import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.internal.widget.VerifyCredentialResponse;
|
||||
import com.android.server.LockSettingsStorage.CredentialHash;
|
||||
|
||||
import libcore.util.HexEncoding;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
|
||||
/**
|
||||
* Keeps the lock pattern/password data and related settings for each user.
|
||||
* Used by LockPatternUtils. Needs to be a service because Settings app also needs
|
||||
@@ -90,6 +111,12 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
private static final int FBE_ENCRYPTED_NOTIFICATION = 0;
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final String PROFILE_KEY_NAME_ENCRYPT = "profile_key_name_encrypt_";
|
||||
private static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_";
|
||||
private static final int PROFILE_KEY_IV_SIZE = 12;
|
||||
private static final String SEPARATE_PROFILE_CHALLENGE_KEY = "lockscreen.profilechallenge";
|
||||
private final Object mSeparateChallengeLock = new Object();
|
||||
|
||||
private final Context mContext;
|
||||
private final LockSettingsStorage mStorage;
|
||||
private final LockSettingsStrongAuth mStrongAuth;
|
||||
@@ -125,6 +152,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
AndroidKeyStoreProvider.install();
|
||||
mLockSettingsService = new LockSettingsService(getContext());
|
||||
publishBinderService("lock_settings", mLockSettingsService);
|
||||
}
|
||||
@@ -149,6 +177,46 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tie managed profile to primary profile if it is in unified mode and not tied before.
|
||||
*
|
||||
* @param managedUserId Managed profile user Id
|
||||
* @param managedUserPassword Managed profile original password (when it has separated lock).
|
||||
* NULL when it does not have a separated lock before.
|
||||
*/
|
||||
public void tieManagedProfileLockIfNecessary(int managedUserId, String managedUserPassword) {
|
||||
if (DEBUG) Slog.v(TAG, "Check child profile lock for user: " + managedUserId);
|
||||
// Only for managed profile
|
||||
if (!UserManager.get(mContext).getUserInfo(managedUserId).isManagedProfile()) {
|
||||
return;
|
||||
}
|
||||
// Do not tie managed profile when work challenge is enabled
|
||||
if (mLockPatternUtils.isSeparateProfileChallengeEnabled(managedUserId)) {
|
||||
return;
|
||||
}
|
||||
// Do not tie managed profile to parent when it's done already
|
||||
if (mStorage.hasChildProfileLock(managedUserId)) {
|
||||
return;
|
||||
}
|
||||
// Do not tie it to parent when parent does not have a screen lock
|
||||
final int parentId = mUserManager.getProfileParent(managedUserId).id;
|
||||
if (!mStorage.hasPassword(parentId) && !mStorage.hasPattern(parentId)) {
|
||||
if (DEBUG) Slog.v(TAG, "Parent does not have a screen lock");
|
||||
return;
|
||||
}
|
||||
if (DEBUG) Slog.v(TAG, "Tie managed profile to parent now!");
|
||||
byte[] randomLockSeed = new byte[] {};
|
||||
try {
|
||||
randomLockSeed = SecureRandom.getInstance("SHA1PRNG").generateSeed(40);
|
||||
String newPassword = String.valueOf(HexEncoding.encode(randomLockSeed));
|
||||
setLockPasswordInternal(newPassword, managedUserPassword, managedUserId);
|
||||
tieProfileLockToParent(managedUserId, newPassword);
|
||||
} catch (NoSuchAlgorithmException | RemoteException e) {
|
||||
Slog.e(TAG, "Fail to tie managed profile", e);
|
||||
// Nothing client can do to fix this issue, so we do not throw exception out
|
||||
}
|
||||
}
|
||||
|
||||
public LockSettingsService(Context context) {
|
||||
mContext = context;
|
||||
mStrongAuth = new LockSettingsStrongAuth(context);
|
||||
@@ -271,6 +339,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
}
|
||||
|
||||
public void onUnlockUser(int userId) {
|
||||
tieManagedProfileLockIfNecessary(userId, null);
|
||||
hideEncryptionNotification(new UserHandle(userId));
|
||||
|
||||
// Now we have unlocked the parent user we should show notifications
|
||||
@@ -294,8 +363,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
// Notify keystore that a new user was added.
|
||||
final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
|
||||
final KeyStore ks = KeyStore.getInstance();
|
||||
final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
|
||||
final UserInfo parentInfo = um.getProfileParent(userHandle);
|
||||
final UserInfo parentInfo = mUserManager.getProfileParent(userHandle);
|
||||
final int parentHandle = parentInfo != null ? parentInfo.id : -1;
|
||||
ks.onUserAdded(userHandle, parentHandle);
|
||||
} else if (Intent.ACTION_USER_STARTING.equals(intent.getAction())) {
|
||||
@@ -343,9 +411,8 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
|
||||
// These Settings changed after multi-user was enabled, hence need to be moved per user.
|
||||
if (getString("migrated_user_specific", null, 0) == null) {
|
||||
final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
|
||||
final ContentResolver cr = mContext.getContentResolver();
|
||||
List<UserInfo> users = um.getUsers();
|
||||
List<UserInfo> users = mUserManager.getUsers();
|
||||
for (int user = 0; user < users.size(); user++) {
|
||||
// Migrate owner info
|
||||
final int userId = users.get(user).id;
|
||||
@@ -380,8 +447,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
|
||||
// Migrates biometric weak such that the fallback mechanism becomes the primary.
|
||||
if (getString("migrated_biometric_weak", null, 0) == null) {
|
||||
final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
|
||||
List<UserInfo> users = um.getUsers();
|
||||
List<UserInfo> users = mUserManager.getUsers();
|
||||
for (int i = 0; i < users.size(); i++) {
|
||||
int userId = users.get(i).id;
|
||||
long type = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
|
||||
@@ -407,9 +473,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
// user was present on the system, so if we're upgrading to M and there is more than one
|
||||
// user we disable the flag to remain consistent.
|
||||
if (getString("migrated_lockscreen_disabled", null, 0) == null) {
|
||||
final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
|
||||
|
||||
final List<UserInfo> users = um.getUsers();
|
||||
final List<UserInfo> users = mUserManager.getUsers();
|
||||
final int userCount = users.size();
|
||||
int switchableUsers = 0;
|
||||
for (int i = 0; i < userCount; i++) {
|
||||
@@ -468,6 +532,27 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getSeparateProfileChallengeEnabled(int userId) throws RemoteException {
|
||||
synchronized (mSeparateChallengeLock) {
|
||||
return getBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, false, userId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSeparateProfileChallengeEnabled(int userId, boolean enabled,
|
||||
String managedUserPassword) throws RemoteException {
|
||||
synchronized (mSeparateChallengeLock) {
|
||||
setBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, enabled, userId);
|
||||
if (enabled) {
|
||||
mStorage.removeChildProfileLock(userId);
|
||||
removeKeystoreProfileKey(userId);
|
||||
} else {
|
||||
tieManagedProfileLockIfNecessary(userId, managedUserPassword);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBoolean(String key, boolean value, int userId) throws RemoteException {
|
||||
checkWritePermission(userId);
|
||||
@@ -536,61 +621,65 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
@Override
|
||||
public boolean havePassword(int userId) throws RemoteException {
|
||||
// Do we need a permissions check here?
|
||||
|
||||
return mStorage.hasPassword(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean havePattern(int userId) throws RemoteException {
|
||||
// Do we need a permissions check here?
|
||||
|
||||
return mStorage.hasPattern(userId);
|
||||
}
|
||||
|
||||
private void setKeystorePassword(String password, int userHandle) {
|
||||
final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
|
||||
final KeyStore ks = KeyStore.getInstance();
|
||||
|
||||
if (um.getUserInfo(userHandle).isManagedProfile()) {
|
||||
if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userHandle)) {
|
||||
ks.onUserPasswordChanged(userHandle, password);
|
||||
} else {
|
||||
throw new RuntimeException("Can't set keystore password on a profile that "
|
||||
+ "doesn't have a profile challenge.");
|
||||
}
|
||||
} else {
|
||||
final List<UserInfo> profiles = um.getProfiles(userHandle);
|
||||
for (UserInfo pi : profiles) {
|
||||
// Change password on the given user and all its profiles that don't have
|
||||
// their own profile challenge enabled.
|
||||
if (pi.id == userHandle || (pi.isManagedProfile()
|
||||
&& !mLockPatternUtils.isSeparateProfileChallengeEnabled(pi.id))) {
|
||||
ks.onUserPasswordChanged(pi.id, password);
|
||||
}
|
||||
}
|
||||
}
|
||||
ks.onUserPasswordChanged(userHandle, password);
|
||||
}
|
||||
|
||||
private void unlockKeystore(String password, int userHandle) {
|
||||
final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
|
||||
if (DEBUG) Slog.v(TAG, "Unlock keystore for user: " + userHandle);
|
||||
final KeyStore ks = KeyStore.getInstance();
|
||||
ks.unlock(userHandle, password);
|
||||
}
|
||||
|
||||
if (um.getUserInfo(userHandle).isManagedProfile()) {
|
||||
if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userHandle)) {
|
||||
ks.unlock(userHandle, password);
|
||||
private String getDecryptedPasswordForTiedProfile(int userId)
|
||||
throws KeyStoreException, UnrecoverableKeyException,
|
||||
NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
|
||||
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException,
|
||||
CertificateException, IOException {
|
||||
if (DEBUG) Slog.v(TAG, "Unlock keystore for child profile");
|
||||
byte[] storedData = mStorage.readChildProfileLock(userId);
|
||||
if (storedData == null) {
|
||||
throw new FileNotFoundException("Child profile lock file not found");
|
||||
}
|
||||
byte[] iv = Arrays.copyOfRange(storedData, 0, PROFILE_KEY_IV_SIZE);
|
||||
byte[] encryptedPassword = Arrays.copyOfRange(storedData, PROFILE_KEY_IV_SIZE,
|
||||
storedData.length);
|
||||
byte[] decryptionResult;
|
||||
java.security.KeyStore keyStore = java.security.KeyStore.getInstance("AndroidKeyStore");
|
||||
keyStore.load(null);
|
||||
SecretKey decryptionKey = (SecretKey) keyStore.getKey(
|
||||
PROFILE_KEY_NAME_DECRYPT + userId, null);
|
||||
|
||||
Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
|
||||
+ KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE);
|
||||
|
||||
cipher.init(Cipher.DECRYPT_MODE, decryptionKey, new GCMParameterSpec(128, iv));
|
||||
decryptionResult = cipher.doFinal(encryptedPassword);
|
||||
return new String(decryptionResult, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private void unlockChildProfile(int profileHandle) throws RemoteException {
|
||||
try {
|
||||
doVerifyPassword(getDecryptedPasswordForTiedProfile(profileHandle), false,
|
||||
0 /* no challenge */, profileHandle);
|
||||
} catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
|
||||
| NoSuchAlgorithmException | NoSuchPaddingException
|
||||
| InvalidAlgorithmParameterException | IllegalBlockSizeException
|
||||
| BadPaddingException | CertificateException | IOException e) {
|
||||
if (e instanceof FileNotFoundException) {
|
||||
Slog.i(TAG, "Child profile key not found");
|
||||
} else {
|
||||
throw new RuntimeException("Can't unlock a profile explicitly if it "
|
||||
+ "doesn't have a profile challenge.");
|
||||
}
|
||||
} else {
|
||||
final List<UserInfo> profiles = um.getProfiles(userHandle);
|
||||
for (UserInfo pi : profiles) {
|
||||
// Unlock the given user and all its profiles that don't have
|
||||
// their own profile challenge enabled.
|
||||
if (pi.id == userHandle || (pi.isManagedProfile()
|
||||
&& !mLockPatternUtils.isSeparateProfileChallengeEnabled(pi.id))) {
|
||||
ks.unlock(pi.id, password);
|
||||
}
|
||||
Slog.e(TAG, "Failed to decrypt child profile key", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -627,6 +716,21 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
try {
|
||||
if (!mUserManager.getUserInfo(userId).isManagedProfile()) {
|
||||
final List<UserInfo> profiles = mUserManager.getProfiles(userId);
|
||||
for (UserInfo pi : profiles) {
|
||||
// Unlock managed profile with unified lock
|
||||
if (pi.isManagedProfile()
|
||||
&& !mLockPatternUtils.isSeparateProfileChallengeEnabled(pi.id)
|
||||
&& mStorage.hasChildProfileLock(pi.id)) {
|
||||
unlockChildProfile(pi.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.d(TAG, "Failed to unlock child profile", e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getCurrentHandle(int userId) {
|
||||
@@ -661,10 +765,57 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
return currentHandle;
|
||||
}
|
||||
|
||||
private void onUserLockChanged(int userId) throws RemoteException {
|
||||
if (mUserManager.getUserInfo(userId).isManagedProfile()) {
|
||||
return;
|
||||
}
|
||||
final boolean isSecure = mStorage.hasPassword(userId) || mStorage.hasPattern(userId);
|
||||
final List<UserInfo> profiles = mUserManager.getProfiles(userId);
|
||||
final int size = profiles.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
final UserInfo profile = profiles.get(i);
|
||||
if (profile.isManagedProfile()) {
|
||||
final int managedUserId = profile.id;
|
||||
if (mLockPatternUtils.isSeparateProfileChallengeEnabled(managedUserId)) {
|
||||
continue;
|
||||
}
|
||||
if (isSecure) {
|
||||
tieManagedProfileLockIfNecessary(managedUserId, null);
|
||||
} else {
|
||||
getGateKeeperService().clearSecureUserId(managedUserId);
|
||||
mStorage.writePatternHash(null, managedUserId);
|
||||
setKeystorePassword(null, managedUserId);
|
||||
clearUserKeyProtection(managedUserId);
|
||||
mStorage.removeChildProfileLock(managedUserId);
|
||||
removeKeystoreProfileKey(managedUserId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isManagedProfileWithUnifiedLock(int userId) {
|
||||
return mUserManager.getUserInfo(userId).isManagedProfile()
|
||||
&& !mLockPatternUtils.isSeparateProfileChallengeEnabled(userId);
|
||||
}
|
||||
|
||||
private boolean isManagedProfileWithSeparatedLock(int userId) {
|
||||
return mUserManager.getUserInfo(userId).isManagedProfile()
|
||||
&& mLockPatternUtils.isSeparateProfileChallengeEnabled(userId);
|
||||
}
|
||||
|
||||
// This method should be called by LockPatternUtil only, all internal methods in this class
|
||||
// should call setLockPatternInternal.
|
||||
@Override
|
||||
public void setLockPattern(String pattern, String savedCredential, int userId)
|
||||
throws RemoteException {
|
||||
synchronized (mSeparateChallengeLock) {
|
||||
setLockPatternInternal(pattern, savedCredential, userId);
|
||||
setSeparateProfileChallengeEnabled(userId, true, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void setLockPatternInternal(String pattern, String savedCredential, int userId)
|
||||
throws RemoteException {
|
||||
byte[] currentHandle = getCurrentHandle(userId);
|
||||
|
||||
if (pattern == null) {
|
||||
@@ -672,55 +823,157 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
mStorage.writePatternHash(null, userId);
|
||||
setKeystorePassword(null, userId);
|
||||
clearUserKeyProtection(userId);
|
||||
onUserLockChanged(userId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentHandle == null) {
|
||||
if (savedCredential != null) {
|
||||
Slog.w(TAG, "Saved credential provided, but none stored");
|
||||
if (isManagedProfileWithUnifiedLock(userId)) {
|
||||
// get credential from keystore when managed profile has unified lock
|
||||
try {
|
||||
savedCredential = getDecryptedPasswordForTiedProfile(userId);
|
||||
} catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
|
||||
| NoSuchAlgorithmException | NoSuchPaddingException
|
||||
| InvalidAlgorithmParameterException | IllegalBlockSizeException
|
||||
| BadPaddingException | CertificateException | IOException e) {
|
||||
if (e instanceof FileNotFoundException) {
|
||||
Slog.i(TAG, "Child profile key not found");
|
||||
} else {
|
||||
Slog.e(TAG, "Failed to decrypt child profile key", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (currentHandle == null) {
|
||||
if (savedCredential != null) {
|
||||
Slog.w(TAG, "Saved credential provided, but none stored");
|
||||
}
|
||||
savedCredential = null;
|
||||
}
|
||||
savedCredential = null;
|
||||
}
|
||||
|
||||
byte[] enrolledHandle = enrollCredential(currentHandle, savedCredential, pattern, userId);
|
||||
if (enrolledHandle != null) {
|
||||
mStorage.writePatternHash(enrolledHandle, userId);
|
||||
setUserKeyProtection(userId, pattern, verifyPattern(pattern, 0, userId));
|
||||
onUserLockChanged(userId);
|
||||
} else {
|
||||
throw new RemoteException("Failed to enroll pattern");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// This method should be called by LockPatternUtil only, all internal methods in this class
|
||||
// should call setLockPasswordInternal.
|
||||
@Override
|
||||
public void setLockPassword(String password, String savedCredential, int userId)
|
||||
throws RemoteException {
|
||||
byte[] currentHandle = getCurrentHandle(userId);
|
||||
synchronized (mSeparateChallengeLock) {
|
||||
setLockPasswordInternal(password, savedCredential, userId);
|
||||
setSeparateProfileChallengeEnabled(userId, true, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void setLockPasswordInternal(String password, String savedCredential, int userId)
|
||||
throws RemoteException {
|
||||
byte[] currentHandle = getCurrentHandle(userId);
|
||||
if (password == null) {
|
||||
getGateKeeperService().clearSecureUserId(userId);
|
||||
mStorage.writePasswordHash(null, userId);
|
||||
setKeystorePassword(null, userId);
|
||||
clearUserKeyProtection(userId);
|
||||
onUserLockChanged(userId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentHandle == null) {
|
||||
if (savedCredential != null) {
|
||||
Slog.w(TAG, "Saved credential provided, but none stored");
|
||||
if (isManagedProfileWithUnifiedLock(userId)) {
|
||||
// get credential from keystore when managed profile has unified lock
|
||||
try {
|
||||
savedCredential = getDecryptedPasswordForTiedProfile(userId);
|
||||
} catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
|
||||
| NoSuchAlgorithmException | NoSuchPaddingException
|
||||
| InvalidAlgorithmParameterException | IllegalBlockSizeException
|
||||
| BadPaddingException | CertificateException | IOException e) {
|
||||
if (e instanceof FileNotFoundException) {
|
||||
Slog.i(TAG, "Child profile key not found");
|
||||
} else {
|
||||
Slog.e(TAG, "Failed to decrypt child profile key", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (currentHandle == null) {
|
||||
if (savedCredential != null) {
|
||||
Slog.w(TAG, "Saved credential provided, but none stored");
|
||||
}
|
||||
savedCredential = null;
|
||||
}
|
||||
savedCredential = null;
|
||||
}
|
||||
|
||||
byte[] enrolledHandle = enrollCredential(currentHandle, savedCredential, password, userId);
|
||||
if (enrolledHandle != null) {
|
||||
mStorage.writePasswordHash(enrolledHandle, userId);
|
||||
setUserKeyProtection(userId, password, verifyPassword(password, 0, userId));
|
||||
onUserLockChanged(userId);
|
||||
} else {
|
||||
throw new RemoteException("Failed to enroll password");
|
||||
}
|
||||
}
|
||||
|
||||
private void tieProfileLockToParent(int userId, String password) {
|
||||
if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
|
||||
byte[] randomLockSeed = password.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] encryptionResult;
|
||||
byte[] iv;
|
||||
try {
|
||||
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
|
||||
keyGenerator.init(new SecureRandom());
|
||||
SecretKey secretKey = keyGenerator.generateKey();
|
||||
|
||||
java.security.KeyStore keyStore = java.security.KeyStore.getInstance("AndroidKeyStore");
|
||||
keyStore.load(null);
|
||||
keyStore.setEntry(
|
||||
PROFILE_KEY_NAME_ENCRYPT + userId,
|
||||
new java.security.KeyStore.SecretKeyEntry(secretKey),
|
||||
new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||
.build());
|
||||
keyStore.setEntry(
|
||||
PROFILE_KEY_NAME_DECRYPT + userId,
|
||||
new java.security.KeyStore.SecretKeyEntry(secretKey),
|
||||
new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||
.setUserAuthenticationRequired(true)
|
||||
.setUserAuthenticationValidityDurationSeconds(30)
|
||||
.build());
|
||||
|
||||
// Key imported, obtain a reference to it.
|
||||
SecretKey keyStoreEncryptionKey = (SecretKey) keyStore.getKey(
|
||||
PROFILE_KEY_NAME_ENCRYPT + userId, null);
|
||||
// The original key can now be discarded.
|
||||
|
||||
Cipher cipher = Cipher.getInstance(
|
||||
KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
|
||||
+ KeyProperties.ENCRYPTION_PADDING_NONE);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keyStoreEncryptionKey);
|
||||
encryptionResult = cipher.doFinal(randomLockSeed);
|
||||
iv = cipher.getIV();
|
||||
} catch (CertificateException | UnrecoverableKeyException
|
||||
| IOException | BadPaddingException | IllegalBlockSizeException | KeyStoreException
|
||||
| NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
|
||||
throw new RuntimeException("Failed to encrypt key", e);
|
||||
}
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
try {
|
||||
if (iv.length != PROFILE_KEY_IV_SIZE) {
|
||||
throw new RuntimeException("Invalid iv length: " + iv.length);
|
||||
}
|
||||
outputStream.write(iv);
|
||||
outputStream.write(encryptionResult);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to concatenate byte arrays", e);
|
||||
}
|
||||
mStorage.writeChildProfileLock(userId, outputStream.toByteArray());
|
||||
}
|
||||
|
||||
private byte[] enrollCredential(byte[] enrolledHandle,
|
||||
String enrolledCredential, String toEnroll, int userId)
|
||||
throws RemoteException {
|
||||
@@ -820,7 +1073,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
@Override
|
||||
public void setCredential(String pattern, String oldPattern, int userId)
|
||||
throws RemoteException {
|
||||
setLockPattern(pattern, oldPattern, userId);
|
||||
setLockPatternInternal(pattern, oldPattern, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -838,7 +1091,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
|
||||
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK
|
||||
&& shouldReEnrollBaseZero) {
|
||||
setLockPattern(pattern, patternToVerify, userId);
|
||||
setLockPatternInternal(pattern, patternToVerify, userId);
|
||||
}
|
||||
|
||||
return response;
|
||||
@@ -866,7 +1119,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
@Override
|
||||
public void setCredential(String password, String oldPassword, int userId)
|
||||
throws RemoteException {
|
||||
setLockPassword(password, oldPassword, userId);
|
||||
setLockPasswordInternal(password, oldPassword, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -947,8 +1200,7 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
" with token length " + response.getPayload().length);
|
||||
unlockUser(userId, response.getPayload(), secretFromCredential(credential));
|
||||
|
||||
UserInfo info = UserManager.get(mContext).getUserInfo(userId);
|
||||
if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
|
||||
if (isManagedProfileWithSeparatedLock(userId)) {
|
||||
TrustManager trustManager =
|
||||
(TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
|
||||
trustManager.setDeviceLockedForUser(userId, false);
|
||||
@@ -1027,6 +1279,23 @@ public class LockSettingsService extends ILockSettings.Stub {
|
||||
} catch (RemoteException ex) {
|
||||
Slog.w(TAG, "unable to clear GK secure user id");
|
||||
}
|
||||
if (mUserManager.getUserInfo(userId).isManagedProfile()) {
|
||||
removeKeystoreProfileKey(userId);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeKeystoreProfileKey(int targetUserId) {
|
||||
if (DEBUG) Slog.v(TAG, "Remove keystore profile key for user: " + targetUserId);
|
||||
try {
|
||||
java.security.KeyStore keyStore = java.security.KeyStore.getInstance("AndroidKeyStore");
|
||||
keyStore.load(null);
|
||||
keyStore.deleteEntry(PROFILE_KEY_NAME_ENCRYPT + targetUserId);
|
||||
keyStore.deleteEntry(PROFILE_KEY_NAME_DECRYPT + targetUserId);
|
||||
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
|
||||
| IOException e) {
|
||||
// We have tried our best to remove all keys
|
||||
Slog.e(TAG, "Unable to remove keystore profile key for user:" + targetUserId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.android.server;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
@@ -30,6 +29,7 @@ import android.os.UserManager;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -44,6 +44,7 @@ class LockSettingsStorage {
|
||||
|
||||
private static final String TAG = "LockSettingsStorage";
|
||||
private static final String TABLE = "locksettings";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final String COLUMN_KEY = "name";
|
||||
private static final String COLUMN_USERID = "user";
|
||||
@@ -62,6 +63,7 @@ class LockSettingsStorage {
|
||||
private static final String LEGACY_LOCK_PATTERN_FILE = "gesture.key";
|
||||
private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key";
|
||||
private static final String LEGACY_LOCK_PASSWORD_FILE = "password.key";
|
||||
private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key";
|
||||
|
||||
private static final Object DEFAULT = new Object();
|
||||
|
||||
@@ -70,8 +72,7 @@ class LockSettingsStorage {
|
||||
private final Cache mCache = new Cache();
|
||||
private final Object mFileWriteLock = new Object();
|
||||
|
||||
private int mStoredCredentialType;
|
||||
private LockPatternUtils mLockPatternUtils;
|
||||
private SparseArray<Integer> mStoredCredentialType;
|
||||
|
||||
class CredentialHash {
|
||||
static final int TYPE_NONE = -1;
|
||||
@@ -101,7 +102,7 @@ class LockSettingsStorage {
|
||||
public LockSettingsStorage(Context context, Callback callback) {
|
||||
mContext = context;
|
||||
mOpenHelper = new DatabaseHelper(context, callback);
|
||||
mLockPatternUtils = new LockPatternUtils(context);
|
||||
mStoredCredentialType = new SparseArray<Integer>();
|
||||
}
|
||||
|
||||
public void writeKeyValue(String key, String value, int userId) {
|
||||
@@ -182,32 +183,34 @@ class LockSettingsStorage {
|
||||
}
|
||||
|
||||
public int getStoredCredentialType(int userId) {
|
||||
if (mStoredCredentialType != 0) {
|
||||
return mStoredCredentialType;
|
||||
final Integer cachedStoredCredentialType = mStoredCredentialType.get(userId);
|
||||
if (cachedStoredCredentialType != null) {
|
||||
return cachedStoredCredentialType.intValue();
|
||||
}
|
||||
|
||||
int storedCredentialType;
|
||||
CredentialHash pattern = readPatternHash(userId);
|
||||
if (pattern == null) {
|
||||
if (readPasswordHash(userId) != null) {
|
||||
mStoredCredentialType = CredentialHash.TYPE_PASSWORD;
|
||||
storedCredentialType = CredentialHash.TYPE_PASSWORD;
|
||||
} else {
|
||||
mStoredCredentialType = CredentialHash.TYPE_NONE;
|
||||
storedCredentialType = CredentialHash.TYPE_NONE;
|
||||
}
|
||||
} else {
|
||||
CredentialHash password = readPasswordHash(userId);
|
||||
if (password != null) {
|
||||
// Both will never be GateKeeper
|
||||
if (password.version == CredentialHash.VERSION_GATEKEEPER) {
|
||||
mStoredCredentialType = CredentialHash.TYPE_PASSWORD;
|
||||
storedCredentialType = CredentialHash.TYPE_PASSWORD;
|
||||
} else {
|
||||
mStoredCredentialType = CredentialHash.TYPE_PATTERN;
|
||||
storedCredentialType = CredentialHash.TYPE_PATTERN;
|
||||
}
|
||||
} else {
|
||||
mStoredCredentialType = CredentialHash.TYPE_PATTERN;
|
||||
storedCredentialType = CredentialHash.TYPE_PATTERN;
|
||||
}
|
||||
}
|
||||
|
||||
return mStoredCredentialType;
|
||||
mStoredCredentialType.put(userId, storedCredentialType);
|
||||
return storedCredentialType;
|
||||
}
|
||||
|
||||
|
||||
@@ -244,6 +247,27 @@ class LockSettingsStorage {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void removeChildProfileLock(int userId) {
|
||||
if (DEBUG)
|
||||
Slog.e(TAG, "Remove child profile lock for user: " + userId);
|
||||
try {
|
||||
deleteFile(getChildProfileLockFile(userId));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void writeChildProfileLock(int userId, byte[] lock) {
|
||||
writeFile(getChildProfileLockFile(userId), lock);
|
||||
}
|
||||
|
||||
public byte[] readChildProfileLock(int userId) {
|
||||
return readFile(getChildProfileLockFile(userId));
|
||||
}
|
||||
|
||||
public boolean hasChildProfileLock(int userId) {
|
||||
return hasFile(getChildProfileLockFile(userId));
|
||||
}
|
||||
|
||||
public boolean hasPassword(int userId) {
|
||||
return hasFile(getLockPasswordFilename(userId)) ||
|
||||
@@ -321,16 +345,19 @@ class LockSettingsStorage {
|
||||
}
|
||||
|
||||
private void deleteFile(String name) {
|
||||
File f = new File(name);
|
||||
if (f != null) {
|
||||
f.delete();
|
||||
if (DEBUG) Slog.e(TAG, "Delete file " + name);
|
||||
synchronized (mFileWriteLock) {
|
||||
File file = new File(name);
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
mCache.putFile(name, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void writePatternHash(byte[] hash, int userId) {
|
||||
mStoredCredentialType = hash == null
|
||||
? CredentialHash.TYPE_NONE
|
||||
: CredentialHash.TYPE_PATTERN;
|
||||
mStoredCredentialType.put(userId, hash == null ? CredentialHash.TYPE_NONE
|
||||
: CredentialHash.TYPE_PATTERN);
|
||||
writeFile(getLockPatternFilename(userId), hash);
|
||||
clearPasswordHash(userId);
|
||||
}
|
||||
@@ -340,9 +367,8 @@ class LockSettingsStorage {
|
||||
}
|
||||
|
||||
public void writePasswordHash(byte[] hash, int userId) {
|
||||
mStoredCredentialType = hash == null
|
||||
? CredentialHash.TYPE_NONE
|
||||
: CredentialHash.TYPE_PASSWORD;
|
||||
mStoredCredentialType.put(userId, hash == null ? CredentialHash.TYPE_NONE
|
||||
: CredentialHash.TYPE_PASSWORD);
|
||||
writeFile(getLockPasswordFilename(userId), hash);
|
||||
clearPatternHash(userId);
|
||||
}
|
||||
@@ -375,8 +401,11 @@ class LockSettingsStorage {
|
||||
return getLockCredentialFilePathForUser(userId, BASE_ZERO_LOCK_PATTERN_FILE);
|
||||
}
|
||||
|
||||
private String getChildProfileLockFile(int userId) {
|
||||
return getLockCredentialFilePathForUser(userId, CHILD_PROFILE_LOCK_FILE);
|
||||
}
|
||||
|
||||
private String getLockCredentialFilePathForUser(int userId, String basename) {
|
||||
userId = getUserParentOrSelfId(userId);
|
||||
String dataSystemDirectory =
|
||||
android.os.Environment.getDataDirectory().getAbsolutePath() +
|
||||
SYSTEM_DIRECTORY;
|
||||
@@ -388,23 +417,6 @@ class LockSettingsStorage {
|
||||
}
|
||||
}
|
||||
|
||||
private int getUserParentOrSelfId(int userId) {
|
||||
// Device supports per user encryption, so lock is applied to the given user.
|
||||
if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
|
||||
return userId;
|
||||
}
|
||||
// Device uses Block Based Encryption, and the parent user's lock is used for the whole
|
||||
// device.
|
||||
if (userId != 0) {
|
||||
final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
|
||||
final UserInfo pi = um.getProfileParent(userId);
|
||||
if (pi != null) {
|
||||
return pi.id;
|
||||
}
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void removeUser(int userId) {
|
||||
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
|
||||
|
||||
@@ -427,6 +439,9 @@ class LockSettingsStorage {
|
||||
mCache.putFile(name, null);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Manged profile
|
||||
removeChildProfileLock(userId);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -3890,9 +3890,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
|
||||
// back in to the service.
|
||||
final long ident = mInjector.binderClearCallingIdentity();
|
||||
try {
|
||||
if (isManagedProfile(userHandle)) {
|
||||
mLockPatternUtils.setSeparateProfileChallengeEnabled(userHandle, true);
|
||||
}
|
||||
if (!TextUtils.isEmpty(password)) {
|
||||
mLockPatternUtils.saveLockPassword(password, null, quality, userHandle);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user