1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.chromium.android_webview;
7 import android.net.http.SslCertificate;
8 import android.net.http.SslError;
9 import android.util.Log;
10 import android.webkit.ValueCallback;
12 import org.chromium.base.CalledByNative;
13 import org.chromium.base.JNINamespace;
14 import org.chromium.base.ThreadUtils;
15 import org.chromium.net.AndroidPrivateKey;
16 import org.chromium.net.DefaultAndroidKeyStore;
18 import java.security.Principal;
19 import java.security.PrivateKey;
20 import java.security.cert.CertificateEncodingException;
21 import java.security.cert.X509Certificate;
23 import javax.security.auth.x500.X500Principal;
26 * This class handles the JNI communication logic for the the AwContentsClient class.
27 * Both the Java and the native peers of AwContentsClientBridge are owned by the
28 * corresponding AwContents instances. This class and its native peer are connected
29 * via weak references. The native AwContentsClientBridge sets up and clear these weak
32 @JNINamespace("android_webview")
33 public class AwContentsClientBridge {
34 static final String TAG = "AwContentsClientBridge";
36 private AwContentsClient mClient;
37 // The native peer of this object.
38 private long mNativeContentsClientBridge;
40 private DefaultAndroidKeyStore mLocalKeyStore;
42 private ClientCertLookupTable mLookupTable;
44 // Used for mocking this class in tests.
45 protected AwContentsClientBridge(DefaultAndroidKeyStore keyStore,
46 ClientCertLookupTable table) {
47 mLocalKeyStore = keyStore;
51 public AwContentsClientBridge(AwContentsClient client, DefaultAndroidKeyStore keyStore,
52 ClientCertLookupTable table) {
53 assert client != null;
55 mLocalKeyStore = keyStore;
60 * Callback to communicate clientcertificaterequest back to the AwContentsClientBridge.
61 * The public methods should be called on UI thread.
62 * A request can not be proceeded, ignored or canceled more than once. Doing this
63 * is a programming error and causes an exception.
65 public class ClientCertificateRequestCallback {
70 private boolean mIsCalled;
72 public ClientCertificateRequestCallback(int id, String host, int port) {
78 public void proceed(final PrivateKey privateKey, final X509Certificate[] chain) {
79 ThreadUtils.runOnUiThread(new Runnable() {
82 proceedOnUiThread(privateKey, chain);
87 public void ignore() {
88 ThreadUtils.runOnUiThread(new Runnable() {
96 public void cancel() {
97 ThreadUtils.runOnUiThread(new Runnable() {
106 private void proceedOnUiThread(PrivateKey privateKey, X509Certificate[] chain) {
109 AndroidPrivateKey key = mLocalKeyStore.createKey(privateKey);
111 if (key == null || chain == null || chain.length == 0) {
112 Log.w(TAG, "Empty client certificate chain?");
113 provideResponse(null, null);
116 // Encode the certificate chain.
117 byte[][] encodedChain = new byte[chain.length][];
119 for (int i = 0; i < chain.length; ++i) {
120 encodedChain[i] = chain[i].getEncoded();
122 } catch (CertificateEncodingException e) {
123 Log.w(TAG, "Could not retrieve encoded certificate chain: " + e);
124 provideResponse(null, null);
127 mLookupTable.allow(mHost, mPort, key, encodedChain);
128 provideResponse(key, encodedChain);
131 private void ignoreOnUiThread() {
133 provideResponse(null, null);
136 private void cancelOnUiThread() {
138 mLookupTable.deny(mHost, mPort);
139 provideResponse(null, null);
142 private void checkIfCalled() {
144 throw new IllegalStateException("The callback was already called.");
149 private void provideResponse(AndroidPrivateKey androidKey, byte[][] certChain) {
150 if (mNativeContentsClientBridge == 0) return;
151 nativeProvideClientCertificateResponse(mNativeContentsClientBridge, mId,
152 certChain, androidKey);
156 // Used by the native peer to set/reset a weak ref to the native peer.
158 private void setNativeContentsClientBridge(long nativeContentsClientBridge) {
159 mNativeContentsClientBridge = nativeContentsClientBridge;
162 // If returns false, the request is immediately canceled, and any call to proceedSslError
163 // has no effect. If returns true, the request should be canceled or proceeded using
164 // proceedSslError().
165 // Unlike the webview classic, we do not keep keep a database of certificates that
166 // are allowed by the user, because this functionality is already handled via
167 // ssl_policy in native layers.
169 private boolean allowCertificateError(int certError, byte[] derBytes, final String url,
171 final SslCertificate cert = SslUtil.getCertificateFromDerBytes(derBytes);
173 // if the certificate or the client is null, cancel the request
176 final SslError sslError = SslUtil.sslErrorFromNetErrorCode(certError, cert, url);
177 ValueCallback<Boolean> callback = new ValueCallback<Boolean>() {
179 public void onReceiveValue(final Boolean value) {
180 ThreadUtils.runOnUiThread(new Runnable() {
183 proceedSslError(value.booleanValue(), id);
188 mClient.onReceivedSslError(callback, sslError);
192 private void proceedSslError(boolean proceed, int id) {
193 if (mNativeContentsClientBridge == 0) return;
194 nativeProceedSslError(mNativeContentsClientBridge, proceed, id);
197 // Intentionally not private for testing the native peer of this class.
199 protected void selectClientCertificate(final int id, final String[] keyTypes,
200 byte[][] encodedPrincipals, final String host, final int port) {
201 assert mNativeContentsClientBridge != 0;
202 ClientCertLookupTable.Cert cert = mLookupTable.getCertData(host, port);
203 if (mLookupTable.isDenied(host, port)) {
204 nativeProvideClientCertificateResponse(mNativeContentsClientBridge, id,
209 nativeProvideClientCertificateResponse(mNativeContentsClientBridge, id,
210 cert.certChain, cert.privateKey);
213 // Build the list of principals from encoded versions.
214 Principal[] principals = null;
215 if (encodedPrincipals.length > 0) {
216 principals = new X500Principal[encodedPrincipals.length];
217 for (int n = 0; n < encodedPrincipals.length; n++) {
219 principals[n] = new X500Principal(encodedPrincipals[n]);
220 } catch (IllegalArgumentException e) {
221 Log.w(TAG, "Exception while decoding issuers list: " + e);
222 nativeProvideClientCertificateResponse(mNativeContentsClientBridge, id,
230 final ClientCertificateRequestCallback callback =
231 new ClientCertificateRequestCallback(id, host, port);
232 mClient.onReceivedClientCertRequest(callback, keyTypes, principals, host, port);
236 private void handleJsAlert(String url, String message, int id) {
237 JsResultHandler handler = new JsResultHandler(this, id);
238 mClient.handleJsAlert(url, message, handler);
242 private void handleJsConfirm(String url, String message, int id) {
243 JsResultHandler handler = new JsResultHandler(this, id);
244 mClient.handleJsConfirm(url, message, handler);
248 private void handleJsPrompt(String url, String message, String defaultValue, int id) {
249 JsResultHandler handler = new JsResultHandler(this, id);
250 mClient.handleJsPrompt(url, message, defaultValue, handler);
254 private void handleJsBeforeUnload(String url, String message, int id) {
255 JsResultHandler handler = new JsResultHandler(this, id);
256 mClient.handleJsBeforeUnload(url, message, handler);
260 private boolean shouldOverrideUrlLoading(String url) {
261 return mClient.shouldOverrideUrlLoading(url);
264 void confirmJsResult(int id, String prompt) {
265 if (mNativeContentsClientBridge == 0) return;
266 nativeConfirmJsResult(mNativeContentsClientBridge, id, prompt);
269 void cancelJsResult(int id) {
270 if (mNativeContentsClientBridge == 0) return;
271 nativeCancelJsResult(mNativeContentsClientBridge, id);
274 //--------------------------------------------------------------------------------------------
276 //--------------------------------------------------------------------------------------------
277 private native void nativeProceedSslError(long nativeAwContentsClientBridge, boolean proceed,
279 private native void nativeProvideClientCertificateResponse(long nativeAwContentsClientBridge,
280 int id, byte[][] certChain, AndroidPrivateKey androidKey);
282 private native void nativeConfirmJsResult(long nativeAwContentsClientBridge, int id,
284 private native void nativeCancelJsResult(long nativeAwContentsClientBridge, int id);