Chromium Code Reviews| 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); |
| + } |
| +} |