| Index: chrome/android/java/src/org/chromium/chrome/browser/customtabs/OriginVerifier.java
|
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/OriginVerifier.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/OriginVerifier.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..cca9f2d204baf24efea07ec7bed270c221b31b82
|
| --- /dev/null
|
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/OriginVerifier.java
|
| @@ -0,0 +1,162 @@
|
| +// 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.customtabs;
|
| +
|
| +import android.content.pm.PackageInfo;
|
| +import android.content.pm.PackageManager;
|
| +import android.net.Uri;
|
| +import android.support.annotation.NonNull;
|
| +
|
| +import org.chromium.base.ContextUtils;
|
| +import org.chromium.base.Log;
|
| +import org.chromium.base.annotations.CalledByNative;
|
| +import org.chromium.base.annotations.JNINamespace;
|
| +import org.chromium.chrome.browser.IntentHandler;
|
| +
|
| +import java.io.ByteArrayInputStream;
|
| +import java.io.InputStream;
|
| +import java.security.MessageDigest;
|
| +import java.security.NoSuchAlgorithmException;
|
| +import java.security.cert.CertificateEncodingException;
|
| +import java.security.cert.CertificateException;
|
| +import java.security.cert.CertificateFactory;
|
| +import java.security.cert.X509Certificate;
|
| +import java.util.HashMap;
|
| +import java.util.Locale;
|
| +import java.util.Map;
|
| +
|
| +/**
|
| + * Used to verify postMessage origin for a designated package name.
|
| + *
|
| + * Uses Digital Asset Links to confirm that the given origin is associated with the package name as
|
| + * a postMessage origin.
|
| + */
|
| +@JNINamespace("customtabs")
|
| +class OriginVerifier {
|
| + private static final String TAG = "OriginVerifier";
|
| + private static Map<String, Uri> sCachedOriginMap;
|
| + private long mNativeOriginVerifier = 0;
|
| + private final OriginVerificationListener mListener;
|
| + private final String mPackageName;
|
| + private final String mSignatureFingerprint;
|
| + private Uri mOrigin;
|
| +
|
| + private static Uri getPostMessageOriginFromVerifiedOrigin(
|
| + String packageName, Uri verifiedOrigin) {
|
| + return Uri.parse(IntentHandler.ANDROID_APP_REFERRER_SCHEME + "://"
|
| + + verifiedOrigin.getHost() + packageName);
|
| + }
|
| +
|
| + private static void cacheVerifiedOriginIfNeeded(String packageName, Uri origin) {
|
| + if (sCachedOriginMap == null) sCachedOriginMap = new HashMap<>();
|
| + if (!sCachedOriginMap.containsKey(packageName)) {
|
| + sCachedOriginMap.put(packageName, origin);
|
| + }
|
| + }
|
| +
|
| + private Uri getCachedOriginIfExists() {
|
| + if (sCachedOriginMap == null) return null;
|
| + if (!sCachedOriginMap.containsKey(mPackageName)) return null;
|
| + return sCachedOriginMap.get(mPackageName);
|
| + }
|
| +
|
| + public interface OriginVerificationListener {
|
| + void onOriginVerified(String packageName, Uri origin, boolean result);
|
| + }
|
| +
|
| + /**
|
| + * Main constructor.
|
| + * Use {@link OriginVerifier#start(Uri)}
|
| + * @param listener The listener who will get the verification result.
|
| + * @param packageName The package for the Android application for verification.
|
| + */
|
| + public OriginVerifier(OriginVerificationListener listener, String packageName) {
|
| + mListener = listener;
|
| + mPackageName = packageName;
|
| + mSignatureFingerprint = getCertificateSHA256FingerprintForPackage(mPackageName);
|
| + }
|
| +
|
| + /**
|
| + * Verify the claimed origin for the cached package name asynchronously.
|
| + * @param origin The postMessage origin the application is claiming to have. Can't be null
|
| + */
|
| + public void start(@NonNull Uri origin) {
|
| + mOrigin = origin;
|
| +
|
| + // If this origin is cached as verified already, use that.
|
| + Uri cachedOrigin = getCachedOriginIfExists();
|
| + if (cachedOrigin != null && cachedOrigin.equals(origin)) {
|
| + originVerified(true);
|
| + return;
|
| + }
|
| +
|
| + if (mNativeOriginVerifier != 0) cleanUp();
|
| + mNativeOriginVerifier = nativeInit();
|
| + assert mNativeOriginVerifier != 0;
|
| + nativeVerifyOrigin(
|
| + mNativeOriginVerifier, mPackageName, mSignatureFingerprint, mOrigin.toString());
|
| + }
|
| +
|
| + private void cleanUp() {
|
| + nativeDestroy(mNativeOriginVerifier);
|
| + mNativeOriginVerifier = 0;
|
| + }
|
| +
|
| + private String getCertificateSHA256FingerprintForPackage(String packageName) {
|
| + PackageManager pm = ContextUtils.getApplicationContext().getPackageManager();
|
| + PackageInfo packageInfo = null;
|
| + try {
|
| + packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
|
| + } catch (PackageManager.NameNotFoundException e) {
|
| + Log.w(TAG, "Given package name was not found in installed packages");
|
| + return "";
|
| + }
|
| + InputStream input = new ByteArrayInputStream(packageInfo.signatures[0].toByteArray());
|
| + X509Certificate certificate = null;
|
| + try {
|
| + certificate =
|
| + (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(
|
| + input);
|
| + } catch (CertificateException e) {
|
| + Log.w(TAG, "Certificate type X509 is not available");
|
| + return "";
|
| + }
|
| + String hexString = null;
|
| + try {
|
| + hexString = byteArrayToHexString(
|
| + MessageDigest.getInstance("SHA256").digest(certificate.getEncoded()));
|
| + } catch (NoSuchAlgorithmException e1) {
|
| + Log.w(TAG, "SHA256 is not available");
|
| + } catch (CertificateEncodingException e) {
|
| + Log.w(TAG, "Certificate type X509 encoding failed");
|
| + }
|
| + return hexString;
|
| + }
|
| +
|
| + private static String byteArrayToHexString(byte[] byteArray) {
|
| + StringBuilder hexString = new StringBuilder(byteArray.length * 2);
|
| + for (int i = 0; i < byteArray.length; i++) {
|
| + String hex = Integer.toHexString(byteArray[i]);
|
| + int length = hex.length();
|
| + hex = (length == 1) ? "0" + hex
|
| + : (length > 2) ? hex.substring(length - 2, length) : hex;
|
| + hexString.append(hex.toUpperCase(Locale.getDefault()));
|
| + if (i < (byteArray.length - 1)) hexString.append(':');
|
| + }
|
| + return hexString.toString();
|
| + }
|
| +
|
| + @CalledByNative
|
| + private void originVerified(boolean originVerified) {
|
| + mListener.onOriginVerified(mPackageName, mOrigin, originVerified);
|
| + if (originVerified) cacheVerifiedOriginIfNeeded(mPackageName, mOrigin);
|
| + cleanUp();
|
| + }
|
| +
|
| + private native long nativeInit();
|
| + private native void nativeVerifyOrigin(long nativeOriginVerifier, String packageName,
|
| + String signatureFingerprint, String origin);
|
| + private native void nativeDestroy(long nativeOriginVerifier);
|
| +}
|
|
|