c22e7dcb6862facaa23d7cc21ec2919345326805
[platform/framework/web/crosswalk.git] / src / chrome / android / java / src / org / chromium / chrome / browser / SSLClientCertificateRequest.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.chrome.browser;
6
7 import android.app.Activity;
8 import android.content.Context;
9 import android.os.AsyncTask;
10 import android.security.KeyChain;
11 import android.security.KeyChainAliasCallback;
12 import android.security.KeyChainException;
13 import android.util.Log;
14
15 import org.chromium.base.ActivityStatus;
16 import org.chromium.base.CalledByNative;
17 import org.chromium.base.JNINamespace;
18 import org.chromium.base.ThreadUtils;
19
20 import java.security.Principal;
21 import java.security.PrivateKey;
22 import java.security.cert.CertificateEncodingException;
23 import java.security.cert.X509Certificate;
24
25 import javax.security.auth.x500.X500Principal;
26
27 @JNINamespace("chrome::android")
28 class SSLClientCertificateRequest extends AsyncTask<Void, Void, Void>
29         implements KeyChainAliasCallback {
30
31     static final String TAG = "SSLClientCertificateRequest";
32
33     // ClientCertRequest models an asynchronous client certificate request on the Java side. Use
34     // selectClientCertificate() on the UI thread to start/create a new request, this will launch a
35     // system activity through KeyChain.choosePrivateKeyAlias() to let the user select a client
36     // certificate.
37     //
38     // The selected certificate will be sent back as a string alias, which is used to call
39     // KeyChain.getCertificateChain() and KeyChain.getPrivateKey(). Unfortunately, these APIs are
40     // blocking, thus can't be called from the UI thread.
41     //
42     // To solve this, start an AsyncTask when the alias is received. It will retrieve the
43     // certificate chain and private key in the background, then later send the result back to the
44     // UI thread.
45     //
46     private final int mNativePtr;
47     private String mAlias;
48     private byte[][] mEncodedChain;
49     private PrivateKey mPrivateKey;
50
51     private SSLClientCertificateRequest(int nativePtr) {
52         mNativePtr = nativePtr;
53         mAlias = null;
54         mEncodedChain = null;
55         mPrivateKey = null;
56     }
57
58     // KeyChainAliasCallback implementation
59     @Override
60     public void alias(final String alias) {
61         // This is called by KeyChainActivity in a background thread. Post task to handle the
62         // certificate selection on the UI thread.
63         ThreadUtils.runOnUiThread(new Runnable() {
64             @Override
65             public void run() {
66                 if (alias == null) {
67                     // No certificate was selected.
68                     onPostExecute(null);
69                 } else {
70                     mAlias = alias;
71                     // Launch background thread.
72                     execute();
73                 }
74             }
75         });
76     }
77
78     @Override
79     protected Void doInBackground(Void... params) {
80         // Executed in a background thread, can call blocking APIs.
81         X509Certificate[] chain = null;
82         PrivateKey key = null;
83         Context context = ActivityStatus.getActivity().getApplicationContext();
84         try {
85             key = KeyChain.getPrivateKey(context, mAlias);
86             chain = KeyChain.getCertificateChain(context, mAlias);
87         } catch (KeyChainException e) {
88             Log.w(TAG, "KeyChainException when looking for '" + mAlias + "' certificate");
89             return null;
90         } catch (InterruptedException e) {
91             Log.w(TAG, "InterruptedException when looking for '" + mAlias + "'certificate");
92             return null;
93         }
94
95         if (key == null || chain == null || chain.length == 0) {
96             Log.w(TAG, "Empty client certificate chain?");
97             return null;
98         }
99
100         // Get the encoded certificate chain.
101         byte[][] encodedChain = new byte[chain.length][];
102         try {
103             for (int i = 0; i < chain.length; ++i) {
104                 encodedChain[i] = chain[i].getEncoded();
105             }
106         } catch (CertificateEncodingException e) {
107             Log.w(TAG, "Could not retrieve encoded certificate chain: " + e);
108             return null;
109         }
110
111         mEncodedChain = encodedChain;
112         mPrivateKey = key;
113         return null;
114     }
115
116     @Override
117     protected void onPostExecute(Void result) {
118         // Back to the UI thread.
119         nativeOnSystemRequestCompletion(mNativePtr, mEncodedChain, mPrivateKey);
120     }
121
122
123     /**
124      * Create a new asynchronous request to select a client certificate.
125      *
126      * @param nativePtr The native object responsible for this request.
127      * @param keyTypes The list of supported key exchange types.
128      * @param encodedPrincipals The list of CA DistinguishedNames.
129      * @param hostName The server host name is available (empty otherwise).
130      * @param port The server port if available (0 otherwise).
131      * @return true on success.
132      * Note that nativeOnSystemRequestComplete will be called iff this method returns true.
133      */
134     @CalledByNative
135     private static boolean selectClientCertificate(int nativePtr, String[] keyTypes,
136             byte[][] encodedPrincipals, String hostName, int port) {
137         ThreadUtils.assertOnUiThread();
138
139         Activity activity = ActivityStatus.getActivity();
140         if (activity == null) {
141             Log.w(TAG, "No active Chromium main activity!?");
142             return false;
143         }
144
145         // Build the list of principals from encoded versions.
146         Principal[] principals = null;
147         if (encodedPrincipals.length > 0) {
148             principals = new X500Principal[encodedPrincipals.length];
149             try {
150                 for (int n = 0; n < encodedPrincipals.length; n++) {
151                     principals[n] = new X500Principal(encodedPrincipals[n]);
152                 }
153             } catch (Exception e) {
154                 // Bail on error.
155                 Log.w(TAG, "Exception while decoding issuers list: " + e);
156                 return false;
157             }
158         }
159
160         // All good, create new request, add it to our list and launch the certificate selection
161         // activity.
162         SSLClientCertificateRequest request = new SSLClientCertificateRequest(nativePtr);
163
164         KeyChain.choosePrivateKeyAlias(
165                 activity, request, keyTypes, principals, hostName, port, null);
166         return true;
167     }
168
169     // Called to pass request results to native side.
170     private static native void nativeOnSystemRequestCompletion(
171             int requestPtr, byte[][] certChain, PrivateKey privateKey);
172 }