diff options
17 files changed, 614 insertions, 220 deletions
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/PKCS11AuthenticationManager.java b/chrome/android/java/src/org/chromium/chrome/browser/PKCS11AuthenticationManager.java index e3fd436..8e57a70 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/PKCS11AuthenticationManager.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/PKCS11AuthenticationManager.java @@ -6,7 +6,8 @@ package org.chromium.chrome.browser; import android.content.Context; -import java.security.PrivateKey; +import org.chromium.net.AndroidPrivateKey; + import java.security.cert.X509Certificate; /** @@ -34,13 +35,13 @@ public interface PKCS11AuthenticationManager { public X509Certificate[] getCertificateChain(String alias); /** - * Returns the PrivateKey for the requested alias, or null if no there is no result. - */ - public PrivateKey getPrivateKey(String alias); - - /** * Performs necessary initializing for using a PKCS11-based KeysStore. Note that this can * perform expensive operations and cannot be done on the UI thread. */ public void initialize(Context context); + + /** + * Returns the AndroidPrivateKey for the requested alias, or null if there is no result. + */ + public AndroidPrivateKey getPrivateKey(String alias); } diff --git a/chrome/android/java/src/org/chromium/chrome/browser/SSLClientCertificateRequest.java b/chrome/android/java/src/org/chromium/chrome/browser/SSLClientCertificateRequest.java index 31c41fd..ee7d599 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/SSLClientCertificateRequest.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/SSLClientCertificateRequest.java @@ -16,9 +16,10 @@ import org.chromium.base.ActivityStatus; import org.chromium.base.CalledByNative; import org.chromium.base.JNINamespace; import org.chromium.base.ThreadUtils; +import org.chromium.net.AndroidPrivateKey; +import org.chromium.net.DefaultAndroidKeyStore; import java.security.Principal; -import java.security.PrivateKey; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; @@ -34,9 +35,12 @@ import javax.security.auth.x500.X500Principal; * finally pass the results back to the UI thread, which will return to the native code. */ @JNINamespace("chrome::android") -class SSLClientCertificateRequest { +public class SSLClientCertificateRequest { static final String TAG = "SSLClientCertificateRequest"; + private static final DefaultAndroidKeyStore sLocalKeyStore = + new DefaultAndroidKeyStore(); + /** * Common implementation for anynchronous task of handling the certificate request. This * AsyncTask uses the abstract methods to retrieve the authentication material from a @@ -47,7 +51,7 @@ class SSLClientCertificateRequest { // These fields will store the results computed in doInBackground so that they can be posted // back in onPostExecute. private byte[][] mEncodedChain; - private PrivateKey mPrivateKey; + private AndroidPrivateKey mAndroidPrivateKey; // Pointer to the native certificate request needed to return the results. private final int mNativePtr; @@ -58,7 +62,7 @@ class SSLClientCertificateRequest { // These overriden methods will be used to access the key store. abstract String getAlias(); - abstract PrivateKey getPrivateKey(String alias); + abstract AndroidPrivateKey getPrivateKey(String alias); abstract X509Certificate[] getCertificateChain(String alias); @Override @@ -66,8 +70,9 @@ class SSLClientCertificateRequest { String alias = getAlias(); if (alias == null) return null; - PrivateKey key = getPrivateKey(alias); + AndroidPrivateKey key = getPrivateKey(alias); X509Certificate[] chain = getCertificateChain(alias); + if (key == null || chain == null || chain.length == 0) { Log.w(TAG, "Empty client certificate chain?"); return null; @@ -85,14 +90,14 @@ class SSLClientCertificateRequest { } mEncodedChain = encodedChain; - mPrivateKey = key; + mAndroidPrivateKey = key; return null; } @Override protected void onPostExecute(Void result) { ThreadUtils.assertOnUiThread(); - nativeOnSystemRequestCompletion(mNativePtr, mEncodedChain, mPrivateKey); + nativeOnSystemRequestCompletion(mNativePtr, mEncodedChain, mAndroidPrivateKey); } } @@ -114,9 +119,9 @@ class SSLClientCertificateRequest { } @Override - PrivateKey getPrivateKey(String alias) { + AndroidPrivateKey getPrivateKey(String alias) { try { - return KeyChain.getPrivateKey(mContext, alias); + return sLocalKeyStore.createKey(KeyChain.getPrivateKey(mContext, alias)); } catch (KeyChainException e) { Log.w(TAG, "KeyChainException when looking for '" + alias + "' certificate"); return null; @@ -160,7 +165,7 @@ class SSLClientCertificateRequest { } @Override - PrivateKey getPrivateKey(String alias) { + AndroidPrivateKey getPrivateKey(String alias) { return mPKCS11AuthManager.getPrivateKey(alias); } @@ -290,5 +295,5 @@ class SSLClientCertificateRequest { // Called to pass request results to native side. private static native void nativeOnSystemRequestCompletion( - int requestPtr, byte[][] certChain, PrivateKey privateKey); + int requestPtr, byte[][] certChain, AndroidPrivateKey androidKey); } diff --git a/chrome/android/testshell/java/src/org/chromium/chrome/testshell/TestShellPKCS11AuthenticationManager.java b/chrome/android/testshell/java/src/org/chromium/chrome/testshell/TestShellPKCS11AuthenticationManager.java index 691e650..49a954b 100644 --- a/chrome/android/testshell/java/src/org/chromium/chrome/testshell/TestShellPKCS11AuthenticationManager.java +++ b/chrome/android/testshell/java/src/org/chromium/chrome/testshell/TestShellPKCS11AuthenticationManager.java @@ -7,8 +7,8 @@ package org.chromium.chrome.testshell; import android.content.Context; import org.chromium.chrome.browser.PKCS11AuthenticationManager; +import org.chromium.net.AndroidPrivateKey; -import java.security.PrivateKey; import java.security.cert.X509Certificate; /** @@ -36,7 +36,7 @@ public class TestShellPKCS11AuthenticationManager implements PKCS11Authenticatio } @Override - public PrivateKey getPrivateKey(String alias) { + public AndroidPrivateKey getPrivateKey(String alias) { return null; } } diff --git a/net/android/android_private_key.cc b/net/android/android_private_key.cc new file mode 100644 index 0000000..8f56085 --- /dev/null +++ b/net/android/android_private_key.cc @@ -0,0 +1,25 @@ +// Copyright 2014 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. + +#include "net/android/android_private_key.h" + +#include <vector> +#include "jni/AndroidPrivateKey_jni.h" + +namespace net { +namespace android { + +base::android::ScopedJavaLocalRef<jobject> GetKeyStore( + jobject private_key_ref) { + JNIEnv* env = base::android::AttachCurrentThread(); + return Java_AndroidPrivateKey_getKeyStore( + env, private_key_ref); +} + +bool RegisterAndroidPrivateKey(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace net diff --git a/net/android/android_private_key.h b/net/android/android_private_key.h new file mode 100644 index 0000000..9782a2d --- /dev/null +++ b/net/android/android_private_key.h @@ -0,0 +1,28 @@ +// Copyright 2014 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. + +#ifndef NET_ANDROID_ANDROID_PRIVATE_KEY_H +#define NET_ANDROID_ANDROID_PRIVATE_KEY_H + +#include <jni.h> + +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "net/base/net_export.h" + + +namespace net { +namespace android { + +// Returns the KeyStore associated with a given AndroidPrivateKey +NET_EXPORT base::android::ScopedJavaLocalRef<jobject> GetKeyStore( + jobject private_key); + +// Register JNI methods +NET_EXPORT bool RegisterAndroidPrivateKey(JNIEnv* env); + +} // namespace android +} // namespace net + +#endif // NET_ANDROID_ANDROID_PRIVATE_KEY_H diff --git a/net/android/java/src/org/chromium/net/AndroidKeyStore.java b/net/android/java/src/org/chromium/net/AndroidKeyStore.java index 1396bf1..6db0d0a 100644 --- a/net/android/java/src/org/chromium/net/AndroidKeyStore.java +++ b/net/android/java/src/org/chromium/net/AndroidKeyStore.java @@ -1,35 +1,17 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. +// Copyright 2014 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.util.Log; - import org.chromium.base.CalledByNative; import org.chromium.base.JNINamespace; -import java.lang.reflect.Method; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.Signature; -import java.security.interfaces.DSAKey; -import java.security.interfaces.DSAParams; -import java.security.interfaces.DSAPrivateKey; -import java.security.interfaces.ECKey; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.RSAKey; -import java.security.interfaces.RSAPrivateKey; -import java.security.spec.ECParameterSpec; - +/** + * Specifies all the dependencies from the native OpenSSL engine on an Android KeyStore. + */ @JNINamespace("net::android") -public class AndroidKeyStore { - - private static final String TAG = "AndroidKeyStore"; - - //////////////////////////////////////////////////////////////////// - // - // Message signing support. +public interface AndroidKeyStore { /** * Returns the public modulus of a given RSA private key as a byte @@ -43,14 +25,7 @@ public class AndroidKeyStore { * big-endian representation of a BigInteger. */ @CalledByNative - public static byte[] getRSAKeyModulus(PrivateKey key) { - if (key instanceof RSAKey) { - return ((RSAKey) key).getModulus().toByteArray(); - } else { - Log.w(TAG, "Not a RSAKey instance!"); - return null; - } - } + byte[] getRSAKeyModulus(AndroidPrivateKey key); /** * Returns the 'Q' parameter of a given DSA private key as a byte @@ -63,15 +38,7 @@ public class AndroidKeyStore { * a big-endian representation of a BigInteger. */ @CalledByNative - public static byte[] getDSAKeyParamQ(PrivateKey key) { - if (key instanceof DSAKey) { - DSAParams params = ((DSAKey) key).getParams(); - return params.getQ().toByteArray(); - } else { - Log.w(TAG, "Not a DSAKey instance!"); - return null; - } - } + byte[] getDSAKeyParamQ(AndroidPrivateKey key); /** * Returns the 'order' parameter of a given ECDSA private key as a @@ -81,15 +48,7 @@ public class AndroidKeyStore { * This is a big-endian representation of a BigInteger. */ @CalledByNative - public static byte[] getECKeyOrder(PrivateKey key) { - if (key instanceof ECKey) { - ECParameterSpec params = ((ECKey) key).getParams(); - return params.getOrder().toByteArray(); - } else { - Log.w(TAG, "Not an ECKey instance!"); - return null; - } - } + byte[] getECKeyOrder(AndroidPrivateKey key); /** * Returns the encoded data corresponding to a given PrivateKey. @@ -100,9 +59,7 @@ public class AndroidKeyStore { * @return encoded key as PKCS#8 byte array, can be null. */ @CalledByNative - public static byte[] getPrivateKeyEncodedBytes(PrivateKey key) { - return key.getEncoded(); - } + byte[] getPrivateKeyEncodedBytes(AndroidPrivateKey key); /** * Sign a given message with a given PrivateKey object. This method @@ -125,7 +82,7 @@ public class AndroidKeyStore { * message must be a hash and the function shall compute a direct * DSA/ECDSA signature for it. * - * @param privateKey The PrivateKey handle. + * @param key The PrivateKey handle. * @param message The message to sign. * @return signature as a byte buffer. * @@ -134,66 +91,21 @@ public class AndroidKeyStore { * getOpenSSLHandleForPrivateKey() below for work-around. */ @CalledByNative - public static byte[] rawSignDigestWithPrivateKey(PrivateKey privateKey, - byte[] message) { - // Get the Signature for this key. - Signature signature = null; - // Hint: Algorithm names come from: - // http://docs.oracle.com/javase/6/docs/technotes/guides/security/StandardNames.html - try { - if (privateKey instanceof RSAPrivateKey) { - // IMPORTANT: Due to a platform bug, this will throw NoSuchAlgorithmException - // on Android 4.0.x and 4.1.x. Fixed in 4.2 and higher. - // See https://android-review.googlesource.com/#/c/40352/ - signature = Signature.getInstance("NONEwithRSA"); - } else if (privateKey instanceof DSAPrivateKey) { - signature = Signature.getInstance("NONEwithDSA"); - } else if (privateKey instanceof ECPrivateKey) { - signature = Signature.getInstance("NONEwithECDSA"); - } - } catch (NoSuchAlgorithmException e) { - ; - } - - if (signature == null) { - Log.e(TAG, "Unsupported private key algorithm: " + privateKey.getAlgorithm()); - 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 " + privateKey.getAlgorithm() + - " private key: " + e); - return null; - } - } + byte[] rawSignDigestWithPrivateKey(AndroidPrivateKey key, byte[] message); /** * Return the type of a given PrivateKey object. This is an integer * that maps to one of the values defined by org.chromium.net.PrivateKeyType, * which is itself auto-generated from net/android/private_key_type_list.h - * @param privateKey The PrivateKey handle + * @param key The PrivateKey handle * @return key type, or PrivateKeyType.INVALID if unknown. */ @CalledByNative - public static int getPrivateKeyType(PrivateKey privateKey) { - if (privateKey instanceof RSAPrivateKey) - return PrivateKeyType.RSA; - if (privateKey instanceof DSAPrivateKey) - return PrivateKeyType.DSA; - if (privateKey instanceof ECPrivateKey) - return PrivateKeyType.ECDSA; - else - return PrivateKeyType.INVALID; - } + int getPrivateKeyType(AndroidPrivateKey key); /** * Return the system EVP_PKEY handle corresponding to a given PrivateKey - * object, obtained through reflection. + * object. * * This shall only be used when the "NONEwithRSA" signature is not * available, as described in rawSignDigestWithPrivateKey(). I.e. @@ -219,88 +131,15 @@ public class AndroidKeyStore { * libcore/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLRSAPrivateKey.java * libcore/luni/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp * - * @param privateKey The PrivateKey handle. + * @param key The PrivateKey handle. * @return The EVP_PKEY handle, as a 32-bit integer (0 if not available) */ @CalledByNative - public static int getOpenSSLHandleForPrivateKey(PrivateKey privateKey) { - // Sanity checks - if (privateKey == null) { - Log.e(TAG, "privateKey == null"); - return 0; - } - if (!(privateKey instanceof RSAPrivateKey)) { - Log.e(TAG, "does not implement RSAPrivateKey"); - return 0; - } - // First, check that this is a proper instance of OpenSSLRSAPrivateKey - // or one of its sub-classes. - Class<?> superClass; - try { - superClass = Class.forName( - "org.apache.harmony.xnet.provider.jsse.OpenSSLRSAPrivateKey"); - } catch (Exception e) { - // This may happen if the target device has a completely different - // implementation of the java.security APIs, compared to vanilla - // Android. Highly unlikely, but still possible. - Log.e(TAG, "Cannot find system OpenSSLRSAPrivateKey class: " + e); - return 0; - } - if (!superClass.isInstance(privateKey)) { - // This may happen if the PrivateKey was not created by the "AndroidOpenSSL" - // provider, which should be the default. That could happen if an OEM decided - // to implement a different default provider. Also highly unlikely. - Log.e(TAG, "Private key is not an OpenSSLRSAPrivateKey instance, its class name is:" + - privateKey.getClass().getCanonicalName()); - return 0; - } + int getOpenSSLHandleForPrivateKey(AndroidPrivateKey key); - try { - // Use reflection to invoke the 'getOpenSSLKey()' method on - // the private key. This returns another Java object that wraps - // a native EVP_PKEY. Note that the method is final, so calling - // the superclass implementation is ok. - Method getKey = superClass.getDeclaredMethod("getOpenSSLKey"); - getKey.setAccessible(true); - Object opensslKey = null; - try { - opensslKey = getKey.invoke(privateKey); - } finally { - getKey.setAccessible(false); - } - if (opensslKey == null) { - // Bail when detecting OEM "enhancement". - Log.e(TAG, "getOpenSSLKey() returned null"); - return 0; - } - - // Use reflection to invoke the 'getPkeyContext' method on the - // result of the getOpenSSLKey(). This is an 32-bit integer - // which is the address of an EVP_PKEY object. - Method getPkeyContext; - try { - getPkeyContext = opensslKey.getClass().getDeclaredMethod("getPkeyContext"); - } catch (Exception e) { - // Bail here too, something really not working as expected. - Log.e(TAG, "No getPkeyContext() method on OpenSSLKey member:" + e); - return 0; - } - getPkeyContext.setAccessible(true); - int evp_pkey = 0; - try { - evp_pkey = (Integer) getPkeyContext.invoke(opensslKey); - } finally { - getPkeyContext.setAccessible(false); - } - if (evp_pkey == 0) { - // The PrivateKey is probably rotten for some reason. - Log.e(TAG, "getPkeyContext() returned null"); - } - return evp_pkey; - - } catch (Exception e) { - Log.e(TAG, "Exception while trying to retrieve system EVP_PKEY handle: " + e); - return 0; - } - } + /** + * Called when the native OpenSSL engine no longer needs access to the underlying key. + */ + @CalledByNative + void releaseKey(AndroidPrivateKey key); } diff --git a/net/android/java/src/org/chromium/net/AndroidPrivateKey.java b/net/android/java/src/org/chromium/net/AndroidPrivateKey.java new file mode 100644 index 0000000..a6df6d4 --- /dev/null +++ b/net/android/java/src/org/chromium/net/AndroidPrivateKey.java @@ -0,0 +1,18 @@ +// Copyright 2014 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 org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; + +/** + * Abstract private key that bundles the PrivateKey and AndroidKeyStore that it belongs to. + */ +@JNINamespace("net::android") +public interface AndroidPrivateKey { + /** @return AndroidKeyStore that handles this key. */ + @CalledByNative + AndroidKeyStore getKeyStore(); +} diff --git a/net/android/java/src/org/chromium/net/DefaultAndroidKeyStore.java b/net/android/java/src/org/chromium/net/DefaultAndroidKeyStore.java new file mode 100644 index 0000000..5232e46 --- /dev/null +++ b/net/android/java/src/org/chromium/net/DefaultAndroidKeyStore.java @@ -0,0 +1,232 @@ +// Copyright 2013 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.util.Log; + +import java.lang.reflect.Method; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Signature; +import java.security.interfaces.DSAKey; +import java.security.interfaces.DSAParams; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.ECKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.RSAKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.spec.ECParameterSpec; + +/** + * Simple implementation of the AndroidKeyStore for use with an in-process Java KeyStore. + */ +public class DefaultAndroidKeyStore implements AndroidKeyStore { + + private static final String TAG = "AndroidKeyStoreInProcessImpl"; + + private static class DefaultAndroidPrivateKey implements AndroidPrivateKey { + // The actual Java key being wrapped. + final PrivateKey mKey; + // Key store handling this key. + final DefaultAndroidKeyStore mStore; + + DefaultAndroidPrivateKey(PrivateKey key, DefaultAndroidKeyStore store) { + mKey = key; + mStore = store; + } + + PrivateKey getJavaKey() { + return mKey; + } + + @Override + public AndroidKeyStore getKeyStore() { + return mStore; + } + } + + public AndroidPrivateKey createKey(PrivateKey javaKey) { + return new DefaultAndroidPrivateKey(javaKey, this); + } + + @Override + public byte[] getRSAKeyModulus(AndroidPrivateKey key) { + PrivateKey javaKey = ((DefaultAndroidPrivateKey) key).getJavaKey(); + if (javaKey instanceof RSAKey) { + return ((RSAKey) javaKey).getModulus().toByteArray(); + } + Log.w(TAG, "Not a RSAKey instance!"); + return null; + } + + @Override + public byte[] getDSAKeyParamQ(AndroidPrivateKey key) { + PrivateKey javaKey = ((DefaultAndroidPrivateKey) key).getJavaKey(); + if (javaKey instanceof DSAKey) { + DSAParams params = ((DSAKey) javaKey).getParams(); + return params.getQ().toByteArray(); + } + Log.w(TAG, "Not a DSAKey instance!"); + return null; + } + + @Override + public byte[] getECKeyOrder(AndroidPrivateKey key) { + PrivateKey javaKey = ((DefaultAndroidPrivateKey) key).getJavaKey(); + if (javaKey instanceof ECKey) { + ECParameterSpec params = ((ECKey) javaKey).getParams(); + return params.getOrder().toByteArray(); + } + Log.w(TAG, "Not an ECKey instance!"); + return null; + } + + @Override + public byte[] getPrivateKeyEncodedBytes(AndroidPrivateKey key) { + PrivateKey javaKey = ((DefaultAndroidPrivateKey) key).getJavaKey(); + return javaKey.getEncoded(); + } + + @Override + public byte[] rawSignDigestWithPrivateKey(AndroidPrivateKey key, + byte[] message) { + PrivateKey javaKey = ((DefaultAndroidPrivateKey) key).getJavaKey(); + // Get the Signature for this key. + Signature signature = null; + // Hint: Algorithm names come from: + // http://docs.oracle.com/javase/6/docs/technotes/guides/security/StandardNames.html + try { + if (javaKey instanceof RSAPrivateKey) { + // IMPORTANT: Due to a platform bug, this will throw NoSuchAlgorithmException + // on Android 4.0.x and 4.1.x. Fixed in 4.2 and higher. + // See https://android-review.googlesource.com/#/c/40352/ + signature = Signature.getInstance("NONEwithRSA"); + } else if (javaKey instanceof DSAPrivateKey) { + signature = Signature.getInstance("NONEwithDSA"); + } else if (javaKey instanceof ECPrivateKey) { + signature = Signature.getInstance("NONEwithECDSA"); + } + } catch (NoSuchAlgorithmException e) { + ; + } + + if (signature == null) { + Log.e(TAG, "Unsupported private key algorithm: " + javaKey.getAlgorithm()); + return null; + } + + // Sign the message. + try { + signature.initSign(javaKey); + signature.update(message); + return signature.sign(); + } catch (Exception e) { + Log.e(TAG, "Exception while signing message with " + javaKey.getAlgorithm() + + " private key: " + e); + return null; + } + } + + @Override + public int getPrivateKeyType(AndroidPrivateKey key) { + PrivateKey javaKey = ((DefaultAndroidPrivateKey) key).getJavaKey(); + if (javaKey instanceof RSAPrivateKey) + return PrivateKeyType.RSA; + if (javaKey instanceof DSAPrivateKey) + return PrivateKeyType.DSA; + if (javaKey instanceof ECPrivateKey) + return PrivateKeyType.ECDSA; + else + return PrivateKeyType.INVALID; + } + + @Override + public int getOpenSSLHandleForPrivateKey(AndroidPrivateKey key) { + PrivateKey javaKey = ((DefaultAndroidPrivateKey) key).getJavaKey(); + // Sanity checks + if (javaKey == null) { + Log.e(TAG, "key == null"); + return 0; + } + if (!(javaKey instanceof RSAPrivateKey)) { + Log.e(TAG, "does not implement RSAPrivateKey"); + return 0; + } + // First, check that this is a proper instance of OpenSSLRSAPrivateKey + // or one of its sub-classes. + Class<?> superClass; + try { + superClass = Class.forName( + "org.apache.harmony.xnet.provider.jsse.OpenSSLRSAPrivateKey"); + } catch (Exception e) { + // This may happen if the target device has a completely different + // implementation of the java.security APIs, compared to vanilla + // Android. Highly unlikely, but still possible. + Log.e(TAG, "Cannot find system OpenSSLRSAPrivateKey class: " + e); + return 0; + } + if (!superClass.isInstance(key)) { + // This may happen if the PrivateKey was not created by the "AndroidOpenSSL" + // provider, which should be the default. That could happen if an OEM decided + // to implement a different default provider. Also highly unlikely. + Log.e(TAG, "Private key is not an OpenSSLRSAPrivateKey instance, its class name is:" + + javaKey.getClass().getCanonicalName()); + return 0; + } + + try { + // Use reflection to invoke the 'getOpenSSLKey()' method on + // the private key. This returns another Java object that wraps + // a native EVP_PKEY. Note that the method is final, so calling + // the superclass implementation is ok. + Method getKey = superClass.getDeclaredMethod("getOpenSSLKey"); + getKey.setAccessible(true); + Object opensslKey = null; + try { + opensslKey = getKey.invoke(javaKey); + } finally { + getKey.setAccessible(false); + } + if (opensslKey == null) { + // Bail when detecting OEM "enhancement". + Log.e(TAG, "getOpenSSLKey() returned null"); + return 0; + } + + // Use reflection to invoke the 'getPkeyContext' method on the + // result of the getOpenSSLKey(). This is an 32-bit integer + // which is the address of an EVP_PKEY object. + Method getPkeyContext; + try { + getPkeyContext = opensslKey.getClass().getDeclaredMethod("getPkeyContext"); + } catch (Exception e) { + // Bail here too, something really not working as expected. + Log.e(TAG, "No getPkeyContext() method on OpenSSLKey member:" + e); + return 0; + } + getPkeyContext.setAccessible(true); + int evp_pkey = 0; + try { + evp_pkey = (Integer) getPkeyContext.invoke(opensslKey); + } finally { + getPkeyContext.setAccessible(false); + } + if (evp_pkey == 0) { + // The PrivateKey is probably rotten for some reason. + Log.e(TAG, "getPkeyContext() returned null"); + } + return evp_pkey; + + } catch (Exception e) { + Log.e(TAG, "Exception while trying to retrieve system EVP_PKEY handle: " + e); + return 0; + } + } + + @Override + public void releaseKey(AndroidPrivateKey key) { + // no-op for in-process. GC will handle key collection + } +} diff --git a/net/android/java/src/org/chromium/net/IRemoteAndroidKeyStore.aidl b/net/android/java/src/org/chromium/net/IRemoteAndroidKeyStore.aidl new file mode 100644 index 0000000..ee4c65d --- /dev/null +++ b/net/android/java/src/org/chromium/net/IRemoteAndroidKeyStore.aidl @@ -0,0 +1,33 @@ +// Copyright 2014 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 org.chromium.net.IRemoteAndroidKeyStoreCallbacks; + +/** + * Interface for communication with an Android KeyStore in another process. + */ +interface IRemoteAndroidKeyStore { + // Remote calls for SSlClientCertificateRequest - these allow retrieving + // the alias of the certificate to be used, its encoded chain and a handle + // for identifying a private key in the remote process. + String getClientCertificateAlias(); + byte[] getEncodedCertificateChain(in String alias); + int getPrivateKeyHandle(in String alias); + + // Registers callbacks for service->client communication. + void setClientCallbacks(IRemoteAndroidKeyStoreCallbacks callbacks); + + // Remote calls for AndroidKeyStore - these functions are performing operations + // with a PrivateKey in the remote process using the handle provided by + // |getPrivateKeyHandle|. + byte[] getRSAKeyModulus(in int handle); + byte[] getPrivateKeyEncodedBytes(in int handle); + byte[] getDSAKeyParamQ(in int handle); + byte[] getECKeyOrder(in int handle); + byte[] rawSignDigestWithPrivateKey(in int handle, in byte[] message); + int getPrivateKeyType(in int handle); + void releaseKey(in int handle); +} diff --git a/net/android/java/src/org/chromium/net/IRemoteAndroidKeyStoreCallbacks.aidl b/net/android/java/src/org/chromium/net/IRemoteAndroidKeyStoreCallbacks.aidl new file mode 100644 index 0000000..2c2564b --- /dev/null +++ b/net/android/java/src/org/chromium/net/IRemoteAndroidKeyStoreCallbacks.aidl @@ -0,0 +1,23 @@ +// Copyright 2014 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; + +/** + * Interface for communication from the remote authentication service back to the client. + */ +interface IRemoteAndroidKeyStoreCallbacks { + /** + * A critical failure has occurred and the service won't be able to recover. + * The client should unbind and optionally rebind at a later time. + */ + void onDisabled(); + + /** + * The service has started up and is fully initialized. This allows for the + * service to take some time to initialize. Remote calls shouldn't be invoked + * until this call has fired. + */ + void onInitComplete(); +} diff --git a/net/android/java/src/org/chromium/net/IRemoteAndroidKeyStoreInterface.aidl b/net/android/java/src/org/chromium/net/IRemoteAndroidKeyStoreInterface.aidl new file mode 100644 index 0000000..3b54d29 --- /dev/null +++ b/net/android/java/src/org/chromium/net/IRemoteAndroidKeyStoreInterface.aidl @@ -0,0 +1,6 @@ +// Copyright 2014 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. + +interface org.chomium.net.IRemoteAndroidKeyStore; +interface org.chromium.net.IRemoteAndroidKeyStoreCallbacks;
\ No newline at end of file diff --git a/net/android/java/src/org/chromium/net/RemoteAndroidKeyStore.java b/net/android/java/src/org/chromium/net/RemoteAndroidKeyStore.java new file mode 100644 index 0000000..e979036 --- /dev/null +++ b/net/android/java/src/org/chromium/net/RemoteAndroidKeyStore.java @@ -0,0 +1,139 @@ +// Copyright 2014 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.os.RemoteException; +import android.util.Log; + +/** + * Provides a remoted implementation of AndroidKeyStore where all calls are forwarded via + * binder to an external process. + */ +public class RemoteAndroidKeyStore implements AndroidKeyStore { + + private static final String TAG = "AndroidKeyStoreRemoteImpl"; + + private static class RemotePrivateKey implements AndroidPrivateKey { + // Reference to the key on a remote store. + final int mHandle; + // Key store handling this key. + final RemoteAndroidKeyStore mStore; + + RemotePrivateKey(int handle, RemoteAndroidKeyStore store) { + mHandle = handle; + mStore = store; + } + + public int getHandle() { + return mHandle; + } + + @Override + public AndroidKeyStore getKeyStore() { + return mStore; + } + } + + private final IRemoteAndroidKeyStore mRemoteManager; + + public RemoteAndroidKeyStore(IRemoteAndroidKeyStore manager) { + mRemoteManager = manager; + } + + @Override + public byte[] getRSAKeyModulus(AndroidPrivateKey key) { + RemotePrivateKey remoteKey = (RemotePrivateKey) key; + try { + Log.d(TAG, "getRSAKeyModulus"); + return mRemoteManager.getRSAKeyModulus(remoteKey.getHandle()); + } catch (RemoteException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public byte[] getDSAKeyParamQ(AndroidPrivateKey key) { + RemotePrivateKey remoteKey = (RemotePrivateKey) key; + try { + Log.d(TAG, "getDSAKeyParamQ"); + return mRemoteManager.getDSAKeyParamQ(remoteKey.getHandle()); + } catch (RemoteException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public byte[] getECKeyOrder(AndroidPrivateKey key) { + RemotePrivateKey remoteKey = (RemotePrivateKey) key; + try { + Log.d(TAG, "getECKeyOrder"); + return mRemoteManager.getECKeyOrder(remoteKey.getHandle()); + } catch (RemoteException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public byte[] rawSignDigestWithPrivateKey(AndroidPrivateKey key, byte[] message) { + RemotePrivateKey remoteKey = (RemotePrivateKey) key; + try { + Log.d(TAG, "rawSignDigestWithPrivateKey"); + return mRemoteManager.rawSignDigestWithPrivateKey(remoteKey.getHandle(), message); + } catch (RemoteException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public int getPrivateKeyType(AndroidPrivateKey key) { + RemotePrivateKey remoteKey = (RemotePrivateKey) key; + try { + Log.d(TAG, "getPrivateKeyType"); + return mRemoteManager.getPrivateKeyType(remoteKey.getHandle()); + } catch (RemoteException e) { + e.printStackTrace(); + return 0; + } + } + + @Override + public byte[] getPrivateKeyEncodedBytes(AndroidPrivateKey key) { + // This should not be called as it's only for older versions of Android. + assert false; + return null; + } + + @Override + public int getOpenSSLHandleForPrivateKey(AndroidPrivateKey privateKey) { + // This should not be called as it's only for older versions of Android. + assert false; + return 0; + } + + public AndroidPrivateKey createKey(String alias) { + try { + int handle = mRemoteManager.getPrivateKeyHandle(alias); + return new RemotePrivateKey(handle, this); + } catch (RemoteException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public void releaseKey(AndroidPrivateKey key) { + RemotePrivateKey remoteKey = (RemotePrivateKey) key; + try { + Log.d(TAG, "releaseKey"); + mRemoteManager.releaseKey(remoteKey.getHandle()); + } catch (RemoteException e) { + e.printStackTrace(); + } + } +} diff --git a/net/android/keystore.cc b/net/android/keystore.cc index a3d8cc1..cefd4f4 100644 --- a/net/android/keystore.cc +++ b/net/android/keystore.cc @@ -9,8 +9,8 @@ #include "base/android/jni_android.h" #include "base/android/jni_array.h" #include "base/logging.h" - #include "jni/AndroidKeyStore_jni.h" +#include "net/android/android_private_key.h" using base::android::AttachCurrentThread; using base::android::HasException; @@ -28,7 +28,9 @@ bool GetRSAKeyModulus( JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef<jbyteArray> modulus_ref = - Java_AndroidKeyStore_getRSAKeyModulus(env, private_key_ref); + Java_AndroidKeyStore_getRSAKeyModulus(env, + GetKeyStore(private_key_ref).obj(), + private_key_ref); if (modulus_ref.is_null()) return false; @@ -41,7 +43,10 @@ bool GetDSAKeyParamQ(jobject private_key_ref, JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef<jbyteArray> q_ref = - Java_AndroidKeyStore_getDSAKeyParamQ(env, private_key_ref); + Java_AndroidKeyStore_getDSAKeyParamQ( + env, + GetKeyStore(private_key_ref).obj(), + private_key_ref); if (q_ref.is_null()) return false; @@ -54,7 +59,11 @@ bool GetECKeyOrder(jobject private_key_ref, JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef<jbyteArray> order_ref = - Java_AndroidKeyStore_getECKeyOrder(env, private_key_ref); + Java_AndroidKeyStore_getECKeyOrder( + env, + GetKeyStore(private_key_ref).obj(), + private_key_ref); + if (order_ref.is_null()) return false; @@ -62,12 +71,15 @@ bool GetECKeyOrder(jobject private_key_ref, return true; } -bool GetPrivateKeyEncodedBytes(jobject private_key, +bool GetPrivateKeyEncodedBytes(jobject private_key_ref, std::vector<uint8>* result) { JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef<jbyteArray> encoded_ref = - Java_AndroidKeyStore_getPrivateKeyEncodedBytes(env, private_key); + Java_AndroidKeyStore_getPrivateKeyEncodedBytes( + env, + GetKeyStore(private_key_ref).obj(), + private_key_ref); if (encoded_ref.is_null()) return false; @@ -91,7 +103,10 @@ bool RawSignDigestWithPrivateKey( // Invoke platform API ScopedJavaLocalRef<jbyteArray> signature_ref = Java_AndroidKeyStore_rawSignDigestWithPrivateKey( - env, private_key_ref, digest_ref.obj()); + env, + GetKeyStore(private_key_ref).obj(), + private_key_ref, + digest_ref.obj()); if (HasException(env) || signature_ref.is_null()) return false; @@ -100,14 +115,16 @@ bool RawSignDigestWithPrivateKey( return true; } -PrivateKeyType GetPrivateKeyType(jobject private_key) { +PrivateKeyType GetPrivateKeyType(jobject private_key_ref) { JNIEnv* env = AttachCurrentThread(); int type = Java_AndroidKeyStore_getPrivateKeyType( - env, private_key); + env, + GetKeyStore(private_key_ref).obj(), + private_key_ref); return static_cast<PrivateKeyType>(type); } -EVP_PKEY* GetOpenSSLSystemHandleForPrivateKey(jobject private_key) { +EVP_PKEY* GetOpenSSLSystemHandleForPrivateKey(jobject private_key_ref) { JNIEnv* env = AttachCurrentThread(); // Note: the pointer is passed as a jint here because that's how it // is stored in the Java object. Java doesn't have a primitive type @@ -117,11 +134,21 @@ EVP_PKEY* GetOpenSSLSystemHandleForPrivateKey(jobject private_key) { // Given that this routine shall only be called on Android < 4.2, // this won't be a problem in the far future (e.g. when Android gets // ported to 64-bit environments, if ever). - int pkey = - Java_AndroidKeyStore_getOpenSSLHandleForPrivateKey(env, private_key); + int pkey = Java_AndroidKeyStore_getOpenSSLHandleForPrivateKey( + env, + GetKeyStore(private_key_ref).obj(), + private_key_ref); return reinterpret_cast<EVP_PKEY*>(pkey); } +void ReleaseKey(jobject private_key_ref) { + JNIEnv* env = AttachCurrentThread(); + Java_AndroidKeyStore_releaseKey(env, + GetKeyStore(private_key_ref).obj(), + private_key_ref); + env->DeleteGlobalRef(private_key_ref); +} + bool RegisterKeyStore(JNIEnv* env) { return RegisterNativesImpl(env); } diff --git a/net/android/keystore.h b/net/android/keystore.h index f14fa87..ac3babe 100644 --- a/net/android/keystore.h +++ b/net/android/keystore.h @@ -108,6 +108,8 @@ NET_EXPORT PrivateKeyType GetPrivateKeyType(jobject private_key); // the returned key's reference count. EVP_PKEY* GetOpenSSLSystemHandleForPrivateKey(jobject private_key); +NET_EXPORT void ReleaseKey(jobject private_key); + // Register JNI methods NET_EXPORT bool RegisterKeyStore(JNIEnv* env); diff --git a/net/android/keystore_openssl.cc b/net/android/keystore_openssl.cc index 5ad8473..afdca30 100644 --- a/net/android/keystore_openssl.cc +++ b/net/android/keystore_openssl.cc @@ -208,8 +208,7 @@ int RsaMethodFinish(RSA* rsa) { jobject key = reinterpret_cast<jobject>(RSA_get_app_data(rsa)); if (key != NULL) { RSA_set_app_data(rsa, NULL); - JNIEnv* env = base::android::AttachCurrentThread(); - env->DeleteGlobalRef(key); + ReleaseKey(key); } // Actual return value is ignored by OpenSSL. There are no docs // explaining what this is supposed to be. @@ -413,8 +412,7 @@ int DsaMethodFinish(DSA* dsa) { jobject key = reinterpret_cast<jobject>(DSA_get_ex_data(dsa,0)); if (key != NULL) { DSA_set_ex_data(dsa, 0, NULL); - JNIEnv* env = base::android::AttachCurrentThread(); - env->DeleteGlobalRef(key); + ReleaseKey(key); } // Actual return value is ignored by OpenSSL. There are no docs // explaining what this is supposed to be. @@ -493,9 +491,7 @@ void ExDataFree(void* parent, return; CRYPTO_set_ex_data(ad, idx, NULL); - - JNIEnv* env = base::android::AttachCurrentThread(); - env->DeleteGlobalRef(private_key); + ReleaseKey(private_key); } int ExDataDup(CRYPTO_EX_DATA* to, diff --git a/net/android/net_jni_registrar.cc b/net/android/net_jni_registrar.cc index 94c849a..b3e9437 100644 --- a/net/android/net_jni_registrar.cc +++ b/net/android/net_jni_registrar.cc @@ -7,6 +7,7 @@ #include "base/basictypes.h" #include "base/android/jni_android.h" #include "base/android/jni_registrar.h" +#include "net/android/android_private_key.h" #include "net/android/gurl_utils.h" #include "net/android/keystore.h" #include "net/android/network_change_notifier_android.h" @@ -19,6 +20,7 @@ namespace android { static base::android::RegistrationMethod kNetRegisteredMethods[] = { { "AndroidCertVerifyResult", net::android::RegisterCertVerifyResult }, + { "AndroidPrivateKey", net::android::RegisterAndroidPrivateKey}, { "AndroidKeyStore", net::android::RegisterKeyStore }, { "AndroidNetworkLibrary", net::android::RegisterNetworkLibrary }, { "GURLUtils", net::RegisterGURLUtils }, diff --git a/net/net.gyp b/net/net.gyp index 348c134..d8a1d56 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -65,6 +65,8 @@ 'android/cert_verify_status_android_list.h', 'android/gurl_utils.cc', 'android/gurl_utils.h', + 'android/android_private_key.cc', + 'android/android_private_key.h', 'android/keystore.cc', 'android/keystore.h', 'android/keystore_openssl.cc', @@ -3035,6 +3037,7 @@ 'android/java/src/org/chromium/net/AndroidCertVerifyResult.java', 'android/java/src/org/chromium/net/AndroidKeyStore.java', 'android/java/src/org/chromium/net/AndroidNetworkLibrary.java', + 'android/java/src/org/chromium/net/AndroidPrivateKey.java', 'android/java/src/org/chromium/net/GURLUtils.java', 'android/java/src/org/chromium/net/NetworkChangeNotifier.java', 'android/java/src/org/chromium/net/ProxyChangeListener.java', @@ -3070,10 +3073,25 @@ 'certificate_mime_types_java', 'net_errors_java', 'private_key_types_java', + 'remote_android_keystore_aidl', ], 'includes': [ '../build/java.gypi' ], }, { + # Processes the interface files for communication with an Android KeyStore + # running in a separate process. + 'target_name': 'remote_android_keystore_aidl', + 'type': 'none', + 'variables': { + 'aidl_interface_file': '../net/android/java/src/org/chromium/net/IRemoteAndroidKeyStoreInterface.aidl', + }, + 'sources': [ + '../net/android/java/src/org/chromium/net/IRemoteAndroidKeyStore.aidl', + '../net/android/java/src/org/chromium/net/IRemoteAndroidKeyStoreCallbacks.aidl', + ], + 'includes': [ '../build/java_aidl.gypi' ], + }, + { 'target_name': 'net_java_test_support', 'type': 'none', 'variables': { |