Keep managed profile keystores in sync with owner

Fixes setting a keyguard password for keystore in a multi-user setup
while we're at it.

Bug: 16233206.
Change-Id: I7941707ca66ac25bd122fd22e5e0f639e7af697e
diff --git a/core/java/android/security/IKeystoreService.java b/core/java/android/security/IKeystoreService.java
index f8bf45b..7e9aba0 100644
--- a/core/java/android/security/IKeystoreService.java
+++ b/core/java/android/security/IKeystoreService.java
@@ -478,6 +478,59 @@
                 }
                 return _result;
             }
+
+            public int reset_uid(int uid) throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                int _result;
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeInt(uid);
+                    mRemote.transact(Stub.TRANSACTION_reset_uid, _data, _reply, 0);
+                    _reply.readException();
+                    _result = _reply.readInt();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+                return _result;
+            }
+
+            public int sync_uid(int srcUid, int dstUid) throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                int _result;
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeInt(srcUid);
+                    _data.writeInt(dstUid);
+                    mRemote.transact(Stub.TRANSACTION_sync_uid, _data, _reply, 0);
+                    _reply.readException();
+                    _result = _reply.readInt();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+                return _result;
+            }
+
+            public int password_uid(String password, int uid) throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                int _result;
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeString(password);
+                    _data.writeInt(uid);
+                    mRemote.transact(Stub.TRANSACTION_password_uid, _data, _reply, 0);
+                    _reply.readException();
+                    _result = _reply.readInt();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+                return _result;
+            }
         }
 
         private static final String DESCRIPTOR = "android.security.keystore";
@@ -505,6 +558,9 @@
         static final int TRANSACTION_duplicate = IBinder.FIRST_CALL_TRANSACTION + 20;
         static final int TRANSACTION_is_hardware_backed = IBinder.FIRST_CALL_TRANSACTION + 21;
         static final int TRANSACTION_clear_uid = IBinder.FIRST_CALL_TRANSACTION + 22;
