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

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: Resond to Peter's 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;
16 import android.util.Log; 17 import android.util.Log;
17 18
18 import org.chromium.base.annotations.SuppressFBWarnings; 19 import org.chromium.base.annotations.SuppressFBWarnings;
19 20
21 import java.io.IOException;
22 import java.io.RandomAccessFile;
23 import java.nio.MappedByteBuffer;
24 import java.nio.channels.FileChannel;
25 import java.security.KeyFactory;
26 import java.security.PublicKey;
27 import java.security.SignatureException;
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 /**
106 * @param context The context to use to check whether WebAPK is valid. 118 * @param context The context to use to check whether WebAPK is valid.
107 * @param infos The ResolveInfos to search. 119 * @param infos The ResolveInfos to search.
108 * @return ResolveInfo which corresponds to a WebAPK. Null if none of the Re solveInfos 120 * @return ResolveInfo which corresponds to a WebAPK. Null if none of the Re solveInfos
109 * corresponds to a WebAPK. 121 * corresponds to a WebAPK.
110 */ 122 */
111 private static ResolveInfo findResolveInfo(Context context, List<ResolveInfo > infos) { 123 private static ResolveInfo findResolveInfo(Context context, List<ResolveInfo > infos) {
112 for (ResolveInfo info : infos) { 124 for (ResolveInfo info : infos) {
113 if (info.activityInfo != null 125 if (info.activityInfo != null
114 && isValidWebApk(context, info.activityInfo.packageName)) { 126 && isValidWebApk(context, info.activityInfo.packageName)) {
115 return info; 127 return info;
116 } 128 }
117 } 129 }
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.
135 *
pkotwicz 2017/03/31 03:59:24 Nit: Remove the new line
ScottK 2017/04/03 17:44:18 done.
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");
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 (startUrl == null || startUrl.isEmpty());
pkotwicz 2017/03/31 03:59:24 You can use TextUtils.isEmpty() to do this
ScottK 2017/04/03 17:44:18 On 2017/03/31 at 03:59:24, pkotwicz wrote:chrome/a
179 }
180
181 private static boolean verifyV1WebApk(PackageInfo packageInfo, String webapp PackageName) {
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 MappedByteBuffer buf = null;
pkotwicz 2017/03/31 03:59:24 Can |buf| be moved inside to try {} statement?
ScottK 2017/04/03 17:44:18 Done.
217 try {
218 file = new RandomAccessFile(packageFilename, "r");
219 inChannel = file.getChannel();
220 buf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size ());
221 buf.load();
222
223 WebApkVerifySignature v = new WebApkVerifySignature(buf);
224 @WebApkVerifySignature.Error
pkotwicz 2017/03/31 03:59:24 I haven't seen annotations on enum ints anywhere b
ScottK 2017/04/03 17:44:18 sure.
225 int result = v.read();
226 if (result != WebApkVerifySignature.ERROR_OK) {
227 Log.e(TAG, String.format("Failure reading %s: %s", packageFilena me, result));
228 return false;
229 }
230 result = v.verifySignature(commentSignedPublicKey);
231 Log.d(TAG, "File " + packageFilename + ": " + result);
232 return result == WebApkVerifySignature.ERROR_OK;
233 } catch (SignatureException e) {
234 Log.e(TAG, "WebApk SignatureException for file " + packageFilename, e);
235 return false;
236 } catch (IllegalArgumentException | IOException e) {
pkotwicz 2017/03/31 03:59:24 You can just check the generic exception here - ca
ScottK 2017/04/03 17:44:18 Thanks!
237 Log.e(TAG, "WebApk file error for file " + packageFilename, e);
238 return false;
239 } finally {
240 if (inChannel != null) {
241 try {
242 inChannel.close();
243 } catch (IOException e) {
244 }
245 }
246 if (file != null) {
247 try {
248 file.close();
249 } catch (IOException e) {
152 } 250 }
153 } 251 }
154 } 252 }
155 Log.d(TAG, "WebApk invalid");
156 return false;
157 } 253 }
158 254
159 /** 255 /**
160 * Initializes the WebApkValidator with the expected signature that WebAPKs must be signed 256 * Initializes the WebApkValidator with the expected signature that WebAPKs must be signed with
161 * with for the current host. 257 * for the current host.
258 *
pkotwicz 2017/03/31 03:59:24 Nit: Remove new line
ScottK 2017/04/03 17:44:18 Done.
162 * @param expectedSignature 259 * @param expectedSignature
260 * @param v2PublicKeyBytes
pkotwicz 2017/03/31 03:59:24 Remove @params if you are not documenting them
ScottK 2017/04/03 17:44:18 Done.
163 */ 261 */
164 @SuppressFBWarnings("EI_EXPOSE_STATIC_REP2") 262 @SuppressFBWarnings("EI_EXPOSE_STATIC_REP2")
165 public static void initWithBrowserHostSignature(byte[] expectedSignature) { 263 public static void initWithBrowserHostSignature(
166 if (sExpectedSignature != null) { 264 byte[] expectedSignature, byte[] v2PublicKeyBytes) {
167 return; 265 if (sExpectedSignature == null) {
266 sExpectedSignature = expectedSignature;
168 } 267 }
169 sExpectedSignature = expectedSignature; 268 if (sCommentSignedPublicKeyBytes == null) {
269 sCommentSignedPublicKeyBytes = v2PublicKeyBytes;
270 }
271 }
272
273 /**
274 * Lazy evaluate the creation of the Public Key as the KeyFactories may not yet be initialized.
275 * @return The decoded PublicKey or null
276 */
277 private static PublicKey getCommentSignedPublicKey() throws Exception {
278 if (sCommentSignedPublicKey == null) {
279 sCommentSignedPublicKey =
280 KeyFactory.getInstance(KEY_FACTORY)
281 .generatePublic(new X509EncodedKeySpec(sCommentSigne dPublicKeyBytes));
282 }
283 return sCommentSignedPublicKey;
170 } 284 }
171 } 285 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698