Index: android_webview/java/src/org/chromium/android_webview/AwContentsClientBridge.java |
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContentsClientBridge.java b/android_webview/java/src/org/chromium/android_webview/AwContentsClientBridge.java |
index 66e4f1ec9522047bf983c2461f4f653f47352712..6ebfe8a2e48e77d2feca1935ec0e0452e3927968 100644 |
--- a/android_webview/java/src/org/chromium/android_webview/AwContentsClientBridge.java |
+++ b/android_webview/java/src/org/chromium/android_webview/AwContentsClientBridge.java |
@@ -6,10 +6,21 @@ package org.chromium.android_webview; |
import android.net.http.SslCertificate; |
import android.net.http.SslError; |
+import android.util.Log; |
import android.webkit.ValueCallback; |
import org.chromium.base.CalledByNative; |
import org.chromium.base.JNINamespace; |
+import org.chromium.base.ThreadUtils; |
+import org.chromium.net.AndroidPrivateKey; |
+import org.chromium.net.DefaultAndroidKeyStore; |
+ |
+import java.security.Principal; |
+import java.security.PrivateKey; |
+import java.security.cert.CertificateEncodingException; |
+import java.security.cert.X509Certificate; |
+ |
+import javax.security.auth.x500.X500Principal; |
/** |
* This class handles the JNI communication logic for the the AwContentsClient class. |
@@ -20,14 +31,99 @@ import org.chromium.base.JNINamespace; |
*/ |
@JNINamespace("android_webview") |
public class AwContentsClientBridge { |
+ static final String TAG = "AwContentsClientBridge"; |
private AwContentsClient mClient; |
// The native peer of this object. |
private long mNativeContentsClientBridge; |
- public AwContentsClientBridge(AwContentsClient client) { |
+ private DefaultAndroidKeyStore mLocalKeyStore; |
+ |
+ private ClientCertLookupTable mLookupTable; |
+ |
+ // Used for mocking this class in tests. |
+ protected AwContentsClientBridge(DefaultAndroidKeyStore keyStore, |
+ ClientCertLookupTable table) { |
+ mLocalKeyStore = keyStore; |
+ mLookupTable = table; |
+ } |
+ |
+ public AwContentsClientBridge(AwContentsClient client, DefaultAndroidKeyStore keyStore, |
+ ClientCertLookupTable table) { |
assert client != null; |
mClient = client; |
+ mLocalKeyStore = keyStore; |
+ mLookupTable = table; |
+ } |
+ |
+ /** |
+ * Callback to communicate clientcertificaterequest back to the AwContentsClientBridge. |
+ * The public methods should be called on UI thread. |
+ * A request can not be proceeded, ignored or canceled more than once. Doing this |
+ * is a programming error and causes an exception. |
+ */ |
+ public class ClientCertificateRequestCallback { |
+ |
+ private int mId; |
+ private String mHost; |
+ private int mPort; |
+ private boolean mIsCalled; |
+ |
+ public ClientCertificateRequestCallback(int id, String host, int port) { |
+ mId = id; |
+ mHost = host; |
+ mPort = port; |
+ } |
+ |
+ public void proceed(PrivateKey privateKey, X509Certificate[] chain) { |
+ ThreadUtils.assertOnUiThread(); |
+ checkIfCalled(); |
+ |
+ AndroidPrivateKey key = mLocalKeyStore.createKey(privateKey); |
+ |
+ if (key == null || chain == null || chain.length == 0) { |
+ Log.w(TAG, "Empty client certificate chain?"); |
+ provideResponse(null, null); |
+ return; |
+ } |
+ // Encode the certificate chain. |
+ byte[][] encodedChain = new byte[chain.length][]; |
+ try { |
+ for (int i = 0; i < chain.length; ++i) { |
+ encodedChain[i] = chain[i].getEncoded(); |
+ } |
+ } catch (CertificateEncodingException e) { |
+ Log.w(TAG, "Could not retrieve encoded certificate chain: " + e); |
+ provideResponse(null, null); |
+ return; |
+ } |
+ mLookupTable.allow(mHost, mPort, key, encodedChain); |
+ provideResponse(key, encodedChain); |
+ } |
+ |
+ public void ignore() { |
+ ThreadUtils.assertOnUiThread(); |
+ checkIfCalled(); |
+ provideResponse(null, null); |
+ } |
+ |
+ public void cancel() { |
+ ThreadUtils.assertOnUiThread(); |
+ mLookupTable.deny(mHost, mPort); |
+ provideResponse(null, null); |
+ } |
+ |
+ public void checkIfCalled() { |
+ if (mIsCalled) { |
+ throw new IllegalStateException("The callback was already called."); |
+ } |
+ mIsCalled = true; |
+ } |
+ |
+ private void provideResponse(AndroidPrivateKey androidKey, byte[][] certChain) { |
+ nativeProvideClientCertificateResponse(mNativeContentsClientBridge, mId, |
+ certChain, androidKey); |
+ } |
} |
// Used by the native peer to set/reset a weak ref to the native peer. |
@@ -66,6 +162,43 @@ public class AwContentsClientBridge { |
nativeProceedSslError(mNativeContentsClientBridge, proceed, id); |
} |
+ // Intentionally not private for testing the native peer of this class. |
+ @CalledByNative |
+ protected void selectClientCertificate(final int id, final String[] keyTypes, |
+ byte[][] encodedPrincipals, final String host, final int port) { |
+ ClientCertLookupTable.Cert cert = mLookupTable.getCertData(host, port); |
+ if (mLookupTable.isDenied(host, port)) { |
+ nativeProvideClientCertificateResponse(mNativeContentsClientBridge, id, |
+ null, null); |
+ return; |
+ } |
+ if (cert != null) { |
+ nativeProvideClientCertificateResponse(mNativeContentsClientBridge, id, |
+ cert.certChain, cert.privateKey); |
+ return; |
+ } |
+ // Build the list of principals from encoded versions. |
+ Principal[] principals = null; |
+ if (encodedPrincipals.length > 0) { |
+ principals = new X500Principal[encodedPrincipals.length]; |
+ for (int n = 0; n < encodedPrincipals.length; n++) { |
+ try { |
+ principals[n] = new X500Principal(encodedPrincipals[n]); |
+ } catch (IllegalArgumentException e) { |
+ Log.w(TAG, "Exception while decoding issuers list: " + e); |
+ nativeProvideClientCertificateResponse(mNativeContentsClientBridge, id, |
+ null, null); |
+ return; |
+ } |
+ } |
+ |
+ } |
+ |
+ final ClientCertificateRequestCallback callback = |
+ new ClientCertificateRequestCallback(id, host, port); |
+ mClient.onReceivedClientCertRequest(callback, keyTypes, principals, host, port); |
+ } |
+ |
@CalledByNative |
private void handleJsAlert(String url, String message, int id) { |
JsResultHandler handler = new JsResultHandler(this, id); |
@@ -110,6 +243,8 @@ public class AwContentsClientBridge { |
//-------------------------------------------------------------------------------------------- |
private native void nativeProceedSslError(long nativeAwContentsClientBridge, boolean proceed, |
int id); |
+ private native void nativeProvideClientCertificateResponse(long nativeAwContentsClientBridge, |
+ int id, byte[][] certChain, AndroidPrivateKey androidKey); |
private native void nativeConfirmJsResult(long nativeAwContentsClientBridge, int id, |
String prompt); |