OLD | NEW |
---|---|
1 // Copyright 2017 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package org.chromium.content.browser.installedapp; | 5 package org.chromium.content.browser.installedapp; |
6 | 6 |
7 import android.content.Context; | 7 import android.content.Context; |
8 import android.content.pm.ApplicationInfo; | 8 import android.content.pm.ApplicationInfo; |
9 import android.content.pm.PackageManager; | 9 import android.content.pm.PackageManager; |
10 import android.content.pm.PackageManager.NameNotFoundException; | 10 import android.content.pm.PackageManager.NameNotFoundException; |
11 import android.content.res.Resources; | 11 import android.content.res.Resources; |
12 import android.os.AsyncTask; | 12 import android.os.AsyncTask; |
13 import android.os.Handler; | |
13 | 14 |
14 import org.json.JSONArray; | 15 import org.json.JSONArray; |
15 import org.json.JSONException; | 16 import org.json.JSONException; |
16 import org.json.JSONObject; | 17 import org.json.JSONObject; |
17 | 18 |
18 import org.chromium.base.Log; | 19 import org.chromium.base.Log; |
19 import org.chromium.base.VisibleForTesting; | 20 import org.chromium.base.VisibleForTesting; |
20 import org.chromium.installedapp.mojom.InstalledAppProvider; | 21 import org.chromium.installedapp.mojom.InstalledAppProvider; |
21 import org.chromium.installedapp.mojom.RelatedApplication; | 22 import org.chromium.installedapp.mojom.RelatedApplication; |
22 import org.chromium.mojo.system.MojoException; | 23 import org.chromium.mojo.system.MojoException; |
23 | 24 |
24 import java.net.URI; | 25 import java.net.URI; |
25 import java.net.URISyntaxException; | 26 import java.net.URISyntaxException; |
26 import java.util.ArrayList; | 27 import java.util.ArrayList; |
28 import java.util.concurrent.atomic.AtomicInteger; | |
27 | 29 |
28 /** | 30 /** |
29 * Android implementation of the InstalledAppProvider service defined in | 31 * Android implementation of the InstalledAppProvider service defined in |
30 * installed_app_provider.mojom | 32 * installed_app_provider.mojom |
31 */ | 33 */ |
32 public class InstalledAppProviderImpl implements InstalledAppProvider { | 34 public class InstalledAppProviderImpl implements InstalledAppProvider { |
33 @VisibleForTesting | 35 @VisibleForTesting |
34 public static final String ASSET_STATEMENTS_KEY = "asset_statements"; | 36 public static final String ASSET_STATEMENTS_KEY = "asset_statements"; |
35 private static final String ASSET_STATEMENT_FIELD_TARGET = "target"; | 37 private static final String ASSET_STATEMENT_FIELD_TARGET = "target"; |
36 private static final String ASSET_STATEMENT_FIELD_NAMESPACE = "namespace"; | 38 private static final String ASSET_STATEMENT_FIELD_NAMESPACE = "namespace"; |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
69 | 71 |
70 @Override | 72 @Override |
71 public void filterInstalledApps( | 73 public void filterInstalledApps( |
72 final RelatedApplication[] relatedApps, final FilterInstalledAppsRes ponse callback) { | 74 final RelatedApplication[] relatedApps, final FilterInstalledAppsRes ponse callback) { |
73 if (mFrameUrlDelegate.isIncognito()) { | 75 if (mFrameUrlDelegate.isIncognito()) { |
74 callback.call(new RelatedApplication[0]); | 76 callback.call(new RelatedApplication[0]); |
75 return; | 77 return; |
76 } | 78 } |
77 | 79 |
78 final URI frameUrl = mFrameUrlDelegate.getUrl(); | 80 final URI frameUrl = mFrameUrlDelegate.getUrl(); |
81 final AtomicInteger delayMillis = new AtomicInteger(); | |
79 | 82 |
80 // Use an AsyncTask to execute the installed/related checks on a backgro und thread (so as | 83 // Use an AsyncTask to execute the installed/related checks on a backgro und thread (so as |
81 // not to block the UI thread). | 84 // not to block the UI thread). |
82 new AsyncTask<Void, Void, RelatedApplication[]>() { | 85 new AsyncTask<Void, Void, RelatedApplication[]>() { |
Ted C
2017/04/12 15:53:50
I "think" it would be slightly cleaner to change t
Matt Giuca
2017/04/13 00:29:55
Done. Thanks, a good suggestion.
| |
83 @Override | 86 @Override |
84 protected RelatedApplication[] doInBackground(Void... unused) { | 87 protected RelatedApplication[] doInBackground(Void... unused) { |
85 return filterInstalledAppsOnBackgroundThread(relatedApps, frameU rl); | 88 return filterInstalledAppsOnBackgroundThread(relatedApps, frameU rl, delayMillis); |
86 } | 89 } |
87 | 90 |
88 @Override | 91 @Override |
89 protected void onPostExecute(RelatedApplication[] installedApps) { | 92 protected void onPostExecute(final RelatedApplication[] installedApp s) { |
90 callback.call(installedApps); | 93 // Before calling the callback, delay for the amount of time tha t has been |
94 // calculated in |delayMillis|. | |
95 delayThenRun(new Runnable() { | |
96 @Override | |
97 public void run() { | |
98 callback.call(installedApps); | |
99 } | |
100 }, delayMillis.get()); | |
91 } | 101 } |
92 } | 102 } |
93 .execute(); | 103 .execute(); |
94 } | 104 } |
95 | 105 |
96 @Override | 106 @Override |
97 public void close() {} | 107 public void close() {} |
98 | 108 |
99 @Override | 109 @Override |
100 public void onConnectionError(MojoException e) {} | 110 public void onConnectionError(MojoException e) {} |
101 | 111 |
102 /** | 112 /** |
103 * Filters a list of apps, returning those that are both installed and match the origin. | 113 * Filters a list of apps, returning those that are both installed and match the origin. |
104 * | 114 * |
105 * This method is expected to be called on a background thread (not the main UI thread). | 115 * This method is expected to be called on a background thread (not the main UI thread). |
106 * | 116 * |
107 * @param relatedApps A list of applications to be filtered. | 117 * @param relatedApps A list of applications to be filtered. |
108 * @param frameUrl The URL of the frame this operation was called from. | 118 * @param frameUrl The URL of the frame this operation was called from. |
119 * @param delayMillis (Output) Number that will be incremented by the total amount of time in ms | |
120 * that should be delayed before returning to the user, t o mask the installed | |
121 * state of the requested apps. | |
109 * @return A subsequence of applications that meet the criteria. | 122 * @return A subsequence of applications that meet the criteria. |
110 */ | 123 */ |
111 private RelatedApplication[] filterInstalledAppsOnBackgroundThread( | 124 private RelatedApplication[] filterInstalledAppsOnBackgroundThread( |
112 RelatedApplication[] relatedApps, URI frameUrl) { | 125 RelatedApplication[] relatedApps, URI frameUrl, AtomicInteger delayM illis) { |
113 ArrayList<RelatedApplication> installedApps = new ArrayList<RelatedAppli cation>(); | 126 ArrayList<RelatedApplication> installedApps = new ArrayList<RelatedAppli cation>(); |
114 PackageManager pm = mContext.getPackageManager(); | 127 PackageManager pm = mContext.getPackageManager(); |
115 for (RelatedApplication app : relatedApps) { | 128 for (RelatedApplication app : relatedApps) { |
116 // If the package is of type "play", it is installed, and the origin is associated with | 129 // If the package is of type "play", it is installed, and the origin is associated with |
117 // package, add the package to the list of valid packages. | 130 // package, add the package to the list of valid packages. |
118 // NOTE: For security, it must not be possible to distinguish (from the response) | 131 // NOTE: For security, it must not be possible to distinguish (from the response) |
119 // between the app not being installed and the origin not being asso ciated with the app | 132 // between the app not being installed and the origin not being asso ciated with the app |
120 // (otherwise, arbitrary websites would be able to test whether un-a ssociated apps are | 133 // (otherwise, arbitrary websites would be able to test whether un-a ssociated apps are |
121 // installed on the user's device). | 134 // installed on the user's device). |
122 if (app.platform.equals(RELATED_APP_PLATFORM_ANDROID) && app.id != n ull | 135 if (app.platform.equals(RELATED_APP_PLATFORM_ANDROID) && app.id != n ull) { |
123 && isAppInstalledAndAssociatedWithOrigin(app.id, frameUrl, p m)) { | 136 delayMillis.addAndGet(calculateDelayForPackageMs(app.id)); |
124 installedApps.add(app); | 137 if (isAppInstalledAndAssociatedWithOrigin(app.id, frameUrl, pm)) { |
138 installedApps.add(app); | |
139 } | |
125 } | 140 } |
126 } | 141 } |
127 | 142 |
128 RelatedApplication[] installedAppsArray = new RelatedApplication[install edApps.size()]; | 143 RelatedApplication[] installedAppsArray = new RelatedApplication[install edApps.size()]; |
129 installedApps.toArray(installedAppsArray); | 144 installedApps.toArray(installedAppsArray); |
130 return installedAppsArray; | 145 return installedAppsArray; |
131 } | 146 } |
132 | 147 |
133 /** | 148 /** |
149 * Determines how long to artifically delay for, for a particular package na me. | |
150 */ | |
151 private int calculateDelayForPackageMs(String packageName) { | |
152 // Important timing-attack prevention measure: delay by a pseudo-random amount of time, to | |
153 // add significant noise to the time taken to check whether this app is installed and | |
154 // related. Otherwise, it would be possible to tell whether a non-relate d app is installed, | |
155 // based on the time this operation takes. | |
156 // | |
157 // Generate a 16-bit hash based on a unique device ID + the package name . | |
158 short hash = PackageHash.hashForPackage(packageName); | |
159 | |
160 // The time delay is the low 10 bits of the hash in 100ths of a ms (betw een 0 and 10ms). | |
161 int delayHundredthsOfMs = hash & 0x3ff; | |
162 return delayHundredthsOfMs / 100; | |
163 } | |
164 | |
165 /** | |
134 * Determines whether a particular app is installed and matches the origin. | 166 * Determines whether a particular app is installed and matches the origin. |
135 * | 167 * |
136 * @param packageName Name of the Android package to check if installed. Ret urns false if the | 168 * @param packageName Name of the Android package to check if installed. Ret urns false if the |
137 * app is not installed. | 169 * app is not installed. |
138 * @param frameUrl Returns false if the Android package does not declare ass ociation with the | 170 * @param frameUrl Returns false if the Android package does not declare ass ociation with the |
139 * origin of this URL. Can be null. | 171 * origin of this URL. Can be null. |
140 */ | 172 */ |
141 private static boolean isAppInstalledAndAssociatedWithOrigin( | 173 private boolean isAppInstalledAndAssociatedWithOrigin( |
142 String packageName, URI frameUrl, PackageManager pm) { | 174 String packageName, URI frameUrl, PackageManager pm) { |
143 if (frameUrl == null) return false; | 175 if (frameUrl == null) return false; |
144 | 176 |
145 // Early-exit if the Android app is not installed. | 177 // Early-exit if the Android app is not installed. |
146 JSONArray statements; | 178 JSONArray statements; |
147 try { | 179 try { |
148 statements = getAssetStatements(packageName, pm); | 180 statements = getAssetStatements(packageName, pm); |
149 } catch (NameNotFoundException e) { | 181 } catch (NameNotFoundException e) { |
150 return false; | 182 return false; |
151 } | 183 } |
(...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
269 } | 301 } |
270 | 302 |
271 private static boolean statementTargetMatches(URI frameUrl, URI assetUrl) { | 303 private static boolean statementTargetMatches(URI frameUrl, URI assetUrl) { |
272 if (assetUrl.getScheme() == null || assetUrl.getAuthority() == null) { | 304 if (assetUrl.getScheme() == null || assetUrl.getAuthority() == null) { |
273 return false; | 305 return false; |
274 } | 306 } |
275 | 307 |
276 return assetUrl.getScheme().equals(frameUrl.getScheme()) | 308 return assetUrl.getScheme().equals(frameUrl.getScheme()) |
277 && assetUrl.getAuthority().equals(frameUrl.getAuthority()); | 309 && assetUrl.getAuthority().equals(frameUrl.getAuthority()); |
278 } | 310 } |
311 | |
312 /** | |
313 * Runs a Runnable task after a given delay. | |
314 * | |
315 * Protected and non-static for testing. | |
316 * | |
317 * @param r The Runnable that will be executed. | |
318 * @param delayMillis The delay (in ms) until the Runnable will be executed. | |
319 * @return True if the Runnable was successfully placed into the message que ue. | |
320 */ | |
321 protected boolean delayThenRun(Runnable r, long delayMillis) { | |
322 return new Handler().postDelayed(r, delayMillis); | |
Ted C
2017/04/12 15:53:50
Use can use ThreadUtils.postOnUiThreadDelayed here
Matt Giuca
2017/04/13 00:29:55
Done.
| |
323 } | |
279 } | 324 } |
OLD | NEW |