Roll forward SSAID Migration to be Per App/User Unique Values.

SSAID is currently shared across all applications for each
user on the device, giving developers the ability to track
users across multiple applications. Using SSAID for tracking
is an abuse of the original intention of the SSAID and has
inherent privacy concerns.

This change will make the SSAID unique per application, per
user on a device. To not affect applications installed prior
to this change they will retain the legacy SSAID value until
uninstalled and reinstalled again.

Across subsequent installations the application will receive
the same SSAID as long as the package name and signature remain
consistent.

Tested manually the following cases:
  - App retains the legacy sssaid after OTA.
  - App gets a new ssaid upon post-OTA installation.
  - App retrieves same ssaid across post-OTA unistall/reinstalls.
  - Different Apps receive different ssaids.
  - Factory reset removes ssaid data and generates a different
    ssaid after App install.
  - System retains legacy ssaid.

Bug: 34395671
Test: CTS tests passed, Manual testing passed

This reverts commit be43257005d086ff7d93c15dae22ac40bc0d545e.

Change-Id: Ibf20e7949304c30d65bb8aa24cdbbe6e104b1002
diff --git a/core/java/android/util/ByteStringUtils.java b/core/java/android/util/ByteStringUtils.java
new file mode 100644
index 0000000..7103e6d
--- /dev/null
+++ b/core/java/android/util/ByteStringUtils.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+/**
+ * A utility class for common byte array to hex string operations and vise versa.
+ *
+ * @hide
+ */
+public final class ByteStringUtils {
+  private final static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
+
+  private ByteStringUtils() {
+    /* hide constructor */
+  }
+
+  /**
+   * Returns the hex encoded string representation of bytes.
+   * @param bytes Byte array to encode.
+   * @return Hex encoded string representation of bytes.
+   */
+  public static String toString(byte[] bytes) {
+    if (bytes == null || bytes.length == 0 || bytes.length % 2 != 0) {
+      return null;
+    }
+
+    final int byteLength = bytes.length;
+    final int charCount = 2 * byteLength;
+    final char[] chars = new char[charCount];
+
+    for (int i = 0; i < byteLength; i++) {
+      final int byteHex = bytes[i] & 0xFF;
+      chars[i * 2] = HEX_ARRAY[byteHex >>> 4];
+      chars[i * 2 + 1] = HEX_ARRAY[byteHex & 0x0F];
+    }
+    return new String(chars);
+  }
+
+  /**
+   * Returns the decoded byte array representation of str.
+   * @param str Hex encoded string to decode.
+   * @return Decoded byte array representation of str.
+   */
+  public static byte[] toByteArray(String str) {
+    if (str == null || str.length() == 0 || str.length() % 2 != 0) {
+      return null;
+    }
+
+    final char[] chars = str.toCharArray();
+    final int charLength = chars.length;
+    final byte[] bytes = new byte[charLength / 2];
+
+    for (int i = 0; i < bytes.length; i++) {
+      bytes[i] =
+          (byte)(((getIndex(chars[i * 2]) << 4) & 0xF0) | (getIndex(chars[i * 2 + 1]) & 0x0F));
+    }
+    return bytes;
+  }
+
+  private static int getIndex(char c) {
+    for (int i = 0; i < HEX_ARRAY.length; i++) {
+      if (HEX_ARRAY[i] == c) {
+        return i;
+      }
+    }
+    return -1;
+  }
+}
diff --git a/core/java/android/util/PackageUtils.java b/core/java/android/util/PackageUtils.java
index 6531aef..3181979 100644
--- a/core/java/android/util/PackageUtils.java
+++ b/core/java/android/util/PackageUtils.java
@@ -30,7 +30,6 @@
  * @hide
  */
 public final class PackageUtils {
-    private final static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
 
     private PackageUtils() {
         /* hide constructor */
@@ -81,16 +80,6 @@
 
         messageDigest.update(data);
 
-        final byte[] digest = messageDigest.digest();
-        final int digestLength = digest.length;
-        final int charCount = 2 * digestLength;
-
-        final char[] chars = new char[charCount];
-        for (int i = 0; i < digestLength; i++) {
-            final int byteHex = digest[i] & 0xFF;
-            chars[i * 2] = HEX_ARRAY[byteHex >>> 4];
-            chars[i * 2 + 1] = HEX_ARRAY[byteHex & 0x0F];
-        }
-        return new String(chars);
+        return ByteStringUtils.toString(messageDigest.digest());
     }
 }
