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

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

Issue 2645813006: Download web payment manifests. (Closed)
Patch Set: Address more comments 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..d309b322f40bf58f8245750767efca1e57740acd
--- /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.chrome.browser.payments.PaymentAppFactory.PaymentManifestParser;
+import org.chromium.chrome.browser.payments.PaymentManifestDownloader.ManifestDownloadCallback;
+import org.chromium.payments.mojom.NativeAndroidPaymentAppManifestSection;
+import org.chromium.payments.mojom.PaymentRequestClient.ParsePaymentManifestResponse;
+
+import java.net.URL;
+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
+ * 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, ParsePaymentManifestResponse {
+ /** 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(URL 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(URL 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(URL 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,
+ * ["308201dd30820146020101300d06092a864886f70d01010505003037311630140"]. Order does not
+ * matter for comparison.
+ */
+ public Set<String> sha256CertFingerprints;
+ }
+
+ private final PackageManager mPackageManager;
+ private final URL mMethodName;
+ private final List<AppInfo> mMatchingApps;
+ private final PaymentManifestDownloader mDownloader;
+ private final PaymentManifestParser mParser;
+ private final ManifestVerifyCallback mCallback;
+ private final MessageDigest mMessageDigest;
+
+ /**
+ * Builds the manifest verifier.
+ *
+ * @param pm The package manager to query for signatures.
+ * @param methodName The name of the payment method name that apps offer to handle. Must be
+ * a valid URL that starts with "https://".
+ * @param matchingApps The identifying information for the native Android payment apps that
+ * offer to handle this payment method.
+ * @param downloader The payment manifest downloader.
+ * @param parser The payment manifest parser.
+ * @param callback The callback to be notified of verification result.
+ */
+ public PaymentManifestVerifier(PackageManager pm, URL methodName,
+ List<ResolveInfo> matchingApps, PaymentManifestDownloader downloader,
+ PaymentManifestParser parser, ManifestVerifyCallback callback) {
+ mPackageManager = pm;
+ 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);
+ }
+ mDownloader = downloader;
+ 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.parsePaymentManifest(content, this);
+ }
+
+ @Override
+ public void onManifestDownloadFailure() {
+ mCallback.onInvalidManifest(mMethodName);
+ }
+
+ /** Called after parsing payment manifest content. */
+ @Override
+ public void call(NativeAndroidPaymentAppManifestSection[] sections) {
+ if (sections == null || sections.length == 0) {
+ mCallback.onInvalidManifest(mMethodName);
+ return;
+ }
+
+ for (int i = 0; i < sections.length; i++) {
+ NativeAndroidPaymentAppManifestSection section = sections[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 =
+ mPackageManager.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 < sections.length; i++) {
+ NativeAndroidPaymentAppManifestSection section = sections[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 < sections.length; j++) {
+ NativeAndroidPaymentAppManifestSection section = sections[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);
+ }
+ }
+
+ /**
+ * 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