Index: net/android/java/src/org/chromium/net/AndroidKeyStore.java |
diff --git a/net/android/java/src/org/chromium/net/AndroidKeyStore.java b/net/android/java/src/org/chromium/net/AndroidKeyStore.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..681d3324c4ba886ed253a14a7732842ccf390c2e |
--- /dev/null |
+++ b/net/android/java/src/org/chromium/net/AndroidKeyStore.java |
@@ -0,0 +1,280 @@ |
+// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.net; |
+ |
+import android.app.Activity; |
+import android.content.Context; |
+import android.os.AsyncTask; |
+import android.security.KeyChain; |
+import android.security.KeyChainAliasCallback; |
+import android.security.KeyChainException; |
+import android.util.Log; |
+ |
+import java.util.ArrayList; |
+import java.security.cert.CertificateEncodingException; |
+import java.security.cert.X509Certificate; |
+import java.security.NoSuchAlgorithmException; |
+import java.security.Principal; |
+import java.security.PrivateKey; |
+import java.security.Signature; |
+import javax.security.auth.x500.X500Principal; |
+ |
+import org.chromium.base.ActivityStatus; |
+import org.chromium.base.CalledByNative; |
+import org.chromium.base.JNINamespace; |
+import org.chromium.base.NativeClassQualifiedName; |
+ |
+@JNINamespace("net::android") |
+public class AndroidKeyStore { |
+ |
+ private static final String TAG = AndroidKeyStore.class.getName(); |
+ |
+ //////////////////////////////////////////////////////////////////// |
+ // |
+ // Message signing support. |
+ // |
+ |
+ /** |
+ * Called from native code to sign a given message with an RSA-based |
+ * PrivateKey object. |
+ * @param privateKey The PrivateKey handle. |
+ * @param message The message to sign. |
+ * @return signature as a byte buffer. |
+ */ |
+ @CalledByNative |
+ public static byte[] signWithPrivateKey(PrivateKey privateKey, |
+ byte[] message) { |
+ synchronized (TAG) { |
+ // Get the Signature singleton for this key. |
+ Signature signature = null; |
+ String algorithm = privateKey.getAlgorithm(); |
+ if (algorithm == "RSA") |
palmer
2013/01/19 01:43:12
Same comment as below about the guarantee of inter
digit1
2013/01/21 13:35:35
I believe this is supported (looking at the Androi
|
+ signature = sRsaSignature; |
+ if (algorithm == "DSA") |
+ signature = sDsaSignature; |
+ if (algorithm == "ECDSA") |
+ signature = sEcdsaSignature; |
+ |
+ if (signature == null) { |
+ Log.e(TAG, "Can't get " + algorithm + " Signature singleton."); |
+ return null; |
+ } |
+ |
+ // Sign the message. |
+ try { |
+ signature.initSign(privateKey); |
+ signature.update(message); |
+ return signature.sign(); |
+ } catch (Exception e) { |
+ Log.e(TAG, "Exception while signing message with " + algorithm + |
+ " private key: " + e); |
+ return null; |
+ } |
+ } |
+ } |
+ |
+ // Single signature instances. Used to perform signing with private |
palmer
2013/01/19 01:43:12
I'd wrap these comments to the 100-column limit.
digit1
2013/01/21 13:35:35
I don't think this is necessary (I don't even use
|
+ // keys. To avoid increasing Chromium's startup time, these are |
+ // created lazily by calling initSignatureForKey below. |
+ private static Signature sRsaSignature; |
+ private static Signature sDsaSignature; |
+ private static Signature sEcdsaSignature; |
+ |
+ // Called to ensure that the global Signature object corresponding |
+ // to a given private key is initialized before a call to signWithPrivateKey |
palmer
2013/01/19 01:43:12
Nit: End sentence with a period. I'm sorry to be s
digit1
2013/01/21 13:35:35
No, that's ok and welcome. I'll fix it, thanks :)
|
+ private static void initSignatureForKey(PrivateKey key) { |
+ String algorithm = key.getAlgorithm(); |
+ synchronized (TAG) { |
+ try { |
+ if (algorithm == "RSA" && sRsaSignature == null) |
palmer
2013/01/19 01:43:12
== isn't *guaranteed* to work, is it? I'd use "RSA
|
+ sRsaSignature = Signature.getInstance("RSAwithNone"); |
+ if (algorithm == "DSA" && sDsaSignature == null) |
+ sDsaSignature = Signature.getInstance("DSAwithNone"); |
+ if (algorithm == "ECDSA" && sEcdsaSignature == null) |
+ sEcdsaSignature = Signature.getInstance("ECDSAwithNone"); |
+ } catch (Exception e) { |
+ Log.w(TAG, "Could not create " + algorithm + " Signature singleton."); |
+ } |
+ } |
+ } |
+ |
+ // ClientCertRequest models an asynchronous client certificate request |
+ // on the Dalvik side. There is a matching C++ net::ClientCertRequest |
+ // class that has _slightly_ different lifecycles (e.g. if a Tab |
+ // is closed while an asynchrouns request is still pending, the native |
palmer
2013/01/19 01:43:12
Typo: asynchronous
digit1
2013/01/21 13:35:35
Done.
|
+ // C++ class will be deleted, but the Java one must persist until the |
+ // system sends its answer to the activity). |
+ // |
+ // In theory, it is possible to only have one Java class to model |
+ // each request, but this makes the native interface slightly more |
+ // complicated to use. |
+ // |
+ // Each request must be started from the UI thread, and the system |
+ // will answer the KeyChain.choosePrivateKeyAlias() call with a |
+ // private key alias string, which can be used to call |
+ // KeyChain.getCertificateChain() and KeyChain.getPrivateKey(), |
+ // however these functions are blocking and can't be called on the |
+ // UI thread. |
+ // |
+ // To solve this, start an AsyncTask when we an alias is received. |
palmer
2013/01/19 01:43:12
"when we" --> "when"
digit1
2013/01/21 13:35:35
Done.
|
+ // it will retrieve the certificate chain and private key in the |
+ // background, then later send the result back to the UI thread. |
+ // |
+ static class ClientCertRequest extends AsyncTask<Void, Void, Void> |
+ implements KeyChainAliasCallback { |
+ private final int mRequestId; |
+ private String mAlias; |
+ private byte[][] mEncodedChain; |
+ private PrivateKey mPrivateKey; |
+ |
+ public ClientCertRequest(int requestId) { |
+ mRequestId = requestId; |
+ mAlias = null; |
+ mEncodedChain = null; |
+ mPrivateKey = null; |
+ } |
+ |
+ // KeyChainAliasCallback implementation |
+ @Override |
+ public void alias(String alias) { |
+ if (alias == null) { |
+ // No certificate was selected. |
+ onPostExecute(null); |
+ } else { |
+ mAlias = alias; |
+ // Launch background thread. |
+ execute(); |
+ } |
+ } |
+ |
+ @Override |
+ protected Void doInBackground(Void... params) { |
+ // Executed in a background thread, can call blocking APIs. |
+ X509Certificate[] chain = null; |
+ PrivateKey key = null; |
+ Context context = ActivityStatus.getActivity().getApplicationContext(); |
+ try { |
+ key = KeyChain.getPrivateKey(context, mAlias); |
+ chain = KeyChain.getCertificateChain(context, mAlias); |
+ } catch (KeyChainException e) { |
+ Log.w(TAG, "KeyChainException when looking for '" + mAlias + "' certificate"); |
+ } catch (InterruptedException e) { |
+ Log.w(TAG, "InterruptedException when looking for '" + mAlias + "'certificate"); |
+ } |
+ |
+ if (key == null || chain == null || chain.length == 0) |
+ return null; |
+ |
+ // Get the encoded certificate chain. |
+ byte[][] encoded_chain = new byte[chain.length][]; |
+ try { |
+ for (int i = 0; i < chain.length; ++i) { |
+ mEncodedChain[i] = chain[i].getEncoded(); |
+ } |
+ } catch (CertificateEncodingException e) { |
+ return null; |
+ } |
+ |
+ // Ensure that the Signature singleton is initialized. |
+ initSignatureForKey(key); |
+ |
+ // It's not possible to get the encoded private key bits |
+ // since JellyBean, so return an object reference instead. |
+ mEncodedChain = encoded_chain; |
+ mPrivateKey = key; |
+ |
+ return null; |
+ } |
+ |
+ @Override |
+ protected void onPostExecute(Void result) { |
+ // Back to the UI thread. |
+ getRequestList().remove(this); |
+ nativeOnRequestCompletion(mRequestId, mAlias, mEncodedChain, mPrivateKey); |
+ } |
+ } |
+ |
+ // Called to pass request results to native side. |
+ private static native void nativeOnRequestCompletion(int requestId, |
+ String alias, |
+ byte[][] certChain, |
+ PrivateKey privateKey); |
+ |
+ /** |
+ * Create a new asynchronous request to select a client certificate. |
+ * |
+ * @param request_id The unique numerical id for the request. |
+ * @param key_types The list of supported key exchange types. |
+ * @param principals The list of CA DistinguishedNames. |
+ * @param host_name The server host name is available (empty otherwise). |
+ * @param port The server port if available (0 otherwise). |
+ * @return true on success. |
+ */ |
+ @CalledByNative |
+ static public boolean selectClientCertificate(int request_id, |
+ String[] key_types, |
+ byte[][] encodedPrincipals, |
palmer
2013/01/19 01:43:12
Mixing naming conventions: key_types, encodedPrinc
digit1
2013/01/21 13:35:35
Done.
|
+ String host_name, |
+ int port, |
+ String alias_hint) { |
+ Activity activity = ActivityStatus.getActivity(); |
+ if (activity == null) { |
+ Log.w(TAG, "No active Chromium main activity!?"); |
+ return false; |
+ } |
+ |
+ // Build the list of principals from encoded versions. |
+ Principal[] principals = null; |
+ if (encodedPrincipals.length > 0) { |
+ principals = new X500Principal[encodedPrincipals.length]; |
+ try { |
+ for (int n = 0; n < encodedPrincipals.length; n++) { |
+ principals[n] = new X500Principal(encodedPrincipals[n]); |
+ } |
+ } catch (Exception e) { |
+ // Bail on error. |
+ Log.w(TAG, "Exception while decoding issuers list: " + e); |
+ return false; |
+ } |
+ } |
+ |
+ // All good, create new request, add it to our list and launch |
+ // the certificate selection activity. |
+ ClientCertRequest request = new ClientCertRequest(request_id); |
+ getRequestList().add(request); |
+ |
+ KeyChain.choosePrivateKeyAlias(activity, request, key_types, |
+ principals, host_name, port, |
+ alias_hint); |
+ return true; |
+ } |
+ |
+ /////////////////////////////////////////////////////////////////// |
+ // |
+ // Global list of pending requests. |
+ // |
+ |
+ private static class RequestList { |
+ private ArrayList<ClientCertRequest> mRequests = new ArrayList<ClientCertRequest>(); |
+ |
+ public void add(ClientCertRequest request) { |
+ mRequests.add(request); |
+ } |
+ |
+ public void remove(ClientCertRequest request) { |
+ mRequests.remove(request); |
+ } |
+ } |
+ |
+ private static RequestList sRequestList; |
+ |
+ private static RequestList getRequestList() { |
+ if (sRequestList == null) { |
palmer
2013/01/19 01:43:12
Should this be synchronized?
digit1
2013/01/21 13:35:35
This shall only be called from the UI thread, so I
|
+ sRequestList = new RequestList(); |
+ } |
+ return sRequestList; |
+ } |
+ |
+} |