diff --git a/core/tests/coretests/src/android/provider/SettingsProviderTest.java b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
index b0ce2c8..3fbf169 100644
--- a/core/tests/coretests/src/android/provider/SettingsProviderTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
@@ -364,4 +364,20 @@
         // one or more activity can handle this intent.
         assertTrue(resolveInfoList.size() > 0);
     }
+
+    @SmallTest
+    public void testValidSsaid() {
+        ContentResolver r = getContext().getContentResolver();
+
+        // Verify ssaid
+        String ssaid = Settings.Secure.getString(r, Settings.Secure.ANDROID_ID);
+        assertTrue(ssaid != null);
+        assertTrue(ssaid.length() == 16);
+
+        String ssaid2 = Settings.Secure.getString(r, Settings.Secure.ANDROID_ID);
+        assertTrue(ssaid2 != null);
+        assertTrue(ssaid2.length() == 16);
+
+        assertTrue(ssaid.equals(ssaid2));
+    }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 3e62158..058e38a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -61,6 +61,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.ByteStringUtils;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -76,9 +77,13 @@
 import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
 import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -162,6 +167,7 @@
     public static final int SETTINGS_TYPE_GLOBAL = 0;
     public static final int SETTINGS_TYPE_SYSTEM = 1;
     public static final int SETTINGS_TYPE_SECURE = 2;
+    public static final int SETTINGS_TYPE_SSAID = 3;
 
     public static final int SETTINGS_TYPE_MASK = 0xF0000000;
     public static final int SETTINGS_TYPE_SHIFT = 28;
@@ -249,6 +255,9 @@
             case SETTINGS_TYPE_SYSTEM: {
                 return "SETTINGS_SYSTEM";
             }
+            case SETTINGS_TYPE_SSAID: {
+                return "SETTINGS_SSAID";
+            }
             default: {
                 return "UNKNOWN";
             }
@@ -704,6 +713,13 @@
                             UserHandle.getUserId(uid));
                 }
             }
+
+            @Override
+            public void onUidRemoved(int uid) {
+                synchronized (mLock) {
+                    mSettingsRegistry.onUidRemovedLocked(uid);
+                }
+            }
         };
 
         // package changes
@@ -957,8 +973,15 @@
                     continue;
                 }
 
-                Setting setting = mSettingsRegistry.getSettingLocked(
-                        SETTINGS_TYPE_SECURE, owningUserId, name);
+                // As of Android O (API 24), the SSAID is read from an app-specific entry in table
+                // SETTINGS_FILE_SSAID, unless accessed by a system process.
+                final Setting setting;
+                if (isNewSsaidSetting(name)) {
+                    setting = getSsaidSettingLocked(owningUserId);
+                } else {
+                    setting = mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_SECURE, owningUserId,
+                            name);
+                }
                 appendSettingToCursor(result, setting);
             }
 
@@ -986,11 +1009,43 @@
 
         // Get the value.
         synchronized (mLock) {
+            // As of Android O (API 24), the SSAID is read from an app-specific entry in table
+            // SETTINGS_FILE_SSAID, unless accessed by a system process.
+            if (isNewSsaidSetting(name)) {
+                return getSsaidSettingLocked(owningUserId);
+            }
+
             return mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_SECURE,
                     owningUserId, name);
         }
     }
 
