diff options
6 files changed, 200 insertions, 74 deletions
diff --git a/core/java/android/content/pm/EphemeralResolveInfo.java b/core/java/android/content/pm/EphemeralResolveInfo.java index b87731b99be4..2b9cfa0050fa 100644 --- a/core/java/android/content/pm/EphemeralResolveInfo.java +++ b/core/java/android/content/pm/EphemeralResolveInfo.java @@ -37,10 +37,7 @@ public final class EphemeralResolveInfo implements Parcelable { /** Algorithm that will be used to generate the domain digest */ public static final String SHA_ALGORITHM = "SHA-256"; - /** Full digest of the domain hash */ - private final byte[] mDigestBytes; - /** The first 4 bytes of the domain hash */ - private final int mDigestPrefix; + private final EphemeralDigest mDigest; private final String mPackageName; /** The filters used to match domain */ private final List<IntentFilter> mFilters = new ArrayList<IntentFilter>(); @@ -55,29 +52,23 @@ public final class EphemeralResolveInfo implements Parcelable { throw new IllegalArgumentException(); } - mDigestBytes = generateDigest(uri); - mDigestPrefix = - (mDigestBytes[0] & 0xFF) << 24 - | (mDigestBytes[1] & 0xFF) << 16 - | (mDigestBytes[2] & 0xFF) << 8 - | (mDigestBytes[3] & 0xFF) << 0; + mDigest = new EphemeralDigest(uri, -1); mFilters.addAll(filters); mPackageName = packageName; } EphemeralResolveInfo(Parcel in) { - mDigestBytes = in.createByteArray(); - mDigestPrefix = in.readInt(); + mDigest = in.readParcelable(null /*loader*/); mPackageName = in.readString(); in.readList(mFilters, null /*loader*/); } public byte[] getDigestBytes() { - return mDigestBytes; + return mDigest.getDigestBytes()[0]; } public int getDigestPrefix() { - return mDigestPrefix; + return mDigest.getDigestPrefix()[0]; } public String getPackageName() { @@ -88,16 +79,6 @@ public final class EphemeralResolveInfo implements Parcelable { return mFilters; } - private static byte[] generateDigest(Uri uri) { - try { - final MessageDigest digest = MessageDigest.getInstance(SHA_ALGORITHM); - final byte[] hostBytes = uri.getHost().getBytes(); - return digest.digest(hostBytes); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("could not find digest algorithm"); - } - } - @Override public int describeContents() { return 0; @@ -105,8 +86,7 @@ public final class EphemeralResolveInfo implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { - out.writeByteArray(mDigestBytes); - out.writeInt(mDigestPrefix); + out.writeParcelable(mDigest, flags); out.writeString(mPackageName); out.writeList(mFilters); } @@ -136,4 +116,120 @@ public final class EphemeralResolveInfo implements Parcelable { return mResolveInfo; } } + + /** + * Helper class to generate and store each of the digests and prefixes + * sent to the Ephemeral Resolver. + * <p> + * Since intent filters may want to handle multiple hosts within a + * domain [eg “*.google.com”], the resolver is presented with multiple + * hash prefixes. For example, "a.b.c.d.e" generates digests for + * "d.e", "c.d.e", "b.c.d.e" and "a.b.c.d.e". + * + * @hide + */ + public static final class EphemeralDigest implements Parcelable { + /** Full digest of the domain hashes */ + private final byte[][] mDigestBytes; + /** The first 4 bytes of the domain hashes */ + private final int[] mDigestPrefix; + + public EphemeralDigest(@NonNull Uri uri, int maxDigests) { + if (uri == null) { + throw new IllegalArgumentException(); + } + mDigestBytes = generateDigest(uri, maxDigests); + mDigestPrefix = new int[mDigestBytes.length]; + for (int i = 0; i < mDigestBytes.length; i++) { + mDigestPrefix[i] = + (mDigestBytes[i][0] & 0xFF) << 24 + | (mDigestBytes[i][1] & 0xFF) << 16 + | (mDigestBytes[i][2] & 0xFF) << 8 + | (mDigestBytes[i][3] & 0xFF) << 0; + } + } + + private static byte[][] generateDigest(Uri uri, int maxDigests) { + ArrayList<byte[]> digests = new ArrayList<>(); + try { + final String host = uri.getHost(); + final MessageDigest digest = MessageDigest.getInstance(SHA_ALGORITHM); + if (maxDigests <= 0) { + final byte[] hostBytes = host.getBytes(); + digests.add(digest.digest(hostBytes)); + } else { + int prevDot = host.lastIndexOf('.'); + prevDot = host.lastIndexOf('.', prevDot - 1); + // shortcut for short URLs + if (prevDot < 0) { + digests.add(digest.digest(host.getBytes())); + } else { + byte[] hostBytes = host.substring(prevDot + 1, host.length()).getBytes(); + digests.add(digest.digest(hostBytes)); + int digestCount = 1; + while (prevDot >= 0 && digestCount < maxDigests) { + prevDot = host.lastIndexOf('.', prevDot - 1); + hostBytes = host.substring(prevDot + 1, host.length()).getBytes(); + digests.add(digest.digest(hostBytes)); + digestCount++; + } + } + } + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("could not find digest algorithm"); + } + return digests.toArray(new byte[digests.size()][]); + } + + EphemeralDigest(Parcel in) { + final int digestCount = in.readInt(); + if (digestCount == -1) { + mDigestBytes = null; + } else { + mDigestBytes = new byte[digestCount][]; + for (int i = 0; i < digestCount; i++) { + mDigestBytes[i] = in.createByteArray(); + } + } + mDigestPrefix = in.createIntArray(); + } + + public byte[][] getDigestBytes() { + return mDigestBytes; + } + + public int[] getDigestPrefix() { + return mDigestPrefix; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + if (mDigestBytes == null) { + out.writeInt(-1); + } else { + out.writeInt(mDigestBytes.length); + for (int i = 0; i < mDigestBytes.length; i++) { + out.writeByteArray(mDigestBytes[i]); + } + } + out.writeIntArray(mDigestPrefix); + } + + @SuppressWarnings("hiding") + public static final Parcelable.Creator<EphemeralDigest> CREATOR = + new Parcelable.Creator<EphemeralDigest>() { + public EphemeralDigest createFromParcel(Parcel in) { + return new EphemeralDigest(in); + } + + public EphemeralDigest[] newArray(int size) { + return new EphemeralDigest[size]; + } + }; + } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 43eb7514855c..88cf138eb123 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -8583,6 +8583,24 @@ public final class Settings { "ephemeral_cookie_max_size_bytes"; /** + * A mask applied to the ephemeral hash to generate the hash prefix. + * <p> + * Type: int + * + * @hide + */ + public static final String EPHEMERAL_HASH_PREFIX_MASK = "ephemeral_hash_prefix_mask"; + + /** + * Number of hash prefixes to send during ephemeral resolution. + * <p> + * Type: int + * + * @hide + */ + public static final String EPHEMERAL_HASH_PREFIX_COUNT = "ephemeral_hash_prefix_count"; + + /** * The duration for caching uninstalled ephemeral apps. * <p> * Type: long diff --git a/core/java/com/android/internal/app/EphemeralResolverService.java b/core/java/com/android/internal/app/EphemeralResolverService.java index 6ba04a98aa2a..68724a7fc4d2 100644 --- a/core/java/com/android/internal/app/EphemeralResolverService.java +++ b/core/java/com/android/internal/app/EphemeralResolverService.java @@ -39,14 +39,19 @@ import java.util.List; public abstract class EphemeralResolverService extends Service { public static final String EXTRA_RESOLVE_INFO = "com.android.internal.app.RESOLVE_INFO"; public static final String EXTRA_SEQUENCE = "com.android.internal.app.SEQUENCE"; + private static final String EXTRA_PREFIX = "com.android.internal.app.PREFIX"; private Handler mHandler; /** * Called to retrieve resolve info for ephemeral applications. * * @param digestPrefix The hash prefix of the ephemeral's domain. + * @param prefixMask A mask that was applied to each digest prefix. This should + * be used when comparing against the digest prefixes as all bits might + * not be set. */ - protected abstract List<EphemeralResolveInfo> getEphemeralResolveInfoList(int digestPrefix); + protected abstract List<EphemeralResolveInfo> getEphemeralResolveInfoList( + int digestPrefix[], int prefixMask); @Override protected final void attachBaseContext(Context base) { @@ -59,10 +64,13 @@ public abstract class EphemeralResolverService extends Service { return new IEphemeralResolver.Stub() { @Override public void getEphemeralResolveInfoList( - IRemoteCallback callback, int digestPrefix, int sequence) { - mHandler.obtainMessage(ServiceHandler.MSG_GET_EPHEMERAL_RESOLVE_INFO, - digestPrefix, sequence, callback) - .sendToTarget(); + IRemoteCallback callback, int digestPrefix[], int prefixMask, int sequence) { + final Message msg = mHandler.obtainMessage( + ServiceHandler.MSG_GET_EPHEMERAL_RESOLVE_INFO, prefixMask, sequence, callback); + final Bundle data = new Bundle(); + data.putIntArray(EXTRA_PREFIX, digestPrefix); + msg.setData(data); + msg.sendToTarget(); } }; } @@ -81,8 +89,9 @@ public abstract class EphemeralResolverService extends Service { switch (action) { case MSG_GET_EPHEMERAL_RESOLVE_INFO: { final IRemoteCallback callback = (IRemoteCallback) message.obj; + final int[] digestPrefix = message.getData().getIntArray(EXTRA_PREFIX); final List<EphemeralResolveInfo> resolveInfo = - getEphemeralResolveInfoList(message.arg1); + getEphemeralResolveInfoList(digestPrefix, message.arg1); final Bundle data = new Bundle(); data.putInt(EXTRA_SEQUENCE, message.arg2); data.putParcelableList(EXTRA_RESOLVE_INFO, resolveInfo); diff --git a/core/java/com/android/internal/app/IEphemeralResolver.aidl b/core/java/com/android/internal/app/IEphemeralResolver.aidl index 40429ee2cccb..9ff1322765f4 100644 --- a/core/java/com/android/internal/app/IEphemeralResolver.aidl +++ b/core/java/com/android/internal/app/IEphemeralResolver.aidl @@ -20,5 +20,6 @@ import android.content.Intent; import android.os.IRemoteCallback; oneway interface IEphemeralResolver { - void getEphemeralResolveInfoList(IRemoteCallback callback, int digestPrefix, int sequence); + void getEphemeralResolveInfoList(IRemoteCallback callback, in int[] digestPrefix, + int prefixMask, int sequence); } diff --git a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java index fe6fb1fd7792..b25ef175c6a1 100644 --- a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java +++ b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java @@ -64,11 +64,12 @@ final class EphemeralResolverConnection { mIntent = new Intent().setComponent(componentName); } - public final List<EphemeralResolveInfo> getEphemeralResolveInfoList(int hashPrefix) { + public final List<EphemeralResolveInfo> getEphemeralResolveInfoList( + int hashPrefix[], int prefixMask) { throwIfCalledOnMainThread(); try { return mGetEphemeralResolveInfoCaller.getEphemeralResolveInfoList( - getRemoteInstanceLazy(), hashPrefix); + getRemoteInstanceLazy(), hashPrefix, prefixMask); } catch (RemoteException re) { } catch (TimeoutException te) { } finally { @@ -177,10 +178,10 @@ final class EphemeralResolverConnection { } public List<EphemeralResolveInfo> getEphemeralResolveInfoList( - IEphemeralResolver target, int hashPrefix) + IEphemeralResolver target, int hashPrefix[], int prefixMask) throws RemoteException, TimeoutException { final int sequence = onBeforeRemoteCall(); - target.getEphemeralResolveInfoList(mCallback, hashPrefix, sequence); + target.getEphemeralResolveInfoList(mCallback, hashPrefix, prefixMask, sequence); return getResultTimed(sequence); } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 0bd9822e0ec0..25627079f36e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -125,6 +125,7 @@ import android.content.pm.AppsQueryHelper; import android.content.pm.ComponentInfo; import android.content.pm.EphemeralApplicationInfo; import android.content.pm.EphemeralResolveInfo; +import android.content.pm.EphemeralResolveInfo.EphemeralDigest; import android.content.pm.EphemeralResolveInfo.EphemeralResolveIntentInfo; import android.content.pm.FeatureInfo; import android.content.pm.IOnPermissionsChangeListener; @@ -197,6 +198,7 @@ import android.os.storage.StorageEventListener; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; import android.os.storage.VolumeRecord; +import android.provider.Settings.Global; import android.security.KeyStore; import android.security.SystemKeyStore; import android.system.ErrnoException; @@ -462,6 +464,9 @@ public class PackageManagerService extends IPackageManager.Stub { private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay"; + private static int DEFAULT_EPHEMERAL_HASH_PREFIX_MASK = 0xFFFFFFFF; + private static int DEFAULT_EPHEMERAL_HASH_PREFIX_COUNT = 5; + /** Permission grant: not grant the permission. */ private static final int GRANT_DENIED = 1; @@ -4991,48 +4996,44 @@ public class PackageManagerService extends IPackageManager.Stub { private EphemeralResolveInfo getEphemeralResolveInfo(Intent intent, String resolvedType, int userId) { - MessageDigest digest = null; - try { - digest = MessageDigest.getInstance(EphemeralResolveInfo.SHA_ALGORITHM); - } catch (NoSuchAlgorithmException e) { - // If we can't create a digest, ignore ephemeral apps. - return null; - } - - final byte[] hostBytes = intent.getData().getHost().getBytes(); - final byte[] digestBytes = digest.digest(hostBytes); - int shaPrefix = - (digestBytes[0] & 0xFF) << 24 - | (digestBytes[1] & 0xFF) << 16 - | (digestBytes[2] & 0xFF) << 8 - | (digestBytes[3] & 0xFF) << 0; + final int ephemeralPrefixMask = Global.getInt(mContext.getContentResolver(), + Global.EPHEMERAL_HASH_PREFIX_MASK, DEFAULT_EPHEMERAL_HASH_PREFIX_MASK); + final int ephemeralPrefixCount = Global.getInt(mContext.getContentResolver(), + Global.EPHEMERAL_HASH_PREFIX_COUNT, DEFAULT_EPHEMERAL_HASH_PREFIX_COUNT); + final EphemeralDigest digest = new EphemeralDigest(intent.getData(), ephemeralPrefixCount); + final int[] shaPrefix = digest.getDigestPrefix(); + final byte[][] digestBytes = digest.getDigestBytes(); final List<EphemeralResolveInfo> ephemeralResolveInfoList = - mEphemeralResolverConnection.getEphemeralResolveInfoList(shaPrefix); + mEphemeralResolverConnection.getEphemeralResolveInfoList( + shaPrefix, ephemeralPrefixMask); if (ephemeralResolveInfoList == null || ephemeralResolveInfoList.size() == 0) { // No hash prefix match; there are no ephemeral apps for this domain. return null; } - for (int i = ephemeralResolveInfoList.size() - 1; i >= 0; --i) { - EphemeralResolveInfo ephemeralApplication = ephemeralResolveInfoList.get(i); - if (!Arrays.equals(digestBytes, ephemeralApplication.getDigestBytes())) { - continue; - } - final List<IntentFilter> filters = ephemeralApplication.getFilters(); - // No filters; this should never happen. - if (filters.isEmpty()) { - continue; - } - // We have a domain match; resolve the filters to see if anything matches. - final EphemeralIntentResolver ephemeralResolver = new EphemeralIntentResolver(); - for (int j = filters.size() - 1; j >= 0; --j) { - final EphemeralResolveIntentInfo intentInfo = - new EphemeralResolveIntentInfo(filters.get(j), ephemeralApplication); - ephemeralResolver.addFilter(intentInfo); - } - List<EphemeralResolveInfo> matchedResolveInfoList = ephemeralResolver.queryIntent( - intent, resolvedType, false /*defaultOnly*/, userId); - if (!matchedResolveInfoList.isEmpty()) { - return matchedResolveInfoList.get(0); + + // Go in reverse order so we match the narrowest scope first. + for (int i = shaPrefix.length; i >= 0 ; --i) { + for (EphemeralResolveInfo ephemeralApplication : ephemeralResolveInfoList) { + if (!Arrays.equals(digestBytes[i], ephemeralApplication.getDigestBytes())) { + continue; + } + final List<IntentFilter> filters = ephemeralApplication.getFilters(); + // No filters; this should never happen. + if (filters.isEmpty()) { + continue; + } + // We have a domain match; resolve the filters to see if anything matches. + final EphemeralIntentResolver ephemeralResolver = new EphemeralIntentResolver(); + for (int j = filters.size() - 1; j >= 0; --j) { + final EphemeralResolveIntentInfo intentInfo = + new EphemeralResolveIntentInfo(filters.get(j), ephemeralApplication); + ephemeralResolver.addFilter(intentInfo); + } + List<EphemeralResolveInfo> matchedResolveInfoList = ephemeralResolver.queryIntent( + intent, resolvedType, false /*defaultOnly*/, userId); + if (!matchedResolveInfoList.isEmpty()) { + return matchedResolveInfoList.get(0); + } } } // Hash or filter mis-match; no ephemeral apps for this domain. |