OLD | NEW |
(Empty) | |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.chrome.browser.payments; |
| 6 |
| 7 import android.content.pm.PackageInfo; |
| 8 import android.content.pm.PackageManager; |
| 9 import android.content.pm.PackageManager.NameNotFoundException; |
| 10 import android.content.pm.ResolveInfo; |
| 11 import android.content.pm.Signature; |
| 12 |
| 13 import org.chromium.base.ContextUtils; |
| 14 import org.chromium.chrome.browser.payments.PaymentManifestDownloader.ManifestDo
wnloadCallback; |
| 15 import org.chromium.chrome.browser.payments.PaymentManifestParser.ManifestParseC
allback; |
| 16 import org.chromium.content_public.browser.WebContents; |
| 17 import org.chromium.payments.mojom.PaymentManifestSection; |
| 18 |
| 19 import java.net.URI; |
| 20 import java.security.MessageDigest; |
| 21 import java.security.NoSuchAlgorithmException; |
| 22 import java.util.ArrayList; |
| 23 import java.util.Formatter; |
| 24 import java.util.HashSet; |
| 25 import java.util.List; |
| 26 import java.util.Set; |
| 27 |
| 28 /** |
| 29 * Verifies that the discovered native Android payment apps have the sufficient
privileges |
| 30 * to handle a single payment method. Downloads and parses the manifest to compa
re package |
| 31 * names, versions, and signatures to the apps. |
| 32 */ |
| 33 public class PaymentManifestVerifier implements ManifestDownloadCallback, Manife
stParseCallback { |
| 34 /** Interface for the callback to invoke when finished verification. */ |
| 35 public interface ManifestVerifyCallback { |
| 36 /** |
| 37 * Enables invoking the given native Android payment app for the given p
ayment method. |
| 38 * Called when the app has been found to have the right privileges to ha
ndle this payment |
| 39 * method. |
| 40 * |
| 41 * @param methodName The payment method name that the payment app offer
s to handle. |
| 42 * @param resolveInfo Identifying information for the native Android pay
ment app. |
| 43 */ |
| 44 void onValidPaymentApp(URI methodName, ResolveInfo resolveInfo); |
| 45 |
| 46 /** |
| 47 * Disables invoking the given native Android payment app for the given
payment method. |
| 48 * Called when the app has been found to not have the right privileges t
o handle this |
| 49 * payment app. |
| 50 * |
| 51 * @param methodName The payment method name that the payment app offer
s to handle. |
| 52 * @param resolveInfo Identifying information for the native Android pay
ment app. |
| 53 */ |
| 54 void onInvalidPaymentApp(URI methodName, ResolveInfo resolveInfo); |
| 55 |
| 56 /** |
| 57 * Disables invoking any native Android payment app for the given paymen
t method. Called if |
| 58 * unable to download or parse the payment method manifest. |
| 59 * |
| 60 * @param methodName The payment method name that has an invalid payment
method manifest. |
| 61 */ |
| 62 void onInvalidManifest(URI methodName); |
| 63 } |
| 64 |
| 65 /** Identifying information about an installed native Android payment app. *
/ |
| 66 private static class AppInfo { |
| 67 /** Identifies a native Android payment app. */ |
| 68 public ResolveInfo resolveInfo; |
| 69 |
| 70 /** The version code for the native Android payment app, e.g., 123. */ |
| 71 public long version; |
| 72 |
| 73 /** |
| 74 * The SHA256 certificate fingerprints for the native Android payment ap
p, .e.g, |
| 75 * ["30:82:01:dd:30:82:01:46:02:01:01:30:0d:06:09:2a:86:48:86:f7:0d:01:0
1:05:05:00:30"]. |
| 76 * Order does not matter for comparison. |
| 77 */ |
| 78 public Set<String> sha256CertFingerprints; |
| 79 } |
| 80 |
| 81 private final PaymentManifestDownloader mDownloader; |
| 82 private final URI mMethodName; |
| 83 private final List<AppInfo> mMatchingApps; |
| 84 private final PaymentManifestParser mParser; |
| 85 private final ManifestVerifyCallback mCallback; |
| 86 private final MessageDigest mMessageDigest; |
| 87 |
| 88 /** |
| 89 * Builds the manifest verifier. |
| 90 * |
| 91 * @param webContents The web contents. |
| 92 * @param methodName The name of the payment method name that apps offer t
o handle. Must be |
| 93 * a valid URI that starts with "https://". |
| 94 * @param matchingApps The identifying information for the native Android pa
yment apps that |
| 95 * offer to handle this payment method. |
| 96 * @param parser The manifest parser. |
| 97 * @param callback The callback to be notified of verification result. |
| 98 */ |
| 99 public PaymentManifestVerifier(WebContents webContents, URI methodName, |
| 100 List<ResolveInfo> matchingApps, PaymentManifestParser parser, |
| 101 ManifestVerifyCallback callback) { |
| 102 mDownloader = new PaymentManifestDownloader(webContents); |
| 103 mMethodName = methodName; |
| 104 mMatchingApps = new ArrayList<>(); |
| 105 for (int i = 0; i < matchingApps.size(); i++) { |
| 106 AppInfo appInfo = new AppInfo(); |
| 107 appInfo.resolveInfo = matchingApps.get(i); |
| 108 mMatchingApps.add(appInfo); |
| 109 } |
| 110 mParser = parser; |
| 111 mCallback = callback; |
| 112 |
| 113 MessageDigest md = null; |
| 114 try { |
| 115 md = MessageDigest.getInstance("SHA-256"); |
| 116 } catch (NoSuchAlgorithmException e) { |
| 117 // Intentionally ignore. |
| 118 } |
| 119 mMessageDigest = md; |
| 120 } |
| 121 |
| 122 /** |
| 123 * Verifies that the discovered native Android payment apps have the suffici
ent |
| 124 * privileges to handle this payment method. |
| 125 */ |
| 126 public void verify() { |
| 127 mDownloader.download(mMethodName, this); |
| 128 } |
| 129 |
| 130 @Override |
| 131 public void onManifestDownloadSuccess(String content) { |
| 132 mParser.parse(content, this); |
| 133 } |
| 134 |
| 135 @Override |
| 136 public void onManifestDownloadFailure() { |
| 137 mCallback.onInvalidManifest(mMethodName); |
| 138 } |
| 139 |
| 140 @Override |
| 141 public void onManifestParseSuccess(PaymentManifestSection[] manifest) { |
| 142 assert manifest != null; |
| 143 assert manifest.length > 0; |
| 144 |
| 145 for (int i = 0; i < manifest.length; i++) { |
| 146 PaymentManifestSection section = manifest[i]; |
| 147 // "package": "*" in the manifest file indicates an unrestricted pay
ment method. Any app |
| 148 // can use this payment method name. |
| 149 if ("*".equals(section.packageName)) { |
| 150 for (int j = 0; j < mMatchingApps.size(); j++) { |
| 151 mCallback.onValidPaymentApp(mMethodName, mMatchingApps.get(j
).resolveInfo); |
| 152 } |
| 153 return; |
| 154 } |
| 155 } |
| 156 |
| 157 if (mMessageDigest == null) { |
| 158 mCallback.onInvalidManifest(mMethodName); |
| 159 return; |
| 160 } |
| 161 |
| 162 for (int i = 0; i < mMatchingApps.size(); i++) { |
| 163 AppInfo appInfo = mMatchingApps.get(i); |
| 164 try { |
| 165 PackageInfo packageInfo = |
| 166 ContextUtils.getApplicationContext().getPackageManager()
.getPackageInfo( |
| 167 appInfo.resolveInfo.activityInfo.packageName, |
| 168 PackageManager.GET_SIGNATURES); |
| 169 appInfo.version = packageInfo.versionCode; |
| 170 appInfo.sha256CertFingerprints = new HashSet<>(); |
| 171 Signature[] signatures = packageInfo.signatures; |
| 172 for (int j = 0; j < signatures.length; j++) { |
| 173 mMessageDigest.update(signatures[j].toByteArray()); |
| 174 |
| 175 // The digest is reset after completing the hash computation
. |
| 176 appInfo.sha256CertFingerprints.add(formatFingerprint(mMessag
eDigest.digest())); |
| 177 } |
| 178 } catch (NameNotFoundException e) { |
| 179 // Leaving appInfo.sha256CertFingerprints uninitialized will cal
l |
| 180 // onInvalidApp() for this app below. |
| 181 } |
| 182 } |
| 183 |
| 184 List<Set<String>> sectionsFingerprints = new ArrayList<>(); |
| 185 for (int i = 0; i < manifest.length; i++) { |
| 186 PaymentManifestSection section = manifest[i]; |
| 187 Set<String> fingerprints = new HashSet<>(); |
| 188 if (section.sha256CertFingerprints != null) { |
| 189 for (int j = 0; j < section.sha256CertFingerprints.length; j++)
{ |
| 190 fingerprints.add(section.sha256CertFingerprints[j]); |
| 191 } |
| 192 } |
| 193 sectionsFingerprints.add(fingerprints); |
| 194 } |
| 195 |
| 196 for (int i = 0; i < mMatchingApps.size(); i++) { |
| 197 AppInfo appInfo = mMatchingApps.get(i); |
| 198 boolean isAllowed = false; |
| 199 for (int j = 0; j < manifest.length; j++) { |
| 200 PaymentManifestSection section = manifest[j]; |
| 201 if (appInfo.resolveInfo.activityInfo.packageName.equals(section.
packageName) |
| 202 && appInfo.version >= section.version |
| 203 && appInfo.sha256CertFingerprints != null |
| 204 && appInfo.sha256CertFingerprints.equals(sectionsFingerp
rints.get(j))) { |
| 205 mCallback.onValidPaymentApp(mMethodName, appInfo.resolveInfo
); |
| 206 isAllowed = true; |
| 207 break; |
| 208 } |
| 209 } |
| 210 if (!isAllowed) mCallback.onInvalidPaymentApp(mMethodName, appInfo.r
esolveInfo); |
| 211 } |
| 212 } |
| 213 |
| 214 @Override |
| 215 public void onManifestParseFailure() { |
| 216 mCallback.onInvalidManifest(mMethodName); |
| 217 } |
| 218 |
| 219 /** |
| 220 * Formats bytes into a string. |
| 221 * |
| 222 * @param input Input bytes. Should not be null. |
| 223 * @return A string representation of the input bytes, e.g., "01:23:45:67:89
:AB:CD:EF". |
| 224 */ |
| 225 private static String formatFingerprint(byte[] input) { |
| 226 StringBuilder builder = new StringBuilder(input.length * 3); |
| 227 Formatter formatter = new Formatter(builder); |
| 228 |
| 229 for (byte b : input) { |
| 230 formatter.format(":%02X", b); |
| 231 } |
| 232 |
| 233 // Cut off the first ":". |
| 234 return builder.substring(1); |
| 235 } |
| 236 } |
OLD | NEW |