+    private boolean isNewSsaidSetting(String name) {
+        return Settings.Secure.ANDROID_ID.equals(name)
+                && UserHandle.getAppId(Binder.getCallingUid()) >= Process.FIRST_APPLICATION_UID;
+    }
+
+    private Setting getSsaidSettingLocked(int owningUserId) {
+        // Get uid of caller (key) used to store ssaid value
+        String name = Integer.toString(
+                UserHandle.getUid(owningUserId, UserHandle.getAppId(Binder.getCallingUid())));
+
+        if (DEBUG) {
+            Slog.v(LOG_TAG, "getSsaidSettingLocked(" + name + "," + owningUserId + ")");
+        }
+
+        // Retrieve the ssaid from the table if present.
+        final Setting ssaid = mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_SSAID, owningUserId,
+                name);
+
+        // Lazy initialize ssaid if not yet present in ssaid table.
+        if (ssaid.isNull() || ssaid.getValue() == null) {
+            return mSettingsRegistry.generateSsaidLocked(getCallingPackage(), owningUserId);
+        }
+
+        return ssaid;
+    }
+
     private boolean insertSecureSetting(String name, String value, String tag,
             boolean makeDefault, int requestingUserId, boolean forceNotify) {
         if (DEBUG) {
@@ -1818,6 +1873,9 @@
         private static final String SETTINGS_FILE_GLOBAL = "settings_global.xml";
         private static final String SETTINGS_FILE_SYSTEM = "settings_system.xml";
         private static final String SETTINGS_FILE_SECURE = "settings_secure.xml";
+        private static final String SETTINGS_FILE_SSAID = "settings_ssaid.xml";
+
+        private static final String SSAID_USER_KEY = "userkey";
 
         private final SparseArray<SettingsState> mSettingsStates = new SparseArray<>();
 
@@ -1832,6 +1890,115 @@
             mGenerationRegistry = new GenerationRegistry(mLock);
             mBackupManager = new BackupManager(getContext());
             migrateAllLegacySettingsIfNeeded();
+            syncSsaidTableOnStart();
+        }
+
+        private void generateUserKeyLocked(int userId) {
+            // Generate a random key for each user used for creating a new ssaid.
+            final byte[] keyBytes = new byte[16];
+            final SecureRandom rand = new SecureRandom();
+            rand.nextBytes(keyBytes);
+
+            // Convert to string for storage in settings table.
+            final String userKey = ByteStringUtils.toString(keyBytes);
+
+            // Store the key in the ssaid table.
+            final SettingsState ssaidSettings = getSettingsLocked(SETTINGS_TYPE_SSAID, userId);
+            final boolean success = ssaidSettings.insertSettingLocked(SSAID_USER_KEY, userKey, null,
+                    true, SettingsState.SYSTEM_PACKAGE_NAME);
+
+            if (!success) {
+                throw new IllegalStateException("Ssaid settings not accessible");
+            }
+        }
+
+        public Setting generateSsaidLocked(String packageName, int userId) {
+            final PackageInfo packageInfo;
+            try {
+                packageInfo = mPackageManager.getPackageInfo(packageName,
+                        PackageManager.GET_SIGNATURES, userId);
+            } catch (RemoteException e) {
+                throw new IllegalStateException("Package info doesn't exist");
+            }
+
+            // Read the user's key from the ssaid table.
+            Setting userKeySetting = getSettingLocked(SETTINGS_TYPE_SSAID, userId, SSAID_USER_KEY);
+            if (userKeySetting.isNull() || userKeySetting.getValue() == null) {
+                // Lazy initialize and store the user key.
+                generateUserKeyLocked(userId);
+                userKeySetting = getSettingLocked(SETTINGS_TYPE_SSAID, userId, SSAID_USER_KEY);
+                if (userKeySetting.isNull() || userKeySetting.getValue() == null) {
+                    throw new IllegalStateException("User key not accessible");
+                }
+            }
+            final String userKey = userKeySetting.getValue();
+
+            // Convert the user's key back to a byte array.
+            final byte[] keyBytes = ByteStringUtils.toByteArray(userKey);
+            if (keyBytes == null || keyBytes.length != 16) {
+                throw new IllegalStateException("User key invalid");
+            }
+
+            final MessageDigest md;
+            try {
+                // Hash package name and signature.
+                md = MessageDigest.getInstance("SHA-256");
+            } catch (NoSuchAlgorithmException e) {
+                throw new IllegalStateException("HmacSHA256 is not available");
+            }
+            md.update(keyBytes);
+            md.update(packageInfo.packageName.getBytes(StandardCharsets.UTF_8));
+            md.update(packageInfo.signatures[0].toByteArray());
+
+            // Convert result to a string for storage in settings table. Only want first 64 bits.
+            final String ssaid = ByteStringUtils.toString(md.digest()).substring(0, 16)
+                    .toLowerCase();
+
+            // Save the ssaid in the ssaid table.
+            final String uid = Integer.toString(packageInfo.applicationInfo.uid);
+            final SettingsState ssaidSettings = getSettingsLocked(SETTINGS_TYPE_SSAID, userId);
+            final boolean success = ssaidSettings.insertSettingLocked(uid, ssaid, null, true,
+                    packageName);
+
+            if (!success) {
+                throw new IllegalStateException("Ssaid settings not accessible");
+            }
+
+            return getSettingLocked(SETTINGS_TYPE_SSAID, userId, uid);
+        }
+
+        public void syncSsaidTableOnStart() {
+            synchronized (mLock) {
+                // Verify that each user's packages and ssaid's are in sync.
+                for (UserInfo user : mUserManager.getUsers(true)) {
+                    // Get all uids for the user's packages.
+                    final List<PackageInfo> packages;
+                    try {
+                        packages = mPackageManager.getInstalledPackages(0, user.id).getList();
+                    } catch (RemoteException e) {
+                        throw new IllegalStateException("Package manager not available");
+                    }
+                    final Set<String> appUids = new HashSet<>();
+                    for (PackageInfo info : packages) {
+                        appUids.add(Integer.toString(info.applicationInfo.uid));
+                    }
+
+                    // Get all uids currently stored in the user's ssaid table.
+                    final Set<String> ssaidUids = new HashSet<>(
+                            getSettingsNamesLocked(SETTINGS_TYPE_SSAID, user.id));
+                    ssaidUids.remove(SSAID_USER_KEY);
+
+                    // Perform a set difference for the appUids and ssaidUids.
+                    ssaidUids.removeAll(appUids);
+
+                    // If there are ssaidUids left over they need to be removed from the table.
+                    final SettingsState ssaidSettings = getSettingsLocked(SETTINGS_TYPE_SSAID,
+                            user.id);
+                    for (String uid : ssaidUids) {
+                        ssaidSettings.deleteSettingLocked(uid);
+                    }
+                }
+            }
         }
 
         public List<String> getSettingsNamesLocked(int type, int userId) {
@@ -1884,6 +2051,10 @@
             final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId);
             ensureSettingsStateLocked(systemKey);
 
+            // Ensure secure settings loaded.
+            final int ssaidKey = makeKey(SETTINGS_TYPE_SSAID, userId);
+            ensureSettingsStateLocked(ssaidKey);
+
             // Upgrade the settings to the latest version.
             UpgradeController upgrader = new UpgradeController(userId);
             upgrader.upgradeIfNeededLocked();
@@ -1936,6 +2107,23 @@
                 }
             }
 
