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

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentManifestVerifier.java

Issue 2645813006: Download web payment manifests. (Closed)
Patch Set: At most INT_MAX sections in manifest and fingperints in section Created 3 years, 10 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 side-by-side diff with in-line comments
Download patch
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);
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698