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