Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / android_webview / java / src / org / chromium / android_webview / AwContentsClientBridge.java
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.
4
5 package org.chromium.android_webview;
6
7 import android.net.http.SslCertificate;
8 import android.net.http.SslError;
9 import android.util.Log;
10 import android.webkit.ValueCallback;
11
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;
17
18 import java.security.Principal;
19 import java.security.PrivateKey;
20 import java.security.cert.CertificateEncodingException;
21 import java.security.cert.X509Certificate;
22
23 import javax.security.auth.x500.X500Principal;
24
25 /**
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
30  * references.
31  */
32 @JNINamespace("android_webview")
33 public class AwContentsClientBridge {
34     static final String TAG = "AwContentsClientBridge";
35
36     private AwContentsClient mClient;
37     // The native peer of this object.
38     private long mNativeContentsClientBridge;
39
40     private DefaultAndroidKeyStore mLocalKeyStore;
41
42     private ClientCertLookupTable mLookupTable;
43
44     // Used for mocking this class in tests.
45     protected AwContentsClientBridge(DefaultAndroidKeyStore keyStore,
46             ClientCertLookupTable table) {
47         mLocalKeyStore = keyStore;
48         mLookupTable = table;
49     }
50
51     public AwContentsClientBridge(AwContentsClient client, DefaultAndroidKeyStore keyStore,
52             ClientCertLookupTable table) {
53         assert client != null;
54         mClient = client;
55         mLocalKeyStore = keyStore;
56         mLookupTable = table;
57     }
58
59     /**
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.
64      */
65     public class ClientCertificateRequestCallback {
66
67         private int mId;
68         private String mHost;
69         private int mPort;
70         private boolean mIsCalled;
71
72         public ClientCertificateRequestCallback(int id, String host, int port) {
73             mId = id;
74             mHost = host;
75             mPort = port;
76         }
77
78         public void proceed(final PrivateKey privateKey, final X509Certificate[] chain) {
79             ThreadUtils.runOnUiThread(new Runnable() {
80                 @Override
81                 public void run() {
82                     proceedOnUiThread(privateKey, chain);
83                 }
84             });
85         }
86
87         public void ignore() {
88             ThreadUtils.runOnUiThread(new Runnable() {
89                 @Override
90                 public void run() {
91                     ignoreOnUiThread();
92                 }
93             });
94         }
95
96         public void cancel() {
97             ThreadUtils.runOnUiThread(new Runnable() {
98                 @Override
99                 public void run() {
100                     cancelOnUiThread();
101                 }
102
103             });
104         }
105
106         private void proceedOnUiThread(PrivateKey privateKey, X509Certificate[] chain) {
107             checkIfCalled();
108
109             AndroidPrivateKey key = mLocalKeyStore.createKey(privateKey);
110
111             if (key == null || chain == null || chain.length == 0) {
112                 Log.w(TAG, "Empty client certificate chain?");
113                 provideResponse(null, null);
114                 return;
115             }
116             // Encode the certificate chain.
117             byte[][] encodedChain = new byte[chain.length][];
118             try {
119                 for (int i = 0; i < chain.length; ++i) {
120                     encodedChain[i] = chain[i].getEncoded();
121                 }
122             } catch (CertificateEncodingException e) {
123                 Log.w(TAG, "Could not retrieve encoded certificate chain: " + e);
124                 provideResponse(null, null);
125                 return;
126             }
127             mLookupTable.allow(mHost, mPort, key, encodedChain);
128             provideResponse(key, encodedChain);
129         }
130
131         private void ignoreOnUiThread() {
132             checkIfCalled();
133             provideResponse(null, null);
134         }
135
136         private void cancelOnUiThread() {
137             checkIfCalled();
138             mLookupTable.deny(mHost, mPort);
139             provideResponse(null, null);
140         }
141
142         private void checkIfCalled() {
143             if (mIsCalled) {
144                 throw new IllegalStateException("The callback was already called.");
145             }
146             mIsCalled = true;
147         }
148
149         private void provideResponse(AndroidPrivateKey androidKey, byte[][] certChain) {
150             if (mNativeContentsClientBridge == 0) return;
151             nativeProvideClientCertificateResponse(mNativeContentsClientBridge, mId,
152                     certChain, androidKey);
153         }
154     }
155
156     // Used by the native peer to set/reset a weak ref to the native peer.
157     @CalledByNative
158     private void setNativeContentsClientBridge(long nativeContentsClientBridge) {
159         mNativeContentsClientBridge = nativeContentsClientBridge;
160     }
161
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.
168     @CalledByNative
169     private boolean allowCertificateError(int certError, byte[] derBytes, final String url,
170             final int id) {
171         final SslCertificate cert = SslUtil.getCertificateFromDerBytes(derBytes);
172         if (cert == null) {
173             // if the certificate or the client is null, cancel the request
174             return false;
175         }
176         final SslError sslError = SslUtil.sslErrorFromNetErrorCode(certError, cert, url);
177         ValueCallback<Boolean> callback = new ValueCallback<Boolean>() {
178             @Override
179             public void onReceiveValue(final Boolean value) {
180                 ThreadUtils.runOnUiThread(new Runnable() {
181                     @Override
182                     public void run() {
183                         proceedSslError(value.booleanValue(), id);
184                     }
185                 });
186             }
187         };
188         mClient.onReceivedSslError(callback, sslError);
189         return true;
190     }
191
192     private void proceedSslError(boolean proceed, int id) {
193         if (mNativeContentsClientBridge == 0) return;
194         nativeProceedSslError(mNativeContentsClientBridge, proceed, id);
195     }
196
197     // Intentionally not private for testing the native peer of this class.
198     @CalledByNative
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,
205                     null, null);
206             return;
207         }
208         if (cert != null) {
209             nativeProvideClientCertificateResponse(mNativeContentsClientBridge, id,
210                     cert.certChain, cert.privateKey);
211             return;
212         }
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++) {
218                 try {
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,
223                         null, null);
224                     return;
225                 }
226             }
227
228         }
229
230         final ClientCertificateRequestCallback callback =
231                 new ClientCertificateRequestCallback(id, host, port);
232         mClient.onReceivedClientCertRequest(callback, keyTypes, principals, host, port);
233     }
234
235     @CalledByNative
236     private void handleJsAlert(String url, String message, int id) {
237         JsResultHandler handler = new JsResultHandler(this, id);
238         mClient.handleJsAlert(url, message, handler);
239     }
240
241     @CalledByNative
242     private void handleJsConfirm(String url, String message, int id) {
243         JsResultHandler handler = new JsResultHandler(this, id);
244         mClient.handleJsConfirm(url, message, handler);
245     }
246
247     @CalledByNative
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);
251     }
252
253     @CalledByNative
254     private void handleJsBeforeUnload(String url, String message, int id) {
255         JsResultHandler handler = new JsResultHandler(this, id);
256         mClient.handleJsBeforeUnload(url, message, handler);
257     }
258
259     @CalledByNative
260     private boolean shouldOverrideUrlLoading(String url) {
261         return mClient.shouldOverrideUrlLoading(url);
262     }
263
264     void confirmJsResult(int id, String prompt) {
265         if (mNativeContentsClientBridge == 0) return;
266         nativeConfirmJsResult(mNativeContentsClientBridge, id, prompt);
267     }
268
269     void cancelJsResult(int id) {
270         if (mNativeContentsClientBridge == 0) return;
271         nativeCancelJsResult(mNativeContentsClientBridge, id);
272     }
273
274     //--------------------------------------------------------------------------------------------
275     //  Native methods
276     //--------------------------------------------------------------------------------------------
277     private native void nativeProceedSslError(long nativeAwContentsClientBridge, boolean proceed,
278             int id);
279     private native void nativeProvideClientCertificateResponse(long nativeAwContentsClientBridge,
280             int id, byte[][] certChain, AndroidPrivateKey androidKey);
281
282     private native void nativeConfirmJsResult(long nativeAwContentsClientBridge, int id,
283             String prompt);
284     private native void nativeCancelJsResult(long nativeAwContentsClientBridge, int id);
285 }