+            // Nuke ssaid settings.
+            final int ssaidKey = makeKey(SETTINGS_TYPE_SSAID, userId);
+            final SettingsState ssaidSettingsState = mSettingsStates.get(ssaidKey);
+            if (ssaidSettingsState != null) {
+                if (permanently) {
+                    mSettingsStates.remove(ssaidKey);
+                    ssaidSettingsState.destroyLocked(null);
+                } else {
+                    ssaidSettingsState.destroyLocked(new Runnable() {
+                        @Override
+                        public void run() {
+                            mSettingsStates.remove(ssaidKey);
+                        }
+                    });
+                }
+            }
+
             // Nuke generation tracking data
             mGenerationRegistry.onUserRemoved(userId);
         }
@@ -1977,8 +2165,10 @@
 
             SettingsState settingsState = peekSettingsStateLocked(key);
             if (settingsState == null) {
-                return null;
+                return settingsState.getNullSetting();
             }
+
+            // getSettingLocked will return non-null result
             return settingsState.getSettingLocked(name);
         }
 
@@ -2079,6 +2269,12 @@
             }
         }
 
+        public void onUidRemovedLocked(int uid) {
+            final SettingsState ssaidSettings = getSettingsLocked(SETTINGS_TYPE_SSAID,
+                    UserHandle.getUserId(uid));
+            ssaidSettings.deleteSettingLocked(Integer.toString(uid));
+        }
+
         private SettingsState peekSettingsStateLocked(int key) {
             SettingsState settingsState = mSettingsStates.get(key);
             if (settingsState != null) {
@@ -2300,6 +2496,10 @@
             return getTypeFromKey(key) == SETTINGS_TYPE_SECURE;
         }
 
+        private boolean isSsaidSettingsKey(int key) {
+            return getTypeFromKey(key) == SETTINGS_TYPE_SSAID;
+        }
+
         private File getSettingsFile(int key) {
             if (isGlobalSettingsKey(key)) {
                 final int userId = getUserIdFromKey(key);
@@ -2313,6 +2513,10 @@
                 final int userId = getUserIdFromKey(key);
                 return new File(Environment.getUserSystemDirectory(userId),
                         SETTINGS_FILE_SECURE);
+            } else if (isSsaidSettingsKey(key)) {
+                final int userId = getUserIdFromKey(key);
+                return new File(Environment.getUserSystemDirectory(userId),
+                        SETTINGS_FILE_SSAID);
             } else {
                 throw new IllegalArgumentException("Invalid settings key:" + key);
             }
@@ -2336,7 +2540,8 @@
         private int getMaxBytesPerPackageForType(int type) {
             switch (type) {
                 case SETTINGS_TYPE_GLOBAL:
-                case SETTINGS_TYPE_SECURE: {
+                case SETTINGS_TYPE_SECURE:
+                case SETTINGS_TYPE_SSAID: {
                     return SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED;
                 }
 
@@ -2374,7 +2579,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 136;
+            private static final int SETTINGS_VERSION = 137;
 
             private final int mUserId;
 
@@ -2447,6 +2652,10 @@
                 return getSettingsLocked(SETTINGS_TYPE_SECURE, userId);
             }
 
+            private SettingsState getSsaidSettingsLocked(int userId) {
+                return getSettingsLocked(SETTINGS_TYPE_SSAID, userId);
+            }
+
             private SettingsState getSystemSettingsLocked(int userId) {
                 return getSettingsLocked(SETTINGS_TYPE_SYSTEM, userId);
             }
@@ -2776,6 +2985,48 @@
                     currentVersion = 136;
                 }
 
+                if (currentVersion == 136) {
+                    // Version 136: Store legacy SSAID for all apps currently installed on the
+                    // device as first step in migrating SSAID to be unique per application.
+
+                    final boolean isUpgrade;
+                    try {
+                        isUpgrade = mPackageManager.isUpgrade();
+                    } catch (RemoteException e) {
+                        throw new IllegalStateException("Package manager not available");
+                    }
+                    // Only retain legacy ssaid if the device is performing an OTA. After wiping
+                    // user data or first boot on a new device should use new ssaid generation.
+                    if (isUpgrade) {
+                        // Retrieve the legacy ssaid from the secure settings table.
+                        final String legacySsaid = getSettingLocked(SETTINGS_TYPE_SECURE, userId,
+                                Settings.Secure.ANDROID_ID).getValue();
+
+                        // Fill each uid with the legacy ssaid to be backwards compatible.
+                        final List<PackageInfo> packages;
+                        try {
+                            packages = mPackageManager.getInstalledPackages(0, userId).getList();
+                        } catch (RemoteException e) {
+                            throw new IllegalStateException("Package manager not available");
+                        }
+
+                        final SettingsState ssaidSettings = getSsaidSettingsLocked(userId);
+                        for (PackageInfo info : packages) {
+                            // Check if the UID already has an entry in the table.
+                            final String uid = Integer.toString(info.applicationInfo.uid);
+                            final Setting ssaid = ssaidSettings.getSettingLocked(uid);
+
+                            if (ssaid.isNull() || ssaid.getValue() == null) {
+                                // Android Id doesn't exist for this package so create it.
+                                ssaidSettings.insertSettingLocked(uid, legacySsaid, null, true,
+                                        info.packageName);
+                            }
+                        }
+                    }
+
+                    currentVersion = 137;
+                }
+
                 if (currentVersion != newVersion) {
                     Slog.wtf("SettingsProvider", "warning: upgrading settings database to version "
                             + newVersion + " left it at "