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

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

Powered by Google App Engine
This is Rietveld 408576698