+        static final int TRANSACTION_reset_uid = IBinder.FIRST_CALL_TRANSACTION + 23;
+        static final int TRANSACTION_sync_uid = IBinder.FIRST_CALL_TRANSACTION + 24;
+        static final int TRANSACTION_password_uid = IBinder.FIRST_CALL_TRANSACTION + 25;
 
         /**
          * Cast an IBinder object into an IKeystoreService interface, generating
@@ -597,4 +653,10 @@
     public int is_hardware_backed(String string) throws RemoteException;
 
     public int clear_uid(long uid) throws RemoteException;
+
+    public int reset_uid(int uid) throws RemoteException;
+
+    public int sync_uid(int sourceUid, int targetUid) throws RemoteException;
+
+    public int password_uid(String password, int uid) throws RemoteException;
 }
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 6ac49ee..0db8c77 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -331,6 +331,36 @@
         }
     }
 
+    public boolean resetUid(int uid) {
+        try {
+            mError = mBinder.reset_uid(uid);
+            return mError == NO_ERROR;
+        } catch (RemoteException e) {
+            Log.w(TAG, "Cannot connect to keystore", e);
+            return false;
+        }
+    }
+
+    public boolean syncUid(int sourceUid, int targetUid) {
+        try {
+            mError = mBinder.sync_uid(sourceUid, targetUid);
+            return mError == NO_ERROR;
+        } catch (RemoteException e) {
+            Log.w(TAG, "Cannot connect to keystore", e);
+            return false;
+        }
+    }
+
+    public boolean passwordUid(String password, int uid) {
+        try {
+            mError = mBinder.password_uid(password, uid);
+            return mError == NO_ERROR;
+        } catch (RemoteException e) {
+            Log.w(TAG, "Cannot connect to keystore", e);
+            return false;
+        }
+    }
+
     public int getLastError() {
         return mError;
     }
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 86ce961..92f5170 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -16,9 +16,12 @@
 
 package com.android.server;
 
+import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 
@@ -31,6 +34,7 @@
 import android.os.Binder;
 import android.os.Environment;
 import android.os.IBinder;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.storage.IMountService;
 import android.os.ServiceManager;
@@ -41,11 +45,14 @@
 import android.provider.Settings;
 import android.provider.Settings.Secure;
 import android.provider.Settings.SettingNotFoundException;
+import android.security.KeyChain;
+import android.security.KeyChain.KeyChainConnection;
 import android.security.KeyStore;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.widget.ILockSettings;
 import com.android.internal.widget.ILockSettingsObserver;
 import com.android.internal.widget.LockPatternUtils;
@@ -99,8 +106,30 @@
 
         mLockPatternUtils = new LockPatternUtils(context);
         mFirstCallToVold = true;
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_ADDED);
+        mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
     }
 
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // Update keystore settings for profiles which use the same password as their parent
+            if (Intent.ACTION_USER_STARTED.equals(intent.getAction())) {
+                final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+                final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
+                final UserInfo parentInfo = um.getProfileParent(userHandle);
+                if (parentInfo != null) {
+                    final KeyStore ks = KeyStore.getInstance();
+                    final int profileUid = UserHandle.getUid(userHandle, Process.SYSTEM_UID);
+                    final int parentUid = UserHandle.getUid(parentInfo.id, Process.SYSTEM_UID);
+                    ks.syncUid(parentUid, profileUid);
+                }
+            }
+        }
+    };
+
     public void systemReady() {
         migrateOldData();
     }
@@ -275,6 +304,17 @@
         }
     }
 
+    private int getUserParentOrSelfId(int userId) {
+        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;
+    }
+
     private String getLockPatternFilename(int userId) {
         String dataSystemDirectory =
                 android.os.Environment.getDataDirectory().getAbsolutePath() +
@@ -283,6 +323,7 @@
             // Leave it in the same place for user 0
             return dataSystemDirectory + LOCK_PATTERN_FILE;
         } else {
+            userId = getUserParentOrSelfId(userId);
             return  new File(Environment.getUserSystemDirectory(userId), LOCK_PATTERN_FILE)
                     .getAbsolutePath();
         }
@@ -296,7 +337,8 @@
             // Leave it in the same place for user 0
             return dataSystemDirectory + LOCK_PASSWORD_FILE;
         } else {
-            return  new File(Environment.getUserSystemDirectory(userId), LOCK_PASSWORD_FILE)
+            userId = getUserParentOrSelfId(userId);
+            return new File(Environment.getUserSystemDirectory(userId), LOCK_PASSWORD_FILE)
                     .getAbsolutePath();
         }
     }
@@ -315,16 +357,27 @@
         return new File(getLockPatternFilename(userId)).length() > 0;
     }
 
-    private void maybeUpdateKeystore(String password, int userId) {
-        if (userId == UserHandle.USER_OWNER) {
-            final KeyStore keyStore = KeyStore.getInstance();
-            // Conditionally reset the keystore if empty. If non-empty, we are just
-            // switching key guard type
-            if (TextUtils.isEmpty(password) && keyStore.isEmpty()) {
-                keyStore.reset();
+    private void maybeUpdateKeystore(String password, int userHandle) {
+        final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
+        final KeyStore ks = KeyStore.getInstance();
+
+        final List<UserInfo> profiles = um.getProfiles(userHandle);
+        boolean shouldReset = TextUtils.isEmpty(password);
+
+        // For historical reasons, don't wipe a non-empty keystore if we have a single user with a
+        // single profile.
+        if (userHandle == UserHandle.USER_OWNER && profiles.size() == 1) {
+            if (!ks.isEmpty()) {
+                shouldReset = false;
+            }
+        }
+
+        for (UserInfo pi : profiles) {
+            final int profileUid = UserHandle.getUid(pi.id, Process.SYSTEM_UID);
+            if (shouldReset) {
+                ks.resetUid(profileUid);
             } else {
-                // Update the keystore password
-                keyStore.password(password);
+                ks.passwordUid(password, profileUid);
             }
         }
     }