| OLD | NEW |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | 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 | 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.chrome.browser.payments; | 5 package org.chromium.chrome.browser.payments; |
| 6 | 6 |
| 7 import android.content.pm.PackageInfo; | 7 import android.content.pm.PackageInfo; |
| 8 import android.content.pm.ResolveInfo; | 8 import android.content.pm.ResolveInfo; |
| 9 import android.content.pm.Signature; | 9 import android.content.pm.Signature; |
| 10 | 10 |
| 11 import org.chromium.base.Log; | 11 import org.chromium.base.Log; |
| 12 import org.chromium.chrome.browser.UrlConstants; | 12 import org.chromium.chrome.browser.UrlConstants; |
| 13 import org.chromium.components.payments.PaymentManifestDownloader; | 13 import org.chromium.components.payments.PaymentManifestDownloader; |
| 14 import org.chromium.components.payments.PaymentManifestDownloader.ManifestDownlo
adCallback; | 14 import org.chromium.components.payments.PaymentManifestDownloader.ManifestDownlo
adCallback; |
| 15 import org.chromium.components.payments.PaymentManifestParser; | 15 import org.chromium.components.payments.PaymentManifestParser; |
| 16 import org.chromium.components.payments.PaymentManifestParser.ManifestParseCallb
ack; | 16 import org.chromium.components.payments.PaymentManifestParser.ManifestParseCallb
ack; |
| 17 import org.chromium.payments.mojom.PaymentManifestSection; | 17 import org.chromium.payments.mojom.WebAppManifestSection; |
| 18 | 18 |
| 19 import java.net.URI; | 19 import java.net.URI; |
| 20 import java.security.MessageDigest; | 20 import java.security.MessageDigest; |
| 21 import java.security.NoSuchAlgorithmException; | 21 import java.security.NoSuchAlgorithmException; |
| 22 import java.util.ArrayList; | 22 import java.util.ArrayList; |
| 23 import java.util.Formatter; | 23 import java.util.Formatter; |
| 24 import java.util.HashMap; |
| 24 import java.util.HashSet; | 25 import java.util.HashSet; |
| 25 import java.util.List; | 26 import java.util.List; |
| 27 import java.util.Map; |
| 26 import java.util.Set; | 28 import java.util.Set; |
| 27 | 29 |
| 28 /** | 30 /** |
| 29 * Verifies that the discovered native Android payment apps have the sufficient
privileges | 31 * 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 | 32 * to handle a single payment method. Downloads and parses the manifest to compa
re package |
| 31 * names, versions, and signatures to the apps. | 33 * names, versions, and signatures to the apps. |
| 32 * | 34 * |
| 33 * Spec: | 35 * Spec: |
| 34 * https://docs.google.com/document/d/1izV4uC-tiRJG3JLooqY3YRLU22tYOsLTNq0P_InPJ
eE/edit#heading=h.cjp3jlnl47h5 | 36 * https://docs.google.com/document/d/1izV4uC-tiRJG3JLooqY3YRLU22tYOsLTNq0P_InPJ
eE/edit#heading=h.cjp3jlnl47h5 |
| 35 */ | 37 */ |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 77 | 79 |
| 78 /** | 80 /** |
| 79 * The SHA256 certificate fingerprints for the native Android payment ap
p, .e.g, | 81 * The SHA256 certificate fingerprints for the native Android payment ap
p, .e.g, |
| 80 * ["308201dd30820146020101300d06092a864886f70d010105050030"]. | 82 * ["308201dd30820146020101300d06092a864886f70d010105050030"]. |
| 81 */ | 83 */ |
| 82 public Set<String> sha256CertFingerprints; | 84 public Set<String> sha256CertFingerprints; |
| 83 } | 85 } |
| 84 | 86 |
| 85 private final PaymentManifestDownloader mDownloader; | 87 private final PaymentManifestDownloader mDownloader; |
| 86 private final URI mMethodName; | 88 private final URI mMethodName; |
| 87 private final List<AppInfo> mMatchingApps; | 89 |
| 90 /** A mapping from the package name to the application that matches the meth
od name. */ |
| 91 private final Map<String, AppInfo> mMatchingApps; |
| 92 |
| 88 private final PaymentManifestParser mParser; | 93 private final PaymentManifestParser mParser; |
| 89 private final PackageManagerDelegate mPackageManagerDelegate; | 94 private final PackageManagerDelegate mPackageManagerDelegate; |
| 90 private final ManifestVerifyCallback mCallback; | 95 private final ManifestVerifyCallback mCallback; |
| 91 private final MessageDigest mMessageDigest; | 96 private final MessageDigest mMessageDigest; |
| 97 private int mPendingWebAppManifestsCount; |
| 98 private boolean mAtLeastOneManifestFailedToDownloadOrParse; |
| 92 | 99 |
| 93 /** | 100 /** |
| 94 * Builds the manifest verifier. | 101 * Builds the manifest verifier. |
| 95 * | 102 * |
| 96 * @param methodName The name of the payment method name that ap
ps offer to handle. | 103 * @param methodName The name of the payment method name that ap
ps offer to handle. |
| 97 * Must be an absolute URI with HTTPS scheme. | 104 * Must be an absolute URI with HTTPS scheme. |
| 98 * @param matchingApps The identifying information for the native
Android payment apps | 105 * @param matchingApps The identifying information for the native
Android payment apps |
| 99 * that offer to handle this payment method. | 106 * that offer to handle this payment method. |
| 100 * @param downloader The manifest downloader. | 107 * @param downloader The manifest downloader. |
| 101 * @param parser The manifest parser. | 108 * @param parser The manifest parser. |
| 102 * @param packageManagerDelegate The package information retriever. | 109 * @param packageManagerDelegate The package information retriever. |
| 103 * @param callback The callback to be notified of verification
result. | 110 * @param callback The callback to be notified of verification
result. |
| 104 */ | 111 */ |
| 105 public PaymentManifestVerifier(URI methodName, List<ResolveInfo> matchingApp
s, | 112 public PaymentManifestVerifier(URI methodName, List<ResolveInfo> matchingApp
s, |
| 106 PaymentManifestDownloader downloader, PaymentManifestParser parser, | 113 PaymentManifestDownloader downloader, PaymentManifestParser parser, |
| 107 PackageManagerDelegate packageManagerDelegate, ManifestVerifyCallbac
k callback) { | 114 PackageManagerDelegate packageManagerDelegate, ManifestVerifyCallbac
k callback) { |
| 108 assert methodName.isAbsolute(); | 115 assert methodName.isAbsolute(); |
| 109 assert UrlConstants.HTTPS_SCHEME.equals(methodName.getScheme()); | 116 assert UrlConstants.HTTPS_SCHEME.equals(methodName.getScheme()); |
| 110 assert !matchingApps.isEmpty(); | 117 assert !matchingApps.isEmpty(); |
| 111 | 118 |
| 112 mMethodName = methodName; | 119 mMethodName = methodName; |
| 113 mMatchingApps = new ArrayList<>(); | 120 mMatchingApps = new HashMap<>(); |
| 114 for (int i = 0; i < matchingApps.size(); i++) { | 121 for (int i = 0; i < matchingApps.size(); i++) { |
| 115 AppInfo appInfo = new AppInfo(); | 122 AppInfo appInfo = new AppInfo(); |
| 116 appInfo.resolveInfo = matchingApps.get(i); | 123 appInfo.resolveInfo = matchingApps.get(i); |
| 117 mMatchingApps.add(appInfo); | 124 mMatchingApps.put(appInfo.resolveInfo.activityInfo.packageName, appI
nfo); |
| 118 } | 125 } |
| 126 |
| 119 mDownloader = downloader; | 127 mDownloader = downloader; |
| 120 mParser = parser; | 128 mParser = parser; |
| 121 mPackageManagerDelegate = packageManagerDelegate; | 129 mPackageManagerDelegate = packageManagerDelegate; |
| 122 mCallback = callback; | 130 mCallback = callback; |
| 123 | 131 |
| 124 MessageDigest md = null; | 132 MessageDigest md = null; |
| 125 try { | 133 try { |
| 126 md = MessageDigest.getInstance("SHA-256"); | 134 md = MessageDigest.getInstance("SHA-256"); |
| 127 } catch (NoSuchAlgorithmException e) { | 135 } catch (NoSuchAlgorithmException e) { |
| 128 // Intentionally ignore. | 136 // Intentionally ignore. |
| 129 Log.d(TAG, "Unable to generate SHA-256 hashes. Only \"package\": \"*
\" supported."); | 137 Log.d(TAG, "Unable to generate SHA-256 hashes."); |
| 130 } | 138 } |
| 131 mMessageDigest = md; | 139 mMessageDigest = md; |
| 132 } | 140 } |
| 133 | 141 |
| 134 /** | 142 /** |
| 135 * Verifies that the discovered native Android payment apps have the suffici
ent | 143 * Verifies that the discovered native Android payment apps have the suffici
ent |
| 136 * privileges to handle this payment method. | 144 * privileges to handle this payment method. |
| 137 */ | 145 */ |
| 138 public void verify() { | 146 public void verify() { |
| 139 mDownloader.download(mMethodName, this); | |
| 140 } | |
| 141 | |
| 142 @Override | |
| 143 public void onManifestDownloadSuccess(String content) { | |
| 144 mParser.parse(content, this); | |
| 145 } | |
| 146 | |
| 147 @Override | |
| 148 public void onManifestDownloadFailure() { | |
| 149 mCallback.onInvalidManifest(mMethodName); | |
| 150 } | |
| 151 | |
| 152 @Override | |
| 153 public void onManifestParseSuccess(PaymentManifestSection[] manifest) { | |
| 154 assert manifest != null; | |
| 155 assert manifest.length > 0; | |
| 156 | |
| 157 for (int i = 0; i < manifest.length; i++) { | |
| 158 PaymentManifestSection section = manifest[i]; | |
| 159 // "package": "*" in the manifest file indicates an unrestricted pay
ment method. Any app | |
| 160 // can use this payment method name. | |
| 161 if ("*".equals(section.packageName)) { | |
| 162 for (int j = 0; j < mMatchingApps.size(); j++) { | |
| 163 mCallback.onValidPaymentApp(mMethodName, mMatchingApps.get(j
).resolveInfo); | |
| 164 } | |
| 165 return; | |
| 166 } | |
| 167 } | |
| 168 | |
| 169 if (mMessageDigest == null) { | 147 if (mMessageDigest == null) { |
| 170 mCallback.onInvalidManifest(mMethodName); | 148 mCallback.onInvalidManifest(mMethodName); |
| 171 return; | 149 return; |
| 172 } | 150 } |
| 173 | 151 |
| 174 for (int i = 0; i < mMatchingApps.size(); i++) { | 152 List<String> invalidAppsToRemove = new ArrayList<>(); |
| 175 AppInfo appInfo = mMatchingApps.get(i); | 153 for (Map.Entry<String, AppInfo> entry : mMatchingApps.entrySet()) { |
| 176 PackageInfo packageInfo = mPackageManagerDelegate.getPackageInfoWith
Signatures( | 154 String packageName = entry.getKey(); |
| 177 appInfo.resolveInfo.activityInfo.packageName); | 155 AppInfo appInfo = entry.getValue(); |
| 178 | 156 |
| 179 // Leaving appInfo.sha256CertFingerprints uninitialized will call on
InvalidPaymentApp() | 157 PackageInfo packageInfo = |
| 180 // for this app below. | 158 mPackageManagerDelegate.getPackageInfoWithSignatures(package
Name); |
| 181 if (packageInfo == null) continue; | 159 if (packageInfo == null) { |
| 160 mCallback.onInvalidPaymentApp(mMethodName, appInfo.resolveInfo); |
| 161 invalidAppsToRemove.add(packageName); |
| 162 continue; |
| 163 } |
| 182 | 164 |
| 183 appInfo.version = packageInfo.versionCode; | 165 appInfo.version = packageInfo.versionCode; |
| 184 appInfo.sha256CertFingerprints = new HashSet<>(); | 166 appInfo.sha256CertFingerprints = new HashSet<>(); |
| 185 Signature[] signatures = packageInfo.signatures; | 167 Signature[] signatures = packageInfo.signatures; |
| 186 for (int j = 0; j < signatures.length; j++) { | 168 for (int i = 0; i < signatures.length; i++) { |
| 187 mMessageDigest.update(signatures[j].toByteArray()); | 169 mMessageDigest.update(signatures[i].toByteArray()); |
| 188 | 170 |
| 189 // The digest is reset after completing the hash computation. | 171 // The digest is reset after completing the hash computation. |
| 190 appInfo.sha256CertFingerprints.add(byteArrayToString(mMessageDig
est.digest())); | 172 appInfo.sha256CertFingerprints.add(byteArrayToString(mMessageDig
est.digest())); |
| 191 } | 173 } |
| 192 } | 174 } |
| 193 | 175 |
| 194 List<Set<String>> sectionsFingerprints = new ArrayList<>(); | 176 for (int i = 0; i < invalidAppsToRemove.size(); i++) { |
| 195 for (int i = 0; i < manifest.length; i++) { | 177 mMatchingApps.remove(invalidAppsToRemove.get(i)); |
| 196 PaymentManifestSection section = manifest[i]; | |
| 197 Set<String> fingerprints = new HashSet<>(); | |
| 198 if (section.sha256CertFingerprints != null) { | |
| 199 for (int j = 0; j < section.sha256CertFingerprints.length; j++)
{ | |
| 200 fingerprints.add(byteArrayToString(section.sha256CertFingerp
rints[j])); | |
| 201 } | |
| 202 } | |
| 203 sectionsFingerprints.add(fingerprints); | |
| 204 } | 178 } |
| 205 | 179 |
| 206 for (int i = 0; i < mMatchingApps.size(); i++) { | 180 if (!mMatchingApps.isEmpty()) mDownloader.downloadPaymentMethodManifest(
mMethodName, this); |
| 207 AppInfo appInfo = mMatchingApps.get(i); | |
| 208 boolean isAllowed = false; | |
| 209 for (int j = 0; j < manifest.length; j++) { | |
| 210 PaymentManifestSection section = manifest[j]; | |
| 211 if (appInfo.resolveInfo.activityInfo.packageName.equals(section.
packageName) | |
| 212 && appInfo.version >= section.version | |
| 213 && appInfo.sha256CertFingerprints != null | |
| 214 && appInfo.sha256CertFingerprints.equals(sectionsFingerp
rints.get(j))) { | |
| 215 mCallback.onValidPaymentApp(mMethodName, appInfo.resolveInfo
); | |
| 216 isAllowed = true; | |
| 217 break; | |
| 218 } | |
| 219 } | |
| 220 if (!isAllowed) mCallback.onInvalidPaymentApp(mMethodName, appInfo.r
esolveInfo); | |
| 221 } | |
| 222 } | 181 } |
| 223 | 182 |
| 224 /** | 183 /** |
| 225 * Formats bytes into a string for easier comparison as a member of a set. | 184 * Formats bytes into a string for easier comparison as a member of a set. |
| 226 * | 185 * |
| 227 * @param input Input bytes. | 186 * @param input Input bytes. |
| 228 * @return A string representation of the input bytes, e.g., "0123456789abcd
ef". | 187 * @return A string representation of the input bytes, e.g., "0123456789abcd
ef". |
| 229 */ | 188 */ |
| 230 private static String byteArrayToString(byte[] input) { | 189 private static String byteArrayToString(byte[] input) { |
| 231 if (input == null) return null; | 190 if (input == null) return null; |
| 232 | 191 |
| 233 StringBuilder builder = new StringBuilder(input.length * 2); | 192 StringBuilder builder = new StringBuilder(input.length * 2); |
| 234 Formatter formatter = new Formatter(builder); | 193 Formatter formatter = new Formatter(builder); |
| 235 for (byte b : input) { | 194 for (byte b : input) { |
| 236 formatter.format("%02x", b); | 195 formatter.format("%02x", b); |
| 237 } | 196 } |
| 238 | 197 |
| 239 return builder.toString(); | 198 return builder.toString(); |
| 240 } | 199 } |
| 241 | 200 |
| 242 @Override | 201 @Override |
| 202 public void onPaymentMethodManifestDownloadSuccess(String content) { |
| 203 mParser.parsePaymentMethodManifest(content, this); |
| 204 } |
| 205 |
| 206 @Override |
| 207 public void onPaymentMethodManifestParseSuccess(URI[] webAppManifestUris) { |
| 208 assert webAppManifestUris != null; |
| 209 assert webAppManifestUris.length > 0; |
| 210 assert !mAtLeastOneManifestFailedToDownloadOrParse; |
| 211 assert mPendingWebAppManifestsCount == 0; |
| 212 |
| 213 mPendingWebAppManifestsCount = webAppManifestUris.length; |
| 214 for (int i = 0; i < webAppManifestUris.length; i++) { |
| 215 mDownloader.downloadWebAppManifest(webAppManifestUris[i], this); |
| 216 } |
| 217 } |
| 218 |
| 219 @Override |
| 220 public void onWebAppManifestDownloadSuccess(String content) { |
| 221 if (mAtLeastOneManifestFailedToDownloadOrParse) return; |
| 222 mParser.parseWebAppManifest(content, this); |
| 223 } |
| 224 |
| 225 @Override |
| 226 public void onWebAppManifestParseSuccess(WebAppManifestSection[] manifest) { |
| 227 assert manifest != null; |
| 228 assert manifest.length > 0; |
| 229 |
| 230 if (mAtLeastOneManifestFailedToDownloadOrParse) return; |
| 231 |
| 232 List<Set<String>> sectionsFingerprints = new ArrayList<>(); |
| 233 for (int i = 0; i < manifest.length; i++) { |
| 234 WebAppManifestSection section = manifest[i]; |
| 235 Set<String> fingerprints = new HashSet<>(); |
| 236 for (int j = 0; j < section.fingerprints.length; j++) { |
| 237 fingerprints.add(byteArrayToString(section.fingerprints[j])); |
| 238 } |
| 239 sectionsFingerprints.add(fingerprints); |
| 240 } |
| 241 |
| 242 for (int i = 0; i < manifest.length; i++) { |
| 243 WebAppManifestSection section = manifest[i]; |
| 244 AppInfo appInfo = mMatchingApps.get(section.id); |
| 245 if (appInfo != null && appInfo.version >= section.minVersion |
| 246 && appInfo.sha256CertFingerprints != null |
| 247 && appInfo.sha256CertFingerprints.equals(sectionsFingerprint
s.get(i))) { |
| 248 mCallback.onValidPaymentApp(mMethodName, appInfo.resolveInfo); |
| 249 mMatchingApps.remove(section.id); |
| 250 break; |
| 251 } |
| 252 } |
| 253 |
| 254 mPendingWebAppManifestsCount--; |
| 255 if (mPendingWebAppManifestsCount == 0) { |
| 256 for (Map.Entry<String, AppInfo> entry : mMatchingApps.entrySet()) { |
| 257 mCallback.onInvalidPaymentApp(mMethodName, entry.getValue().reso
lveInfo); |
| 258 } |
| 259 } |
| 260 } |
| 261 |
| 262 @Override |
| 263 public void onManifestDownloadFailure() { |
| 264 if (mAtLeastOneManifestFailedToDownloadOrParse) return; |
| 265 mAtLeastOneManifestFailedToDownloadOrParse = true; |
| 266 |
| 267 mCallback.onInvalidManifest(mMethodName); |
| 268 } |
| 269 |
| 270 @Override |
| 243 public void onManifestParseFailure() { | 271 public void onManifestParseFailure() { |
| 272 if (mAtLeastOneManifestFailedToDownloadOrParse) return; |
| 273 mAtLeastOneManifestFailedToDownloadOrParse = true; |
| 274 |
| 244 mCallback.onInvalidManifest(mMethodName); | 275 mCallback.onInvalidManifest(mMethodName); |
| 245 } | 276 } |
| 246 } | 277 } |
| OLD | NEW |