From 4db6bc16d0cc3c3b60a6b5305118f266ab2028a8 Mon Sep 17 00:00:00 2001 From: Patrick Baumann Date: Tue, 6 Feb 2018 09:55:36 -0800 Subject: [PATCH] Increase instant app privacy with dummy data This change adds some additional digest prefixes to instant app resolution to make inverting the hash more difficult. Change-Id: Ibf907495019338b5cac5dd22da275799e92f9b60 Fixes: 63445230 Test: manual - resolution still works with current production resolver --- .../android/content/pm/InstantAppRequest.java | 10 ++++ .../content/pm/InstantAppResolveInfo.java | 50 +++++++++++++++++-- .../android/server/pm/InstantAppResolver.java | 23 +++------ 3 files changed, 65 insertions(+), 18 deletions(-) diff --git a/core/java/android/content/pm/InstantAppRequest.java b/core/java/android/content/pm/InstantAppRequest.java index 38f02256ee6e0..361d4e4acb3bc 100644 --- a/core/java/android/content/pm/InstantAppRequest.java +++ b/core/java/android/content/pm/InstantAppRequest.java @@ -18,12 +18,14 @@ package android.content.pm; import android.content.Intent; import android.os.Bundle; +import android.text.TextUtils; /** * Information needed to make an instant application resolution request. * @hide */ public final class InstantAppRequest { + /** Response from the first phase of instant application resolution */ public final AuxiliaryResolveInfo responseObj; /** The original intent that triggered instant application resolution */ @@ -40,6 +42,8 @@ public final class InstantAppRequest { public final Bundle verificationBundle; /** Whether resolution occurs because an application is starting */ public final boolean resolveForStart; + /** The instant app digest for this request */ + public final InstantAppResolveInfo.InstantAppDigest digest; public InstantAppRequest(AuxiliaryResolveInfo responseObj, Intent origIntent, String resolvedType, String callingPackage, int userId, Bundle verificationBundle, @@ -51,5 +55,11 @@ public final class InstantAppRequest { this.userId = userId; this.verificationBundle = verificationBundle; this.resolveForStart = resolveForStart; + if (origIntent.getData() != null && !TextUtils.isEmpty(origIntent.getData().getHost())) { + digest = new InstantAppResolveInfo.InstantAppDigest( + origIntent.getData().getHost(), 5 /*maxDigests*/); + } else { + digest = InstantAppResolveInfo.InstantAppDigest.UNDEFINED; + } } } diff --git a/core/java/android/content/pm/InstantAppResolveInfo.java b/core/java/android/content/pm/InstantAppResolveInfo.java index 112c5dae6731f..3a95a5f87d97c 100644 --- a/core/java/android/content/pm/InstantAppResolveInfo.java +++ b/core/java/android/content/pm/InstantAppResolveInfo.java @@ -26,10 +26,13 @@ import android.os.Parcelable; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Random; /** * Describes an externally resolvable instant application. There are three states that this class @@ -227,14 +230,25 @@ public final class InstantAppResolveInfo implements Parcelable { */ @SystemApi public static final class InstantAppDigest implements Parcelable { - private static final int DIGEST_MASK = 0xfffff000; - + static final int DIGEST_MASK = 0xfffff000; public static final InstantAppDigest UNDEFINED = new InstantAppDigest(new byte[][]{}, new int[]{}); + + private static Random sRandom = null; + static { + try { + sRandom = SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + // oh well + sRandom = new Random(); + } + } /** Full digest of the domain hashes */ private final byte[][] mDigestBytes; - /** The first 4 bytes of the domain hashes */ + /** The first 5 bytes of the domain hashes */ private final int[] mDigestPrefix; + /** The first 5 bytes of the domain hashes interspersed with random data */ + private int[] mDigestPrefixSecure; public InstantAppDigest(@NonNull String hostName) { this(hostName, -1 /*maxDigests*/); @@ -306,6 +320,7 @@ public final class InstantAppResolveInfo implements Parcelable { } } mDigestPrefix = in.createIntArray(); + mDigestPrefixSecure = in.createIntArray(); } public byte[][] getDigestBytes() { @@ -316,6 +331,26 @@ public final class InstantAppResolveInfo implements Parcelable { return mDigestPrefix; } + /** + * Returns a digest prefix with additional random prefixes interspersed. + * @hide + */ + public int[] getDigestPrefixSecure() { + if (this == InstantAppResolveInfo.InstantAppDigest.UNDEFINED) { + return getDigestPrefix(); + } else if (mDigestPrefixSecure == null) { + // let's generate some random data to intersperse throughout the set of prefixes + final int realSize = getDigestPrefix().length; + final int manufacturedSize = realSize + 10 + sRandom.nextInt(10); + mDigestPrefixSecure = Arrays.copyOf(getDigestPrefix(), manufacturedSize); + for (int i = realSize; i < manufacturedSize; i++) { + mDigestPrefixSecure[i] = sRandom.nextInt() & DIGEST_MASK; + } + Arrays.sort(mDigestPrefixSecure); + } + return mDigestPrefixSecure; + } + @Override public int describeContents() { return 0; @@ -323,6 +358,11 @@ public final class InstantAppResolveInfo implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { + final boolean isUndefined = this == UNDEFINED; + out.writeBoolean(isUndefined); + if (isUndefined) { + return; + } if (mDigestBytes == null) { out.writeInt(-1); } else { @@ -332,6 +372,7 @@ public final class InstantAppResolveInfo implements Parcelable { } } out.writeIntArray(mDigestPrefix); + out.writeIntArray(mDigestPrefixSecure); } @SuppressWarnings("hiding") @@ -339,6 +380,9 @@ public final class InstantAppResolveInfo implements Parcelable { new Parcelable.Creator() { @Override public InstantAppDigest createFromParcel(Parcel in) { + if (in.readBoolean() /* is undefined */) { + return UNDEFINED; + } return new InstantAppDigest(in); } @Override diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java index af446ba029140..2de7b4e017c1d 100644 --- a/services/core/java/com/android/server/pm/InstantAppResolver.java +++ b/services/core/java/com/android/server/pm/InstantAppResolver.java @@ -126,17 +126,16 @@ public abstract class InstantAppResolver { final Intent origIntent = requestObj.origIntent; final Intent sanitizedIntent = sanitizeIntent(origIntent); - final InstantAppDigest digest = getInstantAppDigest(origIntent); - final int[] shaPrefix = digest.getDigestPrefix(); AuxiliaryResolveInfo resolveInfo = null; @ResolutionStatus int resolutionStatus = RESOLUTION_SUCCESS; try { final List instantAppResolveInfoList = - connection.getInstantAppResolveInfoList(sanitizedIntent, shaPrefix, token); + connection.getInstantAppResolveInfoList(sanitizedIntent, + requestObj.digest.getDigestPrefixSecure(), token); if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) { resolveInfo = InstantAppResolver.filterInstantAppIntent( instantAppResolveInfoList, origIntent, requestObj.resolvedType, - requestObj.userId, origIntent.getPackage(), digest, token); + requestObj.userId, origIntent.getPackage(), requestObj.digest, token); } } catch (ConnectionException e) { if (e.failure == ConnectionException.FAILURE_BIND) { @@ -166,12 +165,6 @@ public abstract class InstantAppResolver { return resolveInfo; } - private static InstantAppDigest getInstantAppDigest(Intent origIntent) { - return origIntent.getData() != null && !TextUtils.isEmpty(origIntent.getData().getHost()) - ? new InstantAppDigest(origIntent.getData().getHost(), 5 /*maxDigests*/) - : InstantAppDigest.UNDEFINED; - } - public static void doInstantAppResolutionPhaseTwo(Context context, InstantAppResolverConnection connection, InstantAppRequest requestObj, ActivityInfo instantAppInstaller, Handler callbackHandler) { @@ -182,8 +175,6 @@ public abstract class InstantAppResolver { } final Intent origIntent = requestObj.origIntent; final Intent sanitizedIntent = sanitizeIntent(origIntent); - final InstantAppDigest digest = getInstantAppDigest(origIntent); - final int[] shaPrefix = digest.getDigestPrefix(); final PhaseTwoCallback callback = new PhaseTwoCallback() { @Override @@ -194,7 +185,8 @@ public abstract class InstantAppResolver { final AuxiliaryResolveInfo instantAppIntentInfo = InstantAppResolver.filterInstantAppIntent( instantAppResolveInfoList, origIntent, null /*resolvedType*/, - 0 /*userId*/, origIntent.getPackage(), digest, token); + 0 /*userId*/, origIntent.getPackage(), requestObj.digest, + token); if (instantAppIntentInfo != null) { failureIntent = instantAppIntentInfo.failureIntent; } else { @@ -225,8 +217,9 @@ public abstract class InstantAppResolver { } }; try { - connection.getInstantAppIntentFilterList(sanitizedIntent, shaPrefix, token, callback, - callbackHandler, startTime); + connection.getInstantAppIntentFilterList(sanitizedIntent, + requestObj.digest.getDigestPrefixSecure(), token, callback, callbackHandler, + startTime); } catch (ConnectionException e) { @ResolutionStatus int resolutionStatus = RESOLUTION_FAILURE; if (e.failure == ConnectionException.FAILURE_BIND) {