Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(74)

Side by Side Diff: chrome/android/webapk/libs/client/src/org/chromium/webapk/lib/client/WebApkValidator.java

Issue 2772483002: Commment signed webapks working and verified. (Closed)
Patch Set: Cleanup merge. Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 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.webapk.lib.client; 5 package org.chromium.webapk.lib.client;
6 6
7 import static org.chromium.webapk.lib.common.WebApkConstants.WEBAPK_PACKAGE_PREF IX; 7 import static org.chromium.webapk.lib.common.WebApkConstants.WEBAPK_PACKAGE_PREF IX;
8 import static org.chromium.webapk.lib.common.WebApkMetaDataKeys.START_URL;
8 9
9 import android.content.Context; 10 import android.content.Context;
10 import android.content.Intent; 11 import android.content.Intent;
11 import android.content.pm.PackageInfo; 12 import android.content.pm.PackageInfo;
12 import android.content.pm.PackageManager; 13 import android.content.pm.PackageManager;
13 import android.content.pm.PackageManager.NameNotFoundException; 14 import android.content.pm.PackageManager.NameNotFoundException;
14 import android.content.pm.ResolveInfo; 15 import android.content.pm.ResolveInfo;
15 import android.content.pm.Signature; 16 import android.content.pm.Signature;
17 import android.text.TextUtils;
16 import android.util.Log; 18 import android.util.Log;
17 19
18 import org.chromium.base.annotations.SuppressFBWarnings; 20 import org.chromium.base.annotations.SuppressFBWarnings;
19 21
22 import java.io.IOException;
23 import java.io.RandomAccessFile;
24 import java.nio.MappedByteBuffer;
25 import java.nio.channels.FileChannel;
26 import java.security.KeyFactory;
27 import java.security.PublicKey;
28 import java.security.spec.X509EncodedKeySpec;
20 import java.util.Arrays; 29 import java.util.Arrays;
21 import java.util.LinkedList; 30 import java.util.LinkedList;
22 import java.util.List; 31 import java.util.List;
23 32
24 /** 33 /**
25 * Checks whether a URL belongs to a WebAPK, and whether a WebAPK is signed by t he WebAPK Minting 34 * Checks whether a URL belongs to a WebAPK, and whether a WebAPK is signed by t he WebAPK Minting
26 * Server. 35 * Server.
27 */ 36 */
28 public class WebApkValidator { 37 public class WebApkValidator {
38 private static final String TAG = "WebApkValidator";
39 private static final String KEY_FACTORY = "EC"; // aka "ECDSA"
29 40
30 private static final String TAG = "WebApkValidator";
31 private static byte[] sExpectedSignature; 41 private static byte[] sExpectedSignature;
42 private static byte[] sCommentSignedPublicKeyBytes;
43 private static PublicKey sCommentSignedPublicKey;
32 44
33 /** 45 /**
34 * Queries the PackageManager to determine whether a WebAPK can handle the U RL. Ignores 46 * Queries the PackageManager to determine whether a WebAPK can handle the U RL. Ignores whether
35 * whether the user has selected a default handler for the URL and whether t he default 47 * the user has selected a default handler for the URL and whether the defau lt handler is the
36 * handler is the WebAPK. 48 * WebAPK.
37 * 49 *
38 * NOTE(yfriedman): This can fail if multiple WebAPKs can match the supplied url. 50 * <p>NOTE(yfriedman): This can fail if multiple WebAPKs can match the suppl ied url.
39 * 51 *
40 * @param context The application context. 52 * @param context The application context.
41 * @param url The url to check. 53 * @param url The url to check.
42 * @return Package name of WebAPK which can handle the URL. Null if the url should not be 54 * @return Package name of WebAPK which can handle the URL. Null if the url should not be
43 * handled by a WebAPK. 55 * handled by a WebAPK.
44 */ 56 */
45 public static String queryWebApkPackage(Context context, String url) { 57 public static String queryWebApkPackage(Context context, String url) {
46 return findWebApkPackage(context, resolveInfosForUrl(context, url)); 58 return findWebApkPackage(context, resolveInfosForUrl(context, url));
47 } 59 }
48 60
49 /** 61 /**
50 * Queries the PackageManager to determine whether a WebAPK can handle the U RL. Ignores 62 * Queries the PackageManager to determine whether a WebAPK can handle the U RL. Ignores whether
51 * whether the user has selected a default handler for the URL and whether t he default 63 * the user has selected a default handler for the URL and whether the defau lt handler is the
52 * handler is the WebAPK. 64 * WebAPK.
53 * 65 *
54 * NOTE: This can fail if multiple WebAPKs can match the supplied url. 66 * <p>NOTE: This can fail if multiple WebAPKs can match the supplied url.
55 * 67 *
56 * @param context The application context. 68 * @param context The application context.
57 * @param url The url to check. 69 * @param url The url to check.
58 * @return Resolve Info of a WebAPK which can handle the URL. Null if the ur l should not be 70 * @return Resolve Info of a WebAPK which can handle the URL. Null if the ur l should not be
59 * handled by a WebAPK. 71 * handled by a WebAPK.
60 */ 72 */
61 public static ResolveInfo queryResolveInfo(Context context, String url) { 73 public static ResolveInfo queryResolveInfo(Context context, String url) {
62 return findResolveInfo(context, resolveInfosForUrl(context, url)); 74 return findResolveInfo(context, resolveInfosForUrl(context, url));
63 } 75 }
64 76
65 /** 77 /**
66 * Fetches the list of resolve infos from the PackageManager that can handle the URL. 78 * Fetches the list of resolve infos from the PackageManager that can handle the URL.
67 * 79 *
68 * @param context The application context. 80 * @param context The application context.
69 * @param url The url to check. 81 * @param url The url to check.
(...skipping 15 matching lines...) Expand all
85 selector.setComponent(null); 97 selector.setComponent(null);
86 } 98 }
87 return context.getPackageManager().queryIntentActivities( 99 return context.getPackageManager().queryIntentActivities(
88 intent, PackageManager.GET_RESOLVED_FILTER); 100 intent, PackageManager.GET_RESOLVED_FILTER);
89 } 101 }
90 102
91 /** 103 /**
92 * @param context The context to use to check whether WebAPK is valid. 104 * @param context The context to use to check whether WebAPK is valid.
93 * @param infos The ResolveInfos to search. 105 * @param infos The ResolveInfos to search.
94 * @return Package name of the ResolveInfo which corresponds to a WebAPK. Nu ll if none of the 106 * @return Package name of the ResolveInfo which corresponds to a WebAPK. Nu ll if none of the
95 * ResolveInfos corresponds to a WebAPK. 107 * ResolveInfos corresponds to a WebAPK.
96 */ 108 */
97 public static String findWebApkPackage(Context context, List<ResolveInfo> in fos) { 109 public static String findWebApkPackage(Context context, List<ResolveInfo> in fos) {
98 ResolveInfo resolveInfo = findResolveInfo(context, infos); 110 ResolveInfo resolveInfo = findResolveInfo(context, infos);
99 if (resolveInfo != null) { 111 if (resolveInfo != null) {
100 return resolveInfo.activityInfo.packageName; 112 return resolveInfo.activityInfo.packageName;
101 } 113 }
102 return null; 114 return null;
103 } 115 }
104 116
105 /** 117 /**
(...skipping 12 matching lines...) Expand all
118 return null; 130 return null;
119 } 131 }
120 132
121 /** 133 /**
122 * Returns whether the provided WebAPK is installed and passes signature che cks. 134 * Returns whether the provided WebAPK is installed and passes signature che cks.
123 * @param context A context 135 * @param context A context
124 * @param webappPackageName The package name to check 136 * @param webappPackageName The package name to check
125 * @return true iff the WebAPK is installed and passes security checks 137 * @return true iff the WebAPK is installed and passes security checks
126 */ 138 */
127 public static boolean isValidWebApk(Context context, String webappPackageNam e) { 139 public static boolean isValidWebApk(Context context, String webappPackageNam e) {
128 if (sExpectedSignature == null) { 140 if (sExpectedSignature == null || sCommentSignedPublicKeyBytes == null) {
129 Log.wtf(TAG, "WebApk validation failure - expected signature not set ." 141 Log.wtf(TAG,
130 + "missing call to WebApkValidator.initWithBrowserHostSignat ure"); 142 "WebApk validation failure - expected signature not set."
143 + "missing call to WebApkValidator.initWithBrowserHo stSignature");
131 } 144 }
132 if (!webappPackageName.startsWith(WEBAPK_PACKAGE_PREFIX)) {
133 return false;
134 }
135 // check signature
136 PackageInfo packageInfo = null; 145 PackageInfo packageInfo = null;
137 try { 146 try {
138 packageInfo = context.getPackageManager().getPackageInfo(webappPacka geName, 147 packageInfo = context.getPackageManager().getPackageInfo(webappPacka geName,
139 PackageManager.GET_SIGNATURES); 148 PackageManager.GET_SIGNATURES | PackageManager.GET_META_DATA );
140 } catch (NameNotFoundException e) { 149 } catch (NameNotFoundException e) {
141 e.printStackTrace(); 150 e.printStackTrace();
142 Log.d(TAG, "WebApk not found"); 151 Log.d(TAG, "WebApk not found");
143 return false; 152 return false;
144 } 153 }
154 if (isNotWebApkQuick(packageInfo)) {
155 return false;
156 }
157 if (verifyV1WebApk(packageInfo, webappPackageName)) {
158 return true;
159 }
145 160
146 final Signature[] arrSignatures = packageInfo.signatures; 161 return verifyCommentSignedWebApk(packageInfo);
Yaron 2017/04/04 15:43:29 can we guard this behind an about:flag. Sam pointe
ScottK 2017/04/04 21:03:03 I don't think we need a flag as the existing of a
Yaron 2017/04/06 15:34:46 Explained off-thread why the flag might be advanta
Yaron 2017/04/06 15:51:50 Discussed with Sam off-thread and he proposed a go
ScottK 2017/04/07 21:57:08 Added a new flag `any-webapk-package-name'
147 if (arrSignatures != null && arrSignatures.length == 2) { 162 }
148 for (Signature signature : arrSignatures) { 163
149 if (Arrays.equals(sExpectedSignature, signature.toByteArray())) { 164 /** Determine quickly whether this is definitely not a WebAPK */
150 Log.d(TAG, "WebApk valid - signature match!"); 165 private static boolean isNotWebApkQuick(PackageInfo packageInfo) {
151 return true; 166 if (packageInfo.signatures == null || packageInfo.signatures.length == 0
167 || packageInfo.signatures.length > 2) {
168 // Wrong number of signatures want 1 or 2.
169 return true;
170 }
171 if (packageInfo.applicationInfo == null || packageInfo.applicationInfo.m etaData == null) {
172 Log.e(TAG, "no application info, or metaData retrieved.");
173 return true;
174 }
175 // Having the startURL in AndroidManifest.xml is a strong signal.
176 String startUrl = packageInfo.applicationInfo.metaData.getString(START_U RL);
177 return TextUtils.isEmpty(startUrl);
178 }
179
180 private static boolean verifyV1WebApk(PackageInfo packageInfo, String webapp PackageName) {
181 if (packageInfo.signatures.length != 2
182 || !webappPackageName.startsWith(WEBAPK_PACKAGE_PREFIX)) {
183 return false;
184 }
185 for (Signature signature : packageInfo.signatures) {
186 if (Arrays.equals(sExpectedSignature, signature.toByteArray())) {
187 Log.d(TAG, "WebApk valid - signature match!");
188 return true;
189 }
190 }
191 return false;
192 }
193
194 /** Verify that the comment signed webapk matches the public key. */
195 private static boolean verifyCommentSignedWebApk(PackageInfo packageInfo) {
196 PublicKey commentSignedPublicKey;
197 try {
198 commentSignedPublicKey = getCommentSignedPublicKey();
199 } catch (Exception e) {
200 Log.e(TAG, "WebApk failed to get Public Key", e);
201 return false;
202 }
203 if (commentSignedPublicKey == null) {
204 Log.e(TAG, "WebApk validation failure - unable to decode public key" );
205 return false;
206 }
207 if (packageInfo.applicationInfo == null || packageInfo.applicationInfo.s ourceDir == null) {
208 Log.e(TAG, "WebApk validation failure - missing applicationInfo sour cedir");
209 return false;
210 }
211
212 String packageFilename = packageInfo.applicationInfo.sourceDir;
213 RandomAccessFile file = null;
214 FileChannel inChannel = null;
215 try {
216 file = new RandomAccessFile(packageFilename, "r");
Yaron 2017/04/04 15:43:29 this is potentially doing file io on the main thre
ScottK 2017/04/04 21:03:02 I've added a file StrictMode allowThreadDiskReads.
217 inChannel = file.getChannel();
218
219 MappedByteBuffer buf =
220 inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.si ze());
221 buf.load();
222
223 WebApkVerifySignature v = new WebApkVerifySignature(buf);
224 int result = v.read();
225 if (result != WebApkVerifySignature.ERROR_OK) {
226 Log.e(TAG, String.format("Failure reading %s: %s", packageFilena me, result));
227 return false;
228 }
229 result = v.verifySignature(commentSignedPublicKey);
230 Log.d(TAG, "File " + packageFilename + ": " + result);
Yaron 2017/04/04 15:43:29 remove log statement
ScottK 2017/04/04 21:03:02 I think in the short term this might be useful to
231 return result == WebApkVerifySignature.ERROR_OK;
232 } catch (Exception e) {
233 Log.e(TAG, "WebApk file error for file " + packageFilename, e);
234 return false;
235 } finally {
236 if (inChannel != null) {
237 try {
238 inChannel.close();
239 } catch (IOException e) {
240 }
241 }
242 if (file != null) {
243 try {
244 file.close();
245 } catch (IOException e) {
152 } 246 }
153 } 247 }
154 } 248 }
155 Log.d(TAG, "WebApk invalid");
156 return false;
157 } 249 }
158 250
159 /** 251 /**
160 * Initializes the WebApkValidator with the expected signature that WebAPKs must be signed 252 * Initializes the WebApkValidator with the expected signature that WebAPKs must be signed with
161 * with for the current host. 253 * for the current host.
162 * @param expectedSignature 254 * @param expectedSignature Old WebAPK RSA signature.
Yaron 2017/04/04 15:43:29 Please avoid using "old" and "new". They are two d
ScottK 2017/04/04 21:03:03 I think that v1 *is* likely to go away, as we star
255 * @param v2PublicKeyBytes New comment signed public key bytes as x509 encod ed public key.
163 */ 256 */
164 @SuppressFBWarnings("EI_EXPOSE_STATIC_REP2") 257 @SuppressFBWarnings("EI_EXPOSE_STATIC_REP2")
165 public static void initWithBrowserHostSignature(byte[] expectedSignature) { 258 public static void initWithBrowserHostSignature(
166 if (sExpectedSignature != null) { 259 byte[] expectedSignature, byte[] v2PublicKeyBytes) {
167 return; 260 if (sExpectedSignature == null) {
261 sExpectedSignature = expectedSignature;
168 } 262 }
169 sExpectedSignature = expectedSignature; 263 if (sCommentSignedPublicKeyBytes == null) {
264 sCommentSignedPublicKeyBytes = v2PublicKeyBytes;
265 }
266 }
267
268 /**
269 * Lazy evaluate the creation of the Public Key as the KeyFactories may not yet be initialized.
270 * @return The decoded PublicKey or null
271 */
272 private static PublicKey getCommentSignedPublicKey() throws Exception {
273 if (sCommentSignedPublicKey == null) {
274 sCommentSignedPublicKey =
275 KeyFactory.getInstance(KEY_FACTORY)
276 .generatePublic(new X509EncodedKeySpec(sCommentSigne dPublicKeyBytes));
277 }
278 return sCommentSignedPublicKey;
170 } 279 }
171 } 280 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698