Home | History | Annotate | Download | only in android_webview
      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.mCertChain, cert.mPrivateKey);
    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 }
    286