OSDN Git Service

Keymaster init for work profile
authorRicky Wai <rickywai@google.com>
Thu, 24 Mar 2016 19:55:08 +0000 (19:55 +0000)
committerRicky Wai <rickywai@google.com>
Thu, 7 Apr 2016 14:23:37 +0000 (14:23 +0000)
Changes:
(1) When unified work challenge is enabled and screen lock is secure
  - Store work profile secure key in primary profile
  - When primary user keystore unlocked, unlock work profile keystore
  - When primary user change lock to none, remove work secure key
(2) When unified work challenge is enabled but screen lock is not secure
  - When screen lock changes to secure, store work secure key in primary
(3) When user changes work challenge from unified to separated
  - Remove work secure key in primary
(4) When user changes work challenge from separate to unified
  - Do (1) and (2)

Bug: 27460698

Change-Id: I8f77bde5dc6b8e59c90256e75c5990100e93366b

core/java/com/android/internal/widget/ILockSettings.aidl
core/java/com/android/internal/widget/LockPatternUtils.java
services/core/java/com/android/server/LockSettingsService.java
services/core/java/com/android/server/LockSettingsStorage.java
services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java

index b07e36a..e27e735 100644 (file)
@@ -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);
index 3d892af..e71e0de 100644 (file)
@@ -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;
+        }
     }
 
     /**
index ab0f55e..c7d6321 100644 (file)
@@ -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++) {
@@ -469,6 +533,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);
         setStringUnchecked(key, userId, value ? "1" : "0");
@@ -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);
+    }
+
+    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);
+    }
 
-        if (um.getUserInfo(userHandle).isManagedProfile()) {
-            if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userHandle)) {
-                ks.unlock(userHandle, password);
+    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
index 816c791..d136f1a 100644 (file)
@@ -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 {
index 42b5705..2b37112 100644 (file)
@@ -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 {