Index: chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentManifestVerifier.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentManifestVerifier.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentManifestVerifier.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..cb56fef0675de5abc52f7fd96e718f27d1311992 |
--- /dev/null |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentManifestVerifier.java |
@@ -0,0 +1,236 @@ |
+// Copyright 2017 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.chrome.browser.payments; |
+ |
+import android.content.pm.PackageInfo; |
+import android.content.pm.PackageManager; |
+import android.content.pm.PackageManager.NameNotFoundException; |
+import android.content.pm.ResolveInfo; |
+import android.content.pm.Signature; |
+ |
+import org.chromium.base.ContextUtils; |
+import org.chromium.chrome.browser.payments.PaymentManifestDownloader.ManifestDownloadCallback; |
+import org.chromium.chrome.browser.payments.PaymentManifestParser.ManifestParseCallback; |
+import org.chromium.content_public.browser.WebContents; |
+import org.chromium.payments.mojom.PaymentManifestSection; |
+ |
+import java.net.URI; |
+import java.security.MessageDigest; |
+import java.security.NoSuchAlgorithmException; |
+import java.util.ArrayList; |
+import java.util.Formatter; |
+import java.util.HashSet; |
+import java.util.List; |
+import java.util.Set; |
+ |
+/** |
+ * Verifies that the discovered native Android payment apps have the sufficient privileges |
Mathieu
2017/03/03 19:42:09
Is there a public doc we can link to for this mani
please use gerrit instead
2017/03/09 18:05:33
Linking.
|
+ * to handle a single payment method. Downloads and parses the manifest to compare package |
+ * names, versions, and signatures to the apps. |
+ */ |
+public class PaymentManifestVerifier implements ManifestDownloadCallback, ManifestParseCallback { |
+ /** Interface for the callback to invoke when finished verification. */ |
+ public interface ManifestVerifyCallback { |
+ /** |
+ * Enables invoking the given native Android payment app for the given payment method. |
+ * Called when the app has been found to have the right privileges to handle this payment |
+ * method. |
+ * |
+ * @param methodName The payment method name that the payment app offers to handle. |
+ * @param resolveInfo Identifying information for the native Android payment app. |
+ */ |
+ void onValidPaymentApp(URI methodName, ResolveInfo resolveInfo); |
+ |
+ /** |
+ * Disables invoking the given native Android payment app for the given payment method. |
+ * Called when the app has been found to not have the right privileges to handle this |
+ * payment app. |
+ * |
+ * @param methodName The payment method name that the payment app offers to handle. |
+ * @param resolveInfo Identifying information for the native Android payment app. |
+ */ |
+ void onInvalidPaymentApp(URI methodName, ResolveInfo resolveInfo); |
+ |
+ /** |
+ * Disables invoking any native Android payment app for the given payment method. Called if |
+ * unable to download or parse the payment method manifest. |
+ * |
+ * @param methodName The payment method name that has an invalid payment method manifest. |
+ */ |
+ void onInvalidManifest(URI methodName); |
+ } |
+ |
+ /** Identifying information about an installed native Android payment app. */ |
+ private static class AppInfo { |
+ /** Identifies a native Android payment app. */ |
+ public ResolveInfo resolveInfo; |
+ |
+ /** The version code for the native Android payment app, e.g., 123. */ |
+ public long version; |
+ |
+ /** |
+ * The SHA256 certificate fingerprints for the native Android payment app, .e.g, |
+ * ["30:82:01:dd:30:82:01:46:02:01:01:30:0d:06:09:2a:86:48:86:f7:0d:01:01:05:05:00:30"]. |
+ * Order does not matter for comparison. |
+ */ |
+ public Set<String> sha256CertFingerprints; |
+ } |
+ |
+ private final PaymentManifestDownloader mDownloader; |
+ private final URI mMethodName; |
+ private final List<AppInfo> mMatchingApps; |
+ private final PaymentManifestParser mParser; |
+ private final ManifestVerifyCallback mCallback; |
+ private final MessageDigest mMessageDigest; |
+ |
+ /** |
+ * Builds the manifest verifier. |
+ * |
+ * @param webContents The web contents. |
+ * @param methodName The name of the payment method name that apps offer to handle. Must be |
+ * a valid URI that starts with "https://". |
+ * @param matchingApps The identifying information for the native Android payment apps that |
+ * offer to handle this payment method. |
+ * @param parser The manifest parser. |
+ * @param callback The callback to be notified of verification result. |
+ */ |
+ public PaymentManifestVerifier(WebContents webContents, URI methodName, |
+ List<ResolveInfo> matchingApps, PaymentManifestParser parser, |
+ ManifestVerifyCallback callback) { |
+ mDownloader = new PaymentManifestDownloader(webContents); |
+ mMethodName = methodName; |
+ mMatchingApps = new ArrayList<>(); |
+ for (int i = 0; i < matchingApps.size(); i++) { |
+ AppInfo appInfo = new AppInfo(); |
+ appInfo.resolveInfo = matchingApps.get(i); |
+ mMatchingApps.add(appInfo); |
+ } |
+ mParser = parser; |
+ mCallback = callback; |
+ |
+ MessageDigest md = null; |
+ try { |
+ md = MessageDigest.getInstance("SHA-256"); |
+ } catch (NoSuchAlgorithmException e) { |
+ // Intentionally ignore. |
+ } |
+ mMessageDigest = md; |
+ } |
+ |
+ /** |
+ * Verifies that the discovered native Android payment apps have the sufficient |
+ * privileges to handle this payment method. |
+ */ |
+ public void verify() { |
+ mDownloader.download(mMethodName, this); |
+ } |
+ |
+ @Override |
+ public void onManifestDownloadSuccess(String content) { |
+ mParser.parse(content, this); |
+ } |
+ |
+ @Override |
+ public void onManifestDownloadFailure() { |
+ mCallback.onInvalidManifest(mMethodName); |
+ } |
+ |
+ @Override |
+ public void onManifestParseSuccess(PaymentManifestSection[] manifest) { |
+ assert manifest != null; |
+ assert manifest.length > 0; |
+ |
+ for (int i = 0; i < manifest.length; i++) { |
+ PaymentManifestSection section = manifest[i]; |
+ // "package": "*" in the manifest file indicates an unrestricted payment method. Any app |
+ // can use this payment method name. |
+ if ("*".equals(section.packageName)) { |
+ for (int j = 0; j < mMatchingApps.size(); j++) { |
+ mCallback.onValidPaymentApp(mMethodName, mMatchingApps.get(j).resolveInfo); |
+ } |
+ return; |
+ } |
+ } |
+ |
+ if (mMessageDigest == null) { |
+ mCallback.onInvalidManifest(mMethodName); |
+ return; |
+ } |
+ |
+ for (int i = 0; i < mMatchingApps.size(); i++) { |
+ AppInfo appInfo = mMatchingApps.get(i); |
+ try { |
+ PackageInfo packageInfo = |
+ ContextUtils.getApplicationContext().getPackageManager().getPackageInfo( |
+ appInfo.resolveInfo.activityInfo.packageName, |
+ PackageManager.GET_SIGNATURES); |
+ appInfo.version = packageInfo.versionCode; |
+ appInfo.sha256CertFingerprints = new HashSet<>(); |
+ Signature[] signatures = packageInfo.signatures; |
+ for (int j = 0; j < signatures.length; j++) { |
+ mMessageDigest.update(signatures[j].toByteArray()); |
+ |
+ // The digest is reset after completing the hash computation. |
+ appInfo.sha256CertFingerprints.add(formatFingerprint(mMessageDigest.digest())); |
+ } |
+ } catch (NameNotFoundException e) { |
+ // Leaving appInfo.sha256CertFingerprints uninitialized will call |
+ // onInvalidApp() for this app below. |
+ } |
+ } |
+ |
+ List<Set<String>> sectionsFingerprints = new ArrayList<>(); |
+ for (int i = 0; i < manifest.length; i++) { |
+ PaymentManifestSection section = manifest[i]; |
+ Set<String> fingerprints = new HashSet<>(); |
+ if (section.sha256CertFingerprints != null) { |
+ for (int j = 0; j < section.sha256CertFingerprints.length; j++) { |
+ fingerprints.add(section.sha256CertFingerprints[j]); |
+ } |
+ } |
+ sectionsFingerprints.add(fingerprints); |
+ } |
+ |
+ for (int i = 0; i < mMatchingApps.size(); i++) { |
+ AppInfo appInfo = mMatchingApps.get(i); |
+ boolean isAllowed = false; |
+ for (int j = 0; j < manifest.length; j++) { |
+ PaymentManifestSection section = manifest[j]; |
+ if (appInfo.resolveInfo.activityInfo.packageName.equals(section.packageName) |
+ && appInfo.version >= section.version |
+ && appInfo.sha256CertFingerprints != null |
+ && appInfo.sha256CertFingerprints.equals(sectionsFingerprints.get(j))) { |
+ mCallback.onValidPaymentApp(mMethodName, appInfo.resolveInfo); |
+ isAllowed = true; |
+ break; |
+ } |
+ } |
+ if (!isAllowed) mCallback.onInvalidPaymentApp(mMethodName, appInfo.resolveInfo); |
+ } |
+ } |
+ |
+ @Override |
+ public void onManifestParseFailure() { |
+ mCallback.onInvalidManifest(mMethodName); |
+ } |
+ |
+ /** |
+ * Formats bytes into a string. |
+ * |
+ * @param input Input bytes. Should not be null. |
+ * @return A string representation of the input bytes, e.g., "01:23:45:67:89:AB:CD:EF". |
+ */ |
+ private static String formatFingerprint(byte[] input) { |
+ StringBuilder builder = new StringBuilder(input.length * 3); |
+ Formatter formatter = new Formatter(builder); |
+ |
+ for (byte b : input) { |
+ formatter.format(":%02X", b); |
+ } |
+ |
+ // Cut off the first ":". |
+ return builder.substring(1); |
+ } |
+} |