Chromium Code Reviews| 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..bb06112596396d834c2eb19920528ab1dc714351 |
| --- /dev/null |
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/OriginVerifier.java |
| @@ -0,0 +1,167 @@ |
| +// 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.VisibleForTesting; |
| +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.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 |
|
Benoit L
2017/04/07 15:04:54
nit: Add a comment about caching?
Yusuf
2017/04/07 23:38:41
Done.
|
| + * a postMessage origin. |
| + */ |
| +@JNINamespace("customtabs") |
| +class OriginVerifier { |
| + private static final String TAG = "OriginVerifier"; |
| + private static final char[] HEX_CHAR_LOOKUP = "0123456789ABCDEF".toCharArray(); |
| + 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; |
| + |
| + /** |
| + * To be used for prepopulating verified origin for testing functionality. |
| + * @param packageName The package name to prepopulate for. |
| + * @param origin The origin to add as verified. |
| + */ |
| + @VisibleForTesting |
| + static void prePopulateVerifiedOriginForTesting(String packageName, Uri origin) { |
| + cacheVerifiedOriginIfNeeded(packageName, origin); |
| + } |
| + |
| + 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; |
| + 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(); |
| + if (mNativeOriginVerifier == 0) return; |
| + 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) { |
| + return null; |
| + } |
| + InputStream input = new ByteArrayInputStream(packageInfo.signatures[0].toByteArray()); |
| + X509Certificate certificate = null; |
| + String hexString = null; |
| + try { |
| + certificate = |
| + (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate( |
| + input); |
| + hexString = byteArrayToHexString( |
| + MessageDigest.getInstance("SHA256").digest(certificate.getEncoded())); |
| + } catch (CertificateException | NoSuchAlgorithmException e) { |
| + if (e instanceof CertificateEncodingException) { |
|
Benoit L
2017/04/07 15:04:54
You can put multiple catch blocks, provided that t
Yusuf
2017/04/07 23:38:41
Done.
|
| + Log.w(TAG, "Certificate type X509 encoding failed"); |
| + } |
| + return null; |
| + } |
| + return hexString; |
| + } |
| + |
| + private static String byteArrayToHexString(byte[] byteArray) { |
| + StringBuilder hexString = new StringBuilder(byteArray.length * 2); |
|
Benoit L
2017/04/07 15:04:54
This length computation doesn't account for the de
Yusuf
2017/04/07 23:38:41
Done.
|
| + for (int i = 0; i < byteArray.length; ++i) { |
| + hexString.append(HEX_CHAR_LOOKUP[(byteArray[i] & 0xf0) >>> 4]); |
| + hexString.append(HEX_CHAR_LOOKUP[byteArray[i] & 0xf]); |
| + if (i < (byteArray.length - 1)) hexString.append(':'); |
| + } |
| + return hexString.toString(); |
| + } |
| + |
| + @CalledByNative |
| + private void originVerified(boolean originVerified) { |
| + if (originVerified) { |
| + cacheVerifiedOriginIfNeeded(mPackageName, mOrigin); |
| + mOrigin = getPostMessageOriginFromVerifiedOrigin(mPackageName, mOrigin); |
| + } |
| + mListener.onOriginVerified(mPackageName, mOrigin, originVerified); |
| + cleanUp(); |
| + } |
| + |
| + private native long nativeInit(); |
| + private native void nativeVerifyOrigin(long nativeOriginVerifier, String packageName, |
| + String signatureFingerprint, String origin); |
| + private native void nativeDestroy(long nativeOriginVerifier); |
| +} |