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

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: Copy the arrays to prevent error in try bot. 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
19 import java.io.IOException;
20 import java.io.RandomAccessFile;
21 import java.nio.MappedByteBuffer;
22 import java.nio.channels.FileChannel;
23 import java.security.KeyFactory;
24 import java.security.NoSuchAlgorithmException;
25 import java.security.PublicKey;
26 import java.security.SignatureException;
27 import java.security.spec.InvalidKeySpecException;
28 import java.security.spec.X509EncodedKeySpec;
18 import java.util.Arrays; 29 import java.util.Arrays;
19 import java.util.LinkedList; 30 import java.util.LinkedList;
20 import java.util.List; 31 import java.util.List;
21 32
22 /** 33 /**
23 * 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
24 * Server. 35 * Server.
25 */ 36 */
26 public class WebApkValidator { 37 public class WebApkValidator {
38 private static final String TAG = "WebApkValidator";
39 private static final String KEY_FACTORY = "EC"; // aka "ECDSA"
27 40
28 private static final String TAG = "WebApkValidator";
29 private static byte[] sExpectedSignature; 41 private static byte[] sExpectedSignature;
42 private static byte[] sCommentSignedPublicKeyBytes;
43 private static PublicKey sCommentSignedPublicKey;
30 44
31 /** 45 /**
32 * 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
33 * 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
34 * handler is the WebAPK. 48 * WebAPK.
35 * 49 *
36 * 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.
37 * 51 *
38 * @param context The application context. 52 * @param context The application context.
39 * @param url The url to check. 53 * @param url The url to check.
40 * @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
41 * handled by a WebAPK. 55 * handled by a WebAPK.
42 */ 56 */
43 public static String queryWebApkPackage(Context context, String url) { 57 public static String queryWebApkPackage(Context context, String url) {
44 return findWebApkPackage(context, resolveInfosForUrl(context, url)); 58 return findWebApkPackage(context, resolveInfosForUrl(context, url));
45 } 59 }
46 60
47 /** 61 /**
48 * 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
49 * 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
50 * handler is the WebAPK. 64 * WebAPK.
51 * 65 *
52 * 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.
53 * 67 *
54 * @param context The application context. 68 * @param context The application context.
55 * @param url The url to check. 69 * @param url The url to check.
56 * @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
57 * handled by a WebAPK. 71 * handled by a WebAPK.
58 */ 72 */
59 public static ResolveInfo queryResolveInfo(Context context, String url) { 73 public static ResolveInfo queryResolveInfo(Context context, String url) {
60 return findResolveInfo(context, resolveInfosForUrl(context, url)); 74 return findResolveInfo(context, resolveInfosForUrl(context, url));
61 } 75 }
62 76
63 /** 77 /**
64 * 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.
65 * 79 *
66 * @param context The application context. 80 * @param context The application context.
67 * @param url The url to check. 81 * @param url The url to check.
(...skipping 15 matching lines...) Expand all
83 selector.setComponent(null); 97 selector.setComponent(null);
84 } 98 }
85 return context.getPackageManager().queryIntentActivities( 99 return context.getPackageManager().queryIntentActivities(
86 intent, PackageManager.GET_RESOLVED_FILTER); 100 intent, PackageManager.GET_RESOLVED_FILTER);
87 } 101 }
88 102
89 /** 103 /**
90 * @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.
91 * @param infos The ResolveInfos to search. 105 * @param infos The ResolveInfos to search.
92 * @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
93 * ResolveInfos corresponds to a WebAPK. 107 * ResolveInfos corresponds to a WebAPK.
94 */ 108 */
95 public static String findWebApkPackage(Context context, List<ResolveInfo> in fos) { 109 public static String findWebApkPackage(Context context, List<ResolveInfo> in fos) {
96 ResolveInfo resolveInfo = findResolveInfo(context, infos); 110 ResolveInfo resolveInfo = findResolveInfo(context, infos);
97 if (resolveInfo != null) { 111 if (resolveInfo != null) {
98 return resolveInfo.activityInfo.packageName; 112 return resolveInfo.activityInfo.packageName;
99 } 113 }
100 return null; 114 return null;
101 } 115 }
102 116
103 /** 117 /**
104 * @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.
105 * @param infos The ResolveInfos to search. 119 * @param infos The ResolveInfos to search.
106 * @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
107 * corresponds to a WebAPK. 121 * corresponds to a WebAPK.
108 */ 122 */
109 private static ResolveInfo findResolveInfo(Context context, List<ResolveInfo > infos) { 123 private static ResolveInfo findResolveInfo(Context context, List<ResolveInfo > infos) {
110 for (ResolveInfo info : infos) { 124 for (ResolveInfo info : infos) {
111 if (info.activityInfo != null 125 if (info.activityInfo != null
112 && isValidWebApk(context, info.activityInfo.packageName)) { 126 && isValidWebApk(context, info.activityInfo.packageName)) {
113 return info; 127 return info;
114 } 128 }
115 } 129 }
116 return null; 130 return null;
117 } 131 }
118 132
119 /** 133 /**
120 * 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 *
121 * @param context A context 136 * @param context A context
122 * @param webappPackageName The package name to check 137 * @param webappPackageName The package name to check
123 * @return true iff the WebAPK is installed and passes security checks 138 * @return true iff the WebAPK is installed and passes security checks
124 */ 139 */
125 public static boolean isValidWebApk(Context context, String webappPackageNam e) { 140 public static boolean isValidWebApk(Context context, String webappPackageNam e) {
126 if (sExpectedSignature == null) { 141 if (sExpectedSignature == null || sCommentSignedPublicKeyBytes == null) {
127 Log.wtf(TAG, "WebApk validation failure - expected signature not set ." 142 Log.wtf(TAG,
128 + "missing call to WebApkValidator.initWithBrowserHostSignat ure"); 143 "WebApk validation failure - expected signature not set."
144 + "missing call to WebApkValidator.initWithBrowserHo stSignature");
129 } 145 }
130 if (!webappPackageName.startsWith(WEBAPK_PACKAGE_PREFIX)) {
131 return false;
132 }
133 // check signature
134 PackageInfo packageInfo = null; 146 PackageInfo packageInfo = null;
135 try { 147 try {
136 packageInfo = context.getPackageManager().getPackageInfo(webappPacka geName, 148 packageInfo = context.getPackageManager().getPackageInfo(webappPacka geName,
137 PackageManager.GET_SIGNATURES); 149 PackageManager.GET_SIGNATURES | PackageManager.GET_META_DATA );
138 } catch (NameNotFoundException e) { 150 } catch (NameNotFoundException e) {
139 e.printStackTrace(); 151 e.printStackTrace();
140 Log.d(TAG, "WebApk not found"); 152 Log.d(TAG, "WebApk not found");
141 return false; 153 return false;
142 } 154 }
155 if (isNotWebApkQuick(packageInfo)) {
156 return false;
157 }
158 if (verifyV1WebApk(packageInfo, webappPackageName)) {
159 return true;
160 }
143 161
144 final Signature[] arrSignatures = packageInfo.signatures; 162 try {
145 if (arrSignatures != null && arrSignatures.length == 2) { 163 return verifyCommentSignedWebApk(packageInfo);
146 for (Signature signature : arrSignatures) { 164 } catch (IOException e) {
147 if (Arrays.equals(sExpectedSignature, signature.toByteArray())) { 165 Log.e(TAG, "WebApk IOException", e);
148 Log.d(TAG, "WebApk valid - signature match!"); 166 } catch (SignatureException e) {
149 return true; 167 Log.e(TAG, "WebApk SignatureException", e);
150 } 168 } catch (IllegalArgumentException | InvalidKeySpecException | NoSuchAlgo rithmException e) {
151 } 169 Log.e(TAG, "WebApk Error", e);
152 } 170 }
153 Log.d(TAG, "WebApk invalid");
154 return false; 171 return false;
155 } 172 }
156 173
174 /** Determine quickly whether this is definitely not a WebAPK */
175 private static boolean isNotWebApkQuick(PackageInfo packageInfo) {
176 if (packageInfo.signatures == null || packageInfo.signatures.length == 0
177 || packageInfo.signatures.length > 2) {
178 // Wrong number of signatures want 1 or 2.
179 return true;
180 }
181 if (packageInfo.applicationInfo == null || packageInfo.applicationInfo.m etaData == null) {
182 Log.e(TAG, "no application info, or metaData retrieved.");
183 return true;
184 }
185 // Having the startURL in AndroidManifest.xml is a strong signal.
186 String startUrl = packageInfo.applicationInfo.metaData.getString(START_U RL);
187 return (startUrl == null || startUrl.isEmpty());
188 }
189
190 private static boolean verifyV1WebApk(PackageInfo packageInfo, String webapp PackageName) {
191 if (packageInfo.signatures.length != 2
192 || !webappPackageName.startsWith(WEBAPK_PACKAGE_PREFIX)) {
193 return false;
194 }
195 for (Signature signature : packageInfo.signatures) {
196 if (Arrays.equals(sExpectedSignature, signature.toByteArray())) {
197 Log.d(TAG, "WebApk valid - signature match!");
198 return true;
199 }
200 }
201 return false;
202 }
203
204 /** Verify that the comment signed webapk matches the public key. */
205 private static boolean verifyCommentSignedWebApk(PackageInfo packageInfo)
206 throws IOException, SignatureException, IllegalArgumentException,
207 InvalidKeySpecException, NoSuchAlgorithmException {
pkotwicz 2017/03/28 18:35:41 This method should not throw any exceptions
ScottK 2017/03/29 17:59:12 done
208 PublicKey CommentSignedPublicKey = getCommentSignedPublicKey();
209 if (CommentSignedPublicKey == null) {
210 Log.e(TAG, "WebApk validation failure - unable to decode public key" );
211 return false;
212 }
213 if (packageInfo.applicationInfo == null || packageInfo.applicationInfo.s ourceDir == null) {
214 Log.e(TAG, "WebApk validation failure - missing applicationInfo sour cedir");
215 return false;
216 }
217
218 String packageFilename = packageInfo.applicationInfo.sourceDir;
219 RandomAccessFile file = null;
220 FileChannel inChannel = null;
221 MappedByteBuffer buf = null;
222 WebApkVerifySignature.Error verified;
223 try {
224 file = new RandomAccessFile(packageFilename, "r");
225 inChannel = file.getChannel();
226 buf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size ());
227 buf.load();
228
229 WebApkVerifySignature v = new WebApkVerifySignature(buf);
230 verified = v.read();
231
232 if (verified != WebApkVerifySignature.Error.OK) {
233 Log.e(TAG, String.format("Failure reading %s: %s", packageFilena me, verified));
234 return false;
235 }
236 verified = v.verifySignature(sCommentSignedPublicKey);
pkotwicz 2017/03/28 18:35:41 According to http://stackoverflow.com/questions/65
ScottK 2017/03/29 17:59:12 Returning here.
237 Log.d(TAG, "File " + packageFilename + ": " + verified);
238 } finally {
239 if (buf != null) {
240 buf.clear();
pkotwicz 2017/03/28 18:35:41 Is this necessary? Based on the documentation Buff
ScottK 2017/03/29 17:59:12 Guess not, thought it might be a hint to the gc.
241 }
242 if (inChannel != null) {
243 inChannel.close();
244 }
245 if (file != null) {
246 file.close();
247 }
248 }
249 return verified == WebApkVerifySignature.Error.OK;
250 }
251
157 /** 252 /**
158 * Initializes the WebApkValidator with the expected signature that WebAPKs must be signed 253 * Initializes the WebApkValidator with the expected signature that WebAPKs must be signed with
159 * with for the current host. 254 * for the current host.
255 *
160 * @param expectedSignature 256 * @param expectedSignature
257 * @param v2PublicKeyBytes
161 */ 258 */
162 public static void initWithBrowserHostSignature(byte[] expectedSignature) { 259 public static void initWithBrowserHostSignature(
163 if (sExpectedSignature != null) { 260 byte[] expectedSignature, byte[] v2PublicKeyBytes) {
164 return; 261 if (sExpectedSignature == null) {
262 sExpectedSignature = Arrays.copyOf(expectedSignature, expectedSignat ure.length);
165 } 263 }
166 sExpectedSignature = Arrays.copyOf(expectedSignature, expectedSignature. length); 264 if (sCommentSignedPublicKeyBytes == null) {
265 sCommentSignedPublicKeyBytes = Arrays.copyOf(v2PublicKeyBytes, v2Pub licKeyBytes.length);
266 }
267 }
268
269 /**
270 * Lazy evaluate the creation of the Public Key as the KeyFactories may not yet be initialized.
271 *
pkotwicz 2017/03/28 18:35:41 Nit: Remove the new line
272 * @return The decoded PublicKey or null
273 */
274 private static PublicKey getCommentSignedPublicKey()
275 throws InvalidKeySpecException, NoSuchAlgorithmException {
pkotwicz 2017/03/28 18:35:41 You can simplify this to "throws Exception"
ScottK 2017/03/29 17:59:12 done
276 if (sCommentSignedPublicKey == null) {
277 sCommentSignedPublicKey =
278 KeyFactory.getInstance(KEY_FACTORY)
279 .generatePublic(new X509EncodedKeySpec(sCommentSigne dPublicKeyBytes));
280 }
281 return sCommentSignedPublicKey;
167 } 282 }
168 } 283 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698