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..ebcdd523d875458f66dc68b5bf1be8aeeae423cc |
--- /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 |
+ * a postMessage origin. It caches any origin that has been verified during the current application |
+ * lifecycle and reuses that without making any new network requests. |
+ */ |
+@JNINamespace("customtabs") |
+class OriginVerifier { |
nyquist
2017/04/19 06:35:55
What is the expected lifetime of this object? It d
Yusuf
2017/04/26 00:51:35
Made cleanup package protected and started calling
|
+ 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; |
nyquist
2017/04/19 06:35:55
Nit: Could you move this to after the final fields
Yusuf
2017/04/26 00:51:35
Done.
|
+ 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() { |
nyquist
2017/04/19 06:35:55
Nit: This is non-static. Any particular reason tha
Yusuf
2017/04/26 00:51:35
Nope. this was static before I added the mPackageN
|
+ if (sCachedOriginMap == null) return null; |
+ return sCachedOriginMap.get(mPackageName); |
+ } |
+ |
+ public interface OriginVerificationListener { |
+ void onOriginVerified(String packageName, Uri origin, boolean result); |
nyquist
2017/04/19 06:35:55
When is this called? Is this guaranteed to be post
Yusuf
2017/04/26 00:51:35
Done.
|
+ } |
+ |
+ /** |
+ * 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. |
nyquist
2017/04/19 06:35:55
Nit: Could you mention that this will end up doing
Yusuf
2017/04/26 00:51:35
Done.
|
+ * @param origin The postMessage origin the application is claiming to have. Can't be null |
nyquist
2017/04/19 06:35:56
Nit: Missing period at the end.
Yusuf
2017/04/26 00:51:35
Done.
|
+ */ |
+ public void start(@NonNull Uri origin) { |
nyquist
2017/04/19 06:35:55
Should this only be called from the main thread? I
Yusuf
2017/04/26 00:51:35
Yes, it should be on main thread only (Actually I
nyquist
2017/04/27 04:38:24
Acknowledged.
|
+ mOrigin = origin; |
+ |
+ // If this origin is cached as verified already, use that. |
+ Uri cachedOrigin = getCachedOriginIfExists(); |
+ if (cachedOrigin != null && cachedOrigin.equals(origin)) { |
+ originVerified(true); |
nyquist
2017/04/19 06:35:56
This looks like it's re-entrant. Should this be po
Yusuf
2017/04/26 00:51:35
I posted back on the UI thread. But the native sid
nyquist
2017/04/27 04:38:24
I think you've fixed the native side too?
|
+ return; |
+ } |
+ |
+ if (mNativeOriginVerifier != 0) cleanUp(); |
+ mNativeOriginVerifier = nativeInit(); |
+ if (mNativeOriginVerifier == 0) return; |
nyquist
2017/04/19 06:35:55
This ends up not invoking the callback / listener
Yusuf
2017/04/26 00:51:35
Yes, this is for non-integration tests to still cr
nyquist
2017/04/27 04:38:24
Acknowledged.
|
+ nativeVerifyOrigin( |
+ mNativeOriginVerifier, mPackageName, mSignatureFingerprint, mOrigin.toString()); |
+ } |
+ |
+ private void cleanUp() { |
+ nativeDestroy(mNativeOriginVerifier); |
+ mNativeOriginVerifier = 0; |
+ } |
+ |
+ private String getCertificateSHA256FingerprintForPackage(String packageName) { |
nyquist
2017/04/19 06:35:56
Nit: What does this method return? I.e. what's the
Yusuf
2017/04/26 00:51:35
The certificate for the apk in hex format:
Someth
nyquist
2017/04/27 04:38:24
What you did is fine.
|
+ PackageManager pm = ContextUtils.getApplicationContext().getPackageManager(); |
nyquist
2017/04/19 06:35:55
Nit: Could you extract the package stuff into a pr
Yusuf
2017/04/26 00:51:35
Done.
|
+ 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()); |
nyquist
2017/04/19 06:35:55
Could you extract out a method that takes packageI
Yusuf
2017/04/26 00:51:35
See below, the one below already takes byte[]. Mak
nyquist
2017/04/27 04:38:24
Acknowledged.
|
+ X509Certificate certificate = null; |
+ String hexString = null; |
+ try { |
+ certificate = |
+ (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate( |
nyquist
2017/04/19 06:35:56
Do you think it would be helpful for someone from
Yusuf
2017/04/26 00:51:35
I can do that after you are comfortable for the st
nyquist
2017/04/27 04:38:24
Acknowledged.
|
+ input); |
+ hexString = byteArrayToHexString( |
+ MessageDigest.getInstance("SHA256").digest(certificate.getEncoded())); |
+ } catch (CertificateEncodingException e) { |
+ Log.w(TAG, "Certificate type X509 encoding failed"); |
+ } catch (CertificateException | NoSuchAlgorithmException e) { |
+ // This shouldn't happen. |
+ } |
+ return hexString; |
+ } |
+ |
+ private static String byteArrayToHexString(byte[] byteArray) { |
nyquist
2017/04/19 06:35:55
What do you think about making this package protec
Yusuf
2017/04/26 00:51:35
Done.
|
+ StringBuilder hexString = new StringBuilder(byteArray.length * 3 - 1); |
+ 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); |
+} |