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

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

Powered by Google App Engine
This is Rietveld 408576698