diff options
22 files changed, 2325 insertions, 2 deletions
diff --git a/base/android/jni_array.h b/base/android/jni_array.h index fbc131e..b5050c3 100644 --- a/base/android/jni_array.h +++ b/base/android/jni_array.h @@ -62,7 +62,7 @@ BASE_EXPORT void JavaIntArrayToIntVector( // Assuming |array| is an byte[][] (array of byte arrays), replaces the // content of |out| with the corresponding vector of strings. No UTF-8 // conversion is performed. -void JavaArrayOfByteArrayToStringVector( +BASE_EXPORT void JavaArrayOfByteArrayToStringVector( JNIEnv* env, jobjectArray array, std::vector<std::string>* out); diff --git a/crypto/openssl_util.h b/crypto/openssl_util.h index b390fe7..e8483c96 100644 --- a/crypto/openssl_util.h +++ b/crypto/openssl_util.h @@ -11,7 +11,7 @@ namespace crypto { -// A helper class that takes care of destroying OpenSSL objects when it goes out +// A helper class that takes care of destroying OpenSSL objects when they go out // of scope. template <typename T, void (*destructor)(T*)> class ScopedOpenSSL { @@ -23,6 +23,11 @@ class ScopedOpenSSL { } T* get() const { return ptr_; } + T* release() { + T* ptr = ptr_; + ptr_ = NULL; + return ptr; + } void reset(T* ptr) { if (ptr != ptr_) { if (ptr_) (*destructor)(ptr_); diff --git a/net/android/java/PrivateKeyType.template b/net/android/java/PrivateKeyType.template new file mode 100644 index 0000000..aa7f76f --- /dev/null +++ b/net/android/java/PrivateKeyType.template @@ -0,0 +1,10 @@ +// Copyright (c) 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; + +public class PrivateKeyType { +#define DEFINE_PRIVATE_KEY_TYPE(name,value) public static final int name = value; +#include "net/android/private_key_type_list.h" +} 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 0000000..2edaa89 --- /dev/null +++ b/net/android/java/src/org/chromium/net/AndroidKeyStore.java @@ -0,0 +1,308 @@ +// Copyright (c) 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.InvocationTargetException; +import java.lang.reflect.Method; +import java.math.BigInteger; +import java.security.interfaces.DSAKey; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAParams; +import java.security.interfaces.ECKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.RSAKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Signature; +import java.security.spec.ECParameterSpec; + +import org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; +import org.chromium.net.PrivateKeyType;; + +@JNINamespace("net::android") +public class AndroidKeyStore { + + private static final String TAG = "AndroidKeyStore"; + + //////////////////////////////////////////////////////////////////// + // + // Message signing support. + + /** + * Returns the public modulus of a given RSA private key as a byte + * buffer. + * This can be used by native code to convert the modulus into + * an OpenSSL BIGNUM object. Required to craft a custom native RSA + * object where RSA_size() works as expected. + * + * @param key A PrivateKey instance, must implement RSAKey. + * @return A byte buffer corresponding to the modulus. This is + * 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; + } + } + + /** + * Returns the 'Q' parameter of a given DSA private key as a byte + * buffer. + * This can be used by native code to convert it into an OpenSSL BIGNUM + * object where DSA_size() works as expected. + * + * @param key A PrivateKey instance. Must implement DSAKey. + * @return A byte buffer corresponding to the Q parameter. This is + * 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; + } + } + + /** + * Returns the 'order' parameter of a given ECDSA private key as a + * a byte buffer. + * @param key A PrivateKey instance. Must implement ECKey. + * @return A byte buffer corresponding to the 'order' parameter. + * 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; + } + } + + /** + * Returns the encoded data corresponding to a given PrivateKey. + * Note that this will fail for platform keys on Android 4.0.4 + * and higher. It can be used on 4.0.3 and older platforms to + * route around the platform bug described below. + * @param key A PrivateKey instance + * @return encoded key as PKCS#8 byte array, can be null. + */ + @CalledByNative + public static byte[] getPrivateKeyEncodedBytes(PrivateKey key) { + return key.getEncoded(); + } + + /** + * Sign a given message with a given PrivateKey object. This method + * shall only be used to implement signing in the context of SSL + * client certificate support. + * + * The message will actually be a hash, computed and padded by OpenSSL, + * itself, depending on the type of the key. The result should match + * exactly what the vanilla implementations of the following OpenSSL + * function calls do: + * + * - For a RSA private key, this should be equivalent to calling + * RSA_sign(NDI_md5_sha1,....), i.e. it must generate a raw RSA + * signature. The message must a combined, 36-byte MD5+SHA1 message + * digest padded to the length of the modulus using PKCS#1 padding. + * + * - For a DSA and ECDSA private keys, this should be equivalent to + * calling DSA_sign(0,...) and ECDSA_sign(0,...) respectively. The + * message must be a 20-byte SHA1 hash and the function shall + * compute a direct DSA/ECDSA signature for it. + * + * @param privateKey The PrivateKey handle. + * @param message The message to sign. + * @return signature as a byte buffer. + * + * Important: Due to a platform bug, this function will always fail on + * Android < 4.2 for RSA PrivateKey objects. See the + * 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; + } + } + + /** + * 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 + * @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; + } + + /** + * Return the system EVP_PKEY handle corresponding to a given PrivateKey + * object, obtained through reflection. + * + * This shall only be used when the "NONEwithRSA" signature is not + * available, as described in rawSignDigestWithPrivateKey(). I.e. + * never use this on Android 4.2 or higher. + * + * This can only work in Android 4.0.4 and higher, for older versions + * of the platform (e.g. 4.0.3), there is no system OpenSSL EVP_PKEY, + * but the private key contents can be retrieved directly with + * the getEncoded() method. + * + * This assumes that the target device uses a vanilla AOSP + * implementation of its java.security classes, which is also + * based on OpenSSL (fortunately, no OEM has apperently changed to + * a different implementation, according to the Android team). + * + * Note that the object returned was created with the platform version + * of OpenSSL, and _not_ the one that comes with Chromium. Whether the + * object can be used safely with the Chromium OpenSSL library depends + * on differences between their actual ABI / implementation details. + * + * To better understand what's going on below, please refer to the + * following source files in the Android 4.0.4 and 4.1 source trees: + * 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. + * @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; + } + + 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; + } + } +} diff --git a/net/android/javatests/src/org/chromium/net/AndroidKeyStoreTestUtil.java b/net/android/javatests/src/org/chromium/net/AndroidKeyStoreTestUtil.java new file mode 100644 index 0000000..460dc50 --- /dev/null +++ b/net/android/javatests/src/org/chromium/net/AndroidKeyStoreTestUtil.java @@ -0,0 +1,67 @@ +// Copyright (c) 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.os.Build; +import android.util.Log; + +import java.security.PrivateKey; +import java.security.PrivateKey; +import java.security.Signature; +import java.security.KeyFactory; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.KeyStoreException; +import java.security.spec.InvalidKeySpecException; +import java.security.NoSuchAlgorithmException; + +import org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; +import org.chromium.net.PrivateKeyType; + +@JNINamespace("net::android") +public class AndroidKeyStoreTestUtil { + + private static final String TAG = "AndroidKeyStoreTestUtil"; + + /** + * Called from native code to create a PrivateKey object from its + * encoded PKCS#8 representation. + * @param type The key type, accoding to PrivateKeyType. + * @return new PrivateKey handle, or null in case of error. + */ + @CalledByNative + public static PrivateKey createPrivateKeyFromPKCS8(int type, + byte[] encoded_key) { + String algorithm = null; + switch (type) { + case PrivateKeyType.RSA: + algorithm = "RSA"; + break; + case PrivateKeyType.DSA: + algorithm = "DSA"; + break; + case PrivateKeyType.ECDSA: + algorithm = "EC"; + break; + default: + return null; + } + + try { + KeyFactory factory = KeyFactory.getInstance(algorithm); + KeySpec ks = new PKCS8EncodedKeySpec(encoded_key); + PrivateKey key = factory.generatePrivate(ks); + return key; + + } catch (NoSuchAlgorithmException e) { + Log.e(TAG, "Could not create " + algorithm + " factory instance!"); + return null; + } catch (InvalidKeySpecException e) { + Log.e(TAG, "Could not load " + algorithm + " private key from bytes!"); + return null; + } + } +} diff --git a/net/android/keystore.cc b/net/android/keystore.cc new file mode 100644 index 0000000..a3d8cc1 --- /dev/null +++ b/net/android/keystore.cc @@ -0,0 +1,130 @@ +// Copyright (c) 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. + +#include "net/android/keystore.h" + +#include <vector> + +#include "base/android/jni_android.h" +#include "base/android/jni_array.h" +#include "base/logging.h" + +#include "jni/AndroidKeyStore_jni.h" + +using base::android::AttachCurrentThread; +using base::android::HasException; +using base::android::JavaByteArrayToByteVector; +using base::android::ScopedJavaLocalRef; +using base::android::ToJavaByteArray; +using base::android::JavaArrayOfByteArrayToStringVector; + +namespace net { +namespace android { + +bool GetRSAKeyModulus( + jobject private_key_ref, + std::vector<uint8>* result) { + JNIEnv* env = AttachCurrentThread(); + + ScopedJavaLocalRef<jbyteArray> modulus_ref = + Java_AndroidKeyStore_getRSAKeyModulus(env, private_key_ref); + if (modulus_ref.is_null()) + return false; + + JavaByteArrayToByteVector(env, modulus_ref.obj(), result); + return true; +} + +bool GetDSAKeyParamQ(jobject private_key_ref, + std::vector<uint8>* result) { + JNIEnv* env = AttachCurrentThread(); + + ScopedJavaLocalRef<jbyteArray> q_ref = + Java_AndroidKeyStore_getDSAKeyParamQ(env, private_key_ref); + if (q_ref.is_null()) + return false; + + JavaByteArrayToByteVector(env, q_ref.obj(), result); + return true; +} + +bool GetECKeyOrder(jobject private_key_ref, + std::vector<uint8>* result) { + JNIEnv* env = AttachCurrentThread(); + + ScopedJavaLocalRef<jbyteArray> order_ref = + Java_AndroidKeyStore_getECKeyOrder(env, private_key_ref); + if (order_ref.is_null()) + return false; + + JavaByteArrayToByteVector(env, order_ref.obj(), result); + return true; +} + +bool GetPrivateKeyEncodedBytes(jobject private_key, + std::vector<uint8>* result) { + JNIEnv* env = AttachCurrentThread(); + + ScopedJavaLocalRef<jbyteArray> encoded_ref = + Java_AndroidKeyStore_getPrivateKeyEncodedBytes(env, private_key); + if (encoded_ref.is_null()) + return false; + + JavaByteArrayToByteVector(env, encoded_ref.obj(), result); + return true; +} + +bool RawSignDigestWithPrivateKey( + jobject private_key_ref, + const base::StringPiece& digest, + std::vector<uint8>* signature) { + JNIEnv* env = AttachCurrentThread(); + + // Convert message to byte[] array. + ScopedJavaLocalRef<jbyteArray> digest_ref = + ToJavaByteArray(env, + reinterpret_cast<const uint8*>(digest.data()), + digest.length()); + DCHECK(!digest_ref.is_null()); + + // Invoke platform API + ScopedJavaLocalRef<jbyteArray> signature_ref = + Java_AndroidKeyStore_rawSignDigestWithPrivateKey( + env, private_key_ref, digest_ref.obj()); + if (HasException(env) || signature_ref.is_null()) + return false; + + // Write signature to string. + JavaByteArrayToByteVector(env, signature_ref.obj(), signature); + return true; +} + +PrivateKeyType GetPrivateKeyType(jobject private_key) { + JNIEnv* env = AttachCurrentThread(); + int type = Java_AndroidKeyStore_getPrivateKeyType( + env, private_key); + return static_cast<PrivateKeyType>(type); +} + +EVP_PKEY* GetOpenSSLSystemHandleForPrivateKey(jobject private_key) { + 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 + // like intptr_t that matches the size of pointers on the host + // machine, and Android only runs on 32-bit CPUs. + // + // 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); + return reinterpret_cast<EVP_PKEY*>(pkey); +} + +bool RegisterKeyStore(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace net diff --git a/net/android/keystore.h b/net/android/keystore.h new file mode 100644 index 0000000..58a994b --- /dev/null +++ b/net/android/keystore.h @@ -0,0 +1,117 @@ +// Copyright (c) 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. + +#ifndef NET_ANDROID_KEYSTORE_H +#define NET_ANDROID_KEYSTORE_H + +#include <jni.h> + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/string_piece.h" +#include "net/base/net_export.h" +#include "net/base/ssl_client_cert_type.h" + +// Avoid including <openssl/evp.h> here. +typedef struct evp_pkey_st EVP_PKEY; + +// Misc functions to access the Android platform KeyStore. + +namespace net { +namespace android { + +// Define a list of constants describing private key types. The +// values are shared with Java through org.chromium.net.PrivateKeyType. +// Example: PRIVATE_KEY_TYPE_RSA. +enum PrivateKeyType { +#define DEFINE_PRIVATE_KEY_TYPE(name,value) PRIVATE_KEY_TYPE_ ## name = value, +#include "net/android/private_key_type_list.h" +#undef DEFINE_PRIVATE_KEY_TYPE +}; + +// Returns the modulus of a given RSAPrivateKey platform object, +// as a series of bytes, in big-endian representation. This can be +// used with BN_bin2bn() to convert to an OpenSSL BIGNUM. +// +// |private_key| is a JNI reference for the private key. +// |modulus| will receive the modulus bytes on success. +// Returns true on success, or false on failure (e.g. if the key +// is not RSA). +bool GetRSAKeyModulus(jobject private_key, + std::vector<uint8>* modulus); + +// Returns the Q parameter of a given DSAPrivateKey platform object, +// as a series of bytes, in big-endian representation. This can be used +// with BN_bin2bn() to convert to an OpenSSL BIGNUM. +// |private_key| is a JNI reference for the private key. +// |q| will receive the result bytes on success. +// Returns true on success, or false on failure (e.g. if the key is +// not DSA). +bool GetDSAKeyParamQ(jobject private_key, + std::vector<uint8>* q); + +// Returns the order parameter of a given ECPrivateKey platform object, +// as a series of bytes, in big-endian representation. This can be used +// with BN_bin2bn() to convert to an OpenSSL BIGNUM. +// |private_key| is a JNI reference for the private key. +// |order| will receive the result bytes on success. +// Returns true on success, or false on failure (e.g. if the key is +// not EC). +bool GetECKeyOrder(jobject private_key, + std::vector<uint8>* order); + +// Returns the encoded PKCS#8 representation of a private key. +// This only works on Android 4.0.3 and older releases for platform keys +// (i.e. all keys except those explicitely generated by the application). +// |private_key| is a JNI reference for the private key. +// |encoded| will receive the encoded data on success. +// Returns true on success, or false on failure (e.g. on 4.0.4 or higher). +bool GetPrivateKeyEncodedBytes(jobject private_key, + std::vector<uint8>* encoded); + +// Compute the signature of a given message, which is actually a hash, +// using a private key. For more details, please read the comments for the +// rawSignDigestWithPrivateKey method in AndroidKeyStore.java. +// +// |private_key| is a JNI reference for the private key. +// |digest| is the input digest. +// |signature| will receive the signature on success. +// Returns true on success, false on failure. +// +bool RawSignDigestWithPrivateKey( + jobject private_key, + const base::StringPiece& digest, + std::vector<uint8>* signature); + + +// Return the PrivateKeyType of a given private key. +// |private_key| is a JNI reference for the private key. +// Returns a PrivateKeyType, while will be CLIENT_CERT_INVALID_TYPE +// on error. +PrivateKeyType GetPrivateKeyType(jobject private_key); + +// Returns a handle to the system EVP_PKEY object used to back a given +// private_key object. This must *only* be used for RSA private keys +// on Android < 4.2. Technically, this is only guaranteed to work if +// the system image contains a vanilla implementation of the Java +// API frameworks based on Harmony + OpenSSL. +// +// |private_key| is a JNI reference for the private key. +// Returns an EVP_PKEY* handle, or NULL in case of error. +// +// Note: Despite its name and return type, this function doesn't know +// anything about OpenSSL, it just type-casts a system pointer that +// is passed as an int through JNI. As such, it never increments +// the returned key's reference count. +EVP_PKEY* GetOpenSSLSystemHandleForPrivateKey(jobject private_key); + +// Register JNI methods +NET_EXPORT bool RegisterKeyStore(JNIEnv* env); + +} // namespace android +} // namespace net + +#endif // NET_ANDROID_KEYSTORE_H diff --git a/net/android/keystore_openssl.cc b/net/android/keystore_openssl.cc new file mode 100644 index 0000000..900bf85 --- /dev/null +++ b/net/android/keystore_openssl.cc @@ -0,0 +1,699 @@ +// Copyright (c) 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. + +#include "net/android/keystore_openssl.h" + +#include <jni.h> +#include <openssl/bn.h> +// This include is required to get the ECDSA_METHOD structure definition +// which isn't currently part of the OpenSSL official ABI. This should +// not be a concern for Chromium which always links against its own +// version of the library on Android. +#include <openssl/crypto/ecdsa/ecs_locl.h> +// And this one is needed for the EC_GROUP definition. +#include <openssl/crypto/ec/ec_lcl.h> +#include <openssl/dsa.h> +#include <openssl/ec.h> +#include <openssl/engine.h> +#include <openssl/evp.h> +#include <openssl/rsa.h> + +#include "base/android/build_info.h" +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "crypto/openssl_util.h" +#include "net/android/keystore.h" +#include "net/base/ssl_client_cert_type.h" + +// IMPORTANT NOTE: The following code will currently only work when used +// to implement client certificate support with OpenSSL. That's because +// only the signing operations used in this use case are implemented here. +// +// Generally speaking, OpenSSL provides many different ways to sign +// digests. This code doesn't support all these cases, only the ones that +// are required to sign the MAC during the OpenSSL handshake for TLS < 1.2. +// +// The OpenSSL EVP_PKEY type is a generic wrapper around key pairs. +// Internally, it can hold a pointer to a RSA, DSA or ECDSA structure, +// which model keypair implementations of each respective crypto +// algorithm. +// +// The RSA type has a 'method' field pointer to a vtable-like structure +// called a RSA_METHOD. This contains several function pointers that +// correspond to operations on RSA keys (e.g. decode/encode with public +// key, decode/encode with private key, signing, validation), as well as +// a few flags. +// +// For example, the RSA_sign() function will call "method->rsa_sign()" if +// method->rsa_sign is not NULL, otherwise, it will perform a regular +// signing operation using the other fields in the RSA structure (which +// are used to hold the typical modulus / exponent / parameters for the +// key pair). +// +// This source file thus defines a custom RSA_METHOD structure, which +// fields points to static methods used to implement the corresponding +// RSA operation using platform Android APIs. +// +// However, the platform APIs require a jobject JNI reference to work. +// It must be stored in the RSA instance, or made accessible when the +// custom RSA methods are called. This is done by using RSA_set_app_data() +// and RSA_get_app_data(). +// +// One can thus _directly_ create a new EVP_PKEY that uses a custom RSA +// object with the following: +// +// RSA* rsa = RSA_new() +// RSA_set_method(&custom_rsa_method); +// RSA_set_app_data(rsa, jni_private_key); +// +// EVP_PKEY* pkey = EVP_PKEY_new(); +// EVP_PKEY_assign_RSA(pkey, rsa); +// +// Note that because EVP_PKEY_assign_RSA() is used, instead of +// EVP_PKEY_set1_RSA(), the new EVP_PKEY now owns the RSA object, and +// will destroy it when it is itself destroyed. +// +// Unfortunately, such objects cannot be used with RSA_size(), which +// totally ignores the RSA_METHOD pointers. Instead, it is necessary +// to manually setup the modulus field (n) in the RSA object, with a +// value that matches the wrapped PrivateKey object. See GetRsaPkeyWrapper +// for full details. +// +// Similarly, custom DSA_METHOD and ECDSA_METHOD are defined by this source +// file, and appropriate field setups are performed to ensure that +// DSA_size() and ECDSA_size() work properly with the wrapper EVP_PKEY. +// +// Note that there is no need to define an OpenSSL ENGINE here. These +// are objects that can be used to expose custom methods (i.e. either +// RSA_METHOD, DSA_METHOD, ECDSA_METHOD, and a large number of other ones +// for types not related to this source file), and make them used by +// default for a lot of operations. Very fortunately, this is not needed +// here, which saves a lot of complexity. + +using base::android::ScopedJavaGlobalRef; + +namespace net { +namespace android { + +namespace { + +typedef crypto::ScopedOpenSSL<EVP_PKEY, EVP_PKEY_free> ScopedEVP_PKEY; +typedef crypto::ScopedOpenSSL<RSA, RSA_free> ScopedRSA; +typedef crypto::ScopedOpenSSL<DSA, DSA_free> ScopedDSA; +typedef crypto::ScopedOpenSSL<EC_KEY, EC_KEY_free> ScopedEC_KEY; +typedef crypto::ScopedOpenSSL<EC_GROUP, EC_GROUP_free> ScopedEC_GROUP; + +// Custom RSA_METHOD that uses the platform APIs. +// Note that for now, only signing through RSA_sign() is really supported. +// all other method pointers are either stubs returning errors, or no-ops. +// See <openssl/rsa.h> for exact declaration of RSA_METHOD. + +int RsaMethodPubEnc(int flen, + const unsigned char* from, + unsigned char* to, + RSA* rsa, + int padding) { + NOTIMPLEMENTED(); + RSAerr(RSA_F_RSA_PUBLIC_ENCRYPT, RSA_R_RSA_OPERATIONS_NOT_SUPPORTED); + return -1; +} + +int RsaMethodPubDec(int flen, + const unsigned char* from, + unsigned char* to, + RSA* rsa, + int padding) { + NOTIMPLEMENTED(); + RSAerr(RSA_F_RSA_PUBLIC_DECRYPT, RSA_R_RSA_OPERATIONS_NOT_SUPPORTED); + return -1; +} + +int RsaMethodPrivEnc(int flen, + const unsigned char *from, + unsigned char *to, + RSA *rsa, + int padding) { + NOTIMPLEMENTED(); + RSAerr(RSA_F_RSA_PRIVATE_ENCRYPT, RSA_R_RSA_OPERATIONS_NOT_SUPPORTED); + return -1; +} + +int RsaMethodPrivDec(int flen, + const unsigned char* from, + unsigned char* to, + RSA* rsa, + int padding) { + NOTIMPLEMENTED(); + RSAerr(RSA_F_RSA_PRIVATE_DECRYPT, RSA_R_RSA_OPERATIONS_NOT_SUPPORTED); + return -1; +} + +int RsaMethodInit(RSA* rsa) { + // Required to ensure that RsaMethodSign will be called. + rsa->flags |= RSA_FLAG_SIGN_VER; + return 0; +} + +int RsaMethodFinish(RSA* rsa) { + // Ensure the global JNI reference created with this wrapper is + // properly destroyed with it. + 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); + } + // Actual return value is ignored by OpenSSL. There are no docs + // explaining what this is supposed to be. + return 0; +} + +int RsaMethodSign(int type, + const unsigned char* message, + unsigned int message_len, + unsigned char* signature, + unsigned int* signature_len, + const RSA* rsa) { + // This is only used for client certificate support, which + // will always pass the NID_md5_sha1 |type| value. + DCHECK_EQ(NID_md5_sha1, type); + if (type != NID_md5_sha1) { + RSAerr(RSA_F_RSA_SIGN, RSA_R_UNKNOWN_ALGORITHM_TYPE); + return 0; + } + // Retrieve private key JNI reference. + jobject private_key = reinterpret_cast<jobject>(RSA_get_app_data(rsa)); + if (!private_key) { + LOG(WARNING) << "Null JNI reference passed to RsaMethodSign!"; + return 0; + } + // Sign message with it through JNI. + base::StringPiece message_piece(reinterpret_cast<const char*>(message), + static_cast<size_t>(message_len)); + std::vector<uint8> result; + + if (!RawSignDigestWithPrivateKey( + private_key, message_piece, &result)) { + LOG(WARNING) << "Could not sign message in RsaMethodSign!"; + return 0; + } + + size_t expected_size = static_cast<size_t>(RSA_size(rsa)); + if (result.size() > expected_size) { + LOG(ERROR) << "RSA Signature size mismatch, actual: " + << result.size() << ", expected <= " << expected_size; + return 0; + } + + // Copy result to OpenSSL-provided buffer + memcpy(signature, &result[0], result.size()); + *signature_len = static_cast<unsigned int>(result.size()); + return 1; +} + +const RSA_METHOD android_rsa_method = { + /* .name = */ "Android signing-only RSA method", + /* .rsa_pub_enc = */ RsaMethodPubEnc, + /* .rsa_pub_dec = */ RsaMethodPubDec, + /* .rsa_priv_enc = */ RsaMethodPrivEnc, + /* .rsa_priv_dec = */ RsaMethodPrivDec, + /* .rsa_mod_exp = */ NULL, + /* .bn_mod_exp = */ NULL, + /* .init = */ RsaMethodInit, + /* .finish = */ RsaMethodFinish, + // This flag is necessary to tell OpenSSL to avoid checking the content + // (i.e. internal fields) of the private key. Otherwise, it will complain + // it's not valid for the certificate. + /* .flags = */ RSA_METHOD_FLAG_NO_CHECK, + /* .app_data = */ NULL, + /* .rsa_sign = */ RsaMethodSign, + /* .rsa_verify = */ NULL, + /* .rsa_keygen = */ NULL, +}; + +// Copy the contents of an encoded big integer into an existing BIGNUM. +// This function modifies |*num| in-place. +// |new_bytes| is the byte encoding of the new value. +// |num| points to the BIGNUM which will be assigned with the new value. +// Returns true on success, false otherwise. On failure, |*num| is +// not modified. +bool CopyBigNumFromBytes(const std::vector<uint8>& new_bytes, + BIGNUM* num) { + BIGNUM* ret = BN_bin2bn( + reinterpret_cast<const unsigned char*>(&new_bytes[0]), + static_cast<int>(new_bytes.size()), + num); + return (ret != NULL); +} + +// Decode the contents of an encoded big integer and either create a new +// BIGNUM object (if |*num_ptr| is NULL on input) or copy it (if +// |*num_ptr| is not NULL). +// |new_bytes| is the byte encoding of the new value. +// |num_ptr| is the address of a BIGNUM pointer. |*num_ptr| can be NULL. +// Returns true on success, false otherwise. On failure, |*num_ptr| is +// not modified. On success, |*num_ptr| will always be non-NULL and +// point to a valid BIGNUM object. +bool SwapBigNumPtrFromBytes(const std::vector<uint8>& new_bytes, + BIGNUM** num_ptr) { + BIGNUM* old_num = *num_ptr; + BIGNUM* new_num = BN_bin2bn( + reinterpret_cast<const unsigned char*>(&new_bytes[0]), + static_cast<int>(new_bytes.size()), + old_num); + if (new_num == NULL) + return false; + + if (old_num == NULL) + *num_ptr = new_num; + return true; +} + +// Setup an EVP_PKEY to wrap an existing platform RSA PrivateKey object. +// |private_key| is the JNI reference (local or global) to the object. +// |pkey| is the EVP_PKEY to setup as a wrapper. +// Returns true on success, false otherwise. +// On success, this creates a new global JNI reference to the object +// that is owned by and destroyed with the EVP_PKEY. I.e. caller can +// free |private_key| after the call. +// IMPORTANT: The EVP_PKEY will *only* work on Android >= 4.2. For older +// platforms, use GetRsaLegacyKey() instead. +bool GetRsaPkeyWrapper(jobject private_key, EVP_PKEY* pkey) { + ScopedRSA rsa(RSA_new()); + RSA_set_method(rsa.get(), &android_rsa_method); + + // HACK: RSA_size() doesn't work with custom RSA_METHODs. To ensure that + // it will return the right value, set the 'n' field of the RSA object + // to match the private key's modulus. + std::vector<uint8> modulus; + if (!GetRSAKeyModulus(private_key, &modulus)) { + LOG(ERROR) << "Failed to get private key modulus"; + return false; + } + if (!SwapBigNumPtrFromBytes(modulus, &rsa.get()->n)) { + LOG(ERROR) << "Failed to decode private key modulus"; + return false; + } + + ScopedJavaGlobalRef<jobject> global_key; + global_key.Reset(NULL, private_key); + if (global_key.is_null()) { + LOG(ERROR) << "Could not create global JNI reference"; + return false; + } + RSA_set_app_data(rsa.get(), global_key.Release()); + EVP_PKEY_assign_RSA(pkey, rsa.release()); + return true; +} + +// Setup an EVP_PKEY to wrap an existing platform RSA PrivateKey object +// for Android 4.0 to 4.1.x. Must only be used on Android < 4.2. +// |private_key| is a JNI reference (local or global) to the object. +// |pkey| is the EVP_PKEY to setup as a wrapper. +// Returns true on success, false otherwise. +EVP_PKEY* GetRsaLegacyKey(jobject private_key) { + EVP_PKEY* sys_pkey = + GetOpenSSLSystemHandleForPrivateKey(private_key); + if (sys_pkey != NULL) { + CRYPTO_add(&sys_pkey->references, 1, CRYPTO_LOCK_EVP_PKEY); + } else { + // GetOpenSSLSystemHandleForPrivateKey() will fail on Android + // 4.0.3 and earlier. However, it is possible to get the key + // content with PrivateKey.getEncoded() on these platforms. + // Note that this method may return NULL on 4.0.4 and later. + std::vector<uint8> encoded; + if (!GetPrivateKeyEncodedBytes(private_key, &encoded)) { + LOG(ERROR) << "Can't get private key data!"; + return NULL; + } + const unsigned char* p = + reinterpret_cast<const unsigned char*>(&encoded[0]); + int len = static_cast<int>(encoded.size()); + sys_pkey = d2i_AutoPrivateKey(NULL, &p, len); + if (sys_pkey == NULL) { + LOG(ERROR) << "Can't convert private key data!"; + return NULL; + } + } + return sys_pkey; +} + +// Custom DSA_METHOD that uses the platform APIs. +// Note that for now, only signing through DSA_sign() is really supported. +// all other method pointers are either stubs returning errors, or no-ops. +// See <openssl/dsa.h> for exact declaration of DSA_METHOD. +// +// Note: There is no DSA_set_app_data() and DSA_get_app_data() functions, +// but RSA_set_app_data() is defined as a simple macro that calls +// RSA_set_ex_data() with a hard-coded index of 0, so this code +// does the same thing here. + +DSA_SIG* DsaMethodDoSign(const unsigned char* dgst, + int dlen, + DSA* dsa) { + // Extract the JNI reference to the PrivateKey object. + jobject private_key = reinterpret_cast<jobject>(DSA_get_ex_data(dsa, 0)); + if (private_key == NULL) + return NULL; + + // Sign the message with it, calling platform APIs. + std::vector<uint8> signature; + if (!RawSignDigestWithPrivateKey( + private_key, + base::StringPiece( + reinterpret_cast<const char*>(dgst), + static_cast<size_t>(dlen)), + &signature)) { + return NULL; + } + + // Note: With DSA, the actual signature might be smaller than DSA_size(). + size_t max_expected_size = static_cast<size_t>(DSA_size(dsa)); + if (signature.size() > max_expected_size) { + LOG(ERROR) << "DSA Signature size mismatch, actual: " + << signature.size() << ", expected <= " + << max_expected_size; + return NULL; + } + + // Convert the signature into a DSA_SIG object. + const unsigned char* sigbuf = + reinterpret_cast<const unsigned char*>(&signature[0]); + int siglen = static_cast<size_t>(signature.size()); + DSA_SIG* dsa_sig = d2i_DSA_SIG(NULL, &sigbuf, siglen); + return dsa_sig; +} + +int DsaMethodSignSetup(DSA* dsa, + BN_CTX* ctx_in, + BIGNUM** kinvp, + BIGNUM** rp) { + NOTIMPLEMENTED(); + DSAerr(DSA_F_DSA_SIGN_SETUP, DSA_R_INVALID_DIGEST_TYPE); + return -1; +} + +int DsaMethodDoVerify(const unsigned char* dgst, + int dgst_len, + DSA_SIG* sig, + DSA* dsa) { + NOTIMPLEMENTED(); + DSAerr(DSA_F_DSA_DO_VERIFY, DSA_R_INVALID_DIGEST_TYPE); + return -1; +} + +int DsaMethodFinish(DSA* dsa) { + // Free the global JNI reference that was created with this + // wrapper key. + 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); + } + // Actual return value is ignored by OpenSSL. There are no docs + // explaining what this is supposed to be. + return 0; +} + +const DSA_METHOD android_dsa_method = { + /* .name = */ "Android signing-only DSA method", + /* .dsa_do_sign = */ DsaMethodDoSign, + /* .dsa_sign_setup = */ DsaMethodSignSetup, + /* .dsa_do_verify = */ DsaMethodDoVerify, + /* .dsa_mod_exp = */ NULL, + /* .bn_mod_exp = */ NULL, + /* .init = */ NULL, // nothing to do here. + /* .finish = */ DsaMethodFinish, + /* .flags = */ 0, + /* .app_data = */ NULL, + /* .dsa_paramgem = */ NULL, + /* .dsa_keygen = */ NULL +}; + +// Setup an EVP_PKEY to wrap an existing DSA platform PrivateKey object. +// |private_key| is a JNI reference (local or global) to the object. +// |pkey| is the EVP_PKEY to setup as a wrapper. +// Returns true on success, false otherwise. +// On success, this creates a global JNI reference to the same object +// that will be owned by and destroyed with the EVP_PKEY. +bool GetDsaPkeyWrapper(jobject private_key, EVP_PKEY* pkey) { + ScopedDSA dsa(DSA_new()); + DSA_set_method(dsa.get(), &android_dsa_method); + + // DSA_size() doesn't work with custom DSA_METHODs. To ensure it + // returns the right value, set the 'q' field in the DSA object to + // match the parameter from the platform key. + std::vector<uint8> q; + if (!GetDSAKeyParamQ(private_key, &q)) { + LOG(ERROR) << "Can't extract Q parameter from DSA private key"; + return false; + } + if (!SwapBigNumPtrFromBytes(q, &dsa.get()->q)) { + LOG(ERROR) << "Can't decode Q parameter from DSA private key"; + return false; + } + + ScopedJavaGlobalRef<jobject> global_key; + global_key.Reset(NULL, private_key); + if (global_key.is_null()) { + LOG(ERROR) << "Could not create global JNI reference"; + return false; + } + DSA_set_ex_data(dsa.get(), 0, global_key.Release()); + EVP_PKEY_assign_DSA(pkey, dsa.release()); + return true; +} + +// Custom ECDSA_METHOD that uses the platform APIs. +// Note that for now, only signing through ECDSA_sign() is really supported. +// all other method pointers are either stubs returning errors, or no-ops. +// +// Note: The ECDSA_METHOD structure doesn't have init/finish +// methods. As such, the only way to to ensure the global +// JNI reference is properly released when the EVP_PKEY is +// destroyed is to use a custom EX_DATA type. + +// Used to ensure that the global JNI reference associated with a custom +// EC_KEY + ECDSA_METHOD wrapper is released when its EX_DATA is destroyed +// (this function is called when EVP_PKEY_free() is called on the wrapper). +void ExDataFree(void* parent, + void* ptr, + CRYPTO_EX_DATA* ad, + int idx, + long argl, + void* argp) { + jobject private_key = reinterpret_cast<jobject>(ptr); + if (private_key == NULL) + return; + + CRYPTO_set_ex_data(ad, idx, NULL); + + JNIEnv* env = base::android::AttachCurrentThread(); + env->DeleteGlobalRef(private_key); +} + +int ExDataDup(CRYPTO_EX_DATA* to, + CRYPTO_EX_DATA* from, + void* from_d, + int idx, + long argl, + void* argp) { + // This callback shall never be called with the current OpenSSL + // implementation (the library only ever duplicates EX_DATA items + // for SSL and BIO objects). But provide this to catch regressions + // in the future. + CHECK(false) << "ExDataDup was called for ECDSA custom key !?"; + // Return value is currently ignored by OpenSSL. + return 0; +} + +class EcdsaExDataIndex { +public: + int ex_data_index() { return ex_data_index_; } + + EcdsaExDataIndex() { + ex_data_index_ = ECDSA_get_ex_new_index(0, // argl + NULL, // argp + NULL, // new_func + ExDataDup, // dup_func + ExDataFree); // free_func + } + +private: + int ex_data_index_; +}; + +// Returns the index of the custom EX_DATA used to store the JNI reference. +int EcdsaGetExDataIndex(void) { + // Use a LazyInstance to perform thread-safe lazy initialization. + // Use a leaky one, since OpenSSL doesn't provide a way to release + // allocated EX_DATA indices. + static base::LazyInstance<EcdsaExDataIndex>::Leaky s_instance = + LAZY_INSTANCE_INITIALIZER; + return s_instance.Get().ex_data_index(); +} + +ECDSA_SIG* EcdsaMethodDoSign(const unsigned char* dgst, + int dgst_len, + const BIGNUM* inv, + const BIGNUM* rp, + EC_KEY* eckey) { + // Retrieve private key JNI reference. + jobject private_key = reinterpret_cast<jobject>( + ECDSA_get_ex_data(eckey, EcdsaGetExDataIndex())); + if (!private_key) { + LOG(WARNING) << "Null JNI reference passed to EcdsaMethodDoSign!"; + return NULL; + } + // Sign message with it through JNI. + std::vector<uint8> signature; + base::StringPiece digest( + reinterpret_cast<const char*>(dgst), + static_cast<size_t>(dgst_len)); + if (!RawSignDigestWithPrivateKey( + private_key, digest, &signature)) { + LOG(WARNING) << "Could not sign message in EcdsaMethodDoSign!"; + return NULL; + } + + // Note: With ECDSA, the actual signature may be smaller than + // ECDSA_size(). + size_t max_expected_size = static_cast<size_t>(ECDSA_size(eckey)); + if (signature.size() > max_expected_size) { + LOG(ERROR) << "ECDSA Signature size mismatch, actual: " + << signature.size() << ", expected <= " + << max_expected_size; + return NULL; + } + + // Convert signature to ECDSA_SIG object + const unsigned char* sigbuf = + reinterpret_cast<const unsigned char*>(&signature[0]); + long siglen = static_cast<long>(signature.size()); + return d2i_ECDSA_SIG(NULL, &sigbuf, siglen); +} + +int EcdsaMethodSignSetup(EC_KEY* eckey, + BN_CTX* ctx, + BIGNUM** kinv, + BIGNUM** r) { + NOTIMPLEMENTED(); + ECDSAerr(ECDSA_F_ECDSA_SIGN_SETUP, ECDSA_R_ERR_EC_LIB); + return -1; +} + +int EcdsaMethodDoVerify(const unsigned char* dgst, + int dgst_len, + const ECDSA_SIG* sig, + EC_KEY* eckey) { + NOTIMPLEMENTED(); + ECDSAerr(ECDSA_F_ECDSA_DO_VERIFY, ECDSA_R_ERR_EC_LIB); + return -1; +} + +const ECDSA_METHOD android_ecdsa_method = { + /* .name = */ "Android signing-only ECDSA method", + /* .ecdsa_do_sign = */ EcdsaMethodDoSign, + /* .ecdsa_sign_setup = */ EcdsaMethodSignSetup, + /* .ecdsa_do_verify = */ EcdsaMethodDoVerify, + /* .flags = */ 0, + /* .app_data = */ NULL, +}; + +// Setup an EVP_PKEY to wrap an existing platform PrivateKey object. +// |private_key| is the JNI reference (local or global) to the object. +// |pkey| is the EVP_PKEY to setup as a wrapper. +// Returns true on success, false otherwise. +// On success, this creates a global JNI reference to the object that +// is owned by and destroyed with the EVP_PKEY. I.e. the caller shall +// always free |private_key| after the call. +bool GetEcdsaPkeyWrapper(jobject private_key, EVP_PKEY* pkey) { + ScopedEC_KEY eckey(EC_KEY_new()); + ECDSA_set_method(eckey.get(), &android_ecdsa_method); + + // To ensure that ECDSA_size() works properly, craft a custom EC_GROUP + // that has the same order than the private key. + std::vector<uint8> order; + if (!GetECKeyOrder(private_key, &order)) { + LOG(ERROR) << "Can't extract order parameter from EC private key"; + return false; + } + ScopedEC_GROUP group(EC_GROUP_new(EC_GFp_nist_method())); + if (!group.get()) { + LOG(ERROR) << "Can't create new EC_GROUP"; + return false; + } + if (!CopyBigNumFromBytes(order, &group.get()->order)) { + LOG(ERROR) << "Can't decode order from PrivateKey"; + return false; + } + EC_KEY_set_group(eckey.get(), group.release()); + + ScopedJavaGlobalRef<jobject> global_key; + global_key.Reset(NULL, private_key); + if (global_key.is_null()) { + LOG(ERROR) << "Can't create global JNI reference"; + return false; + } + ECDSA_set_ex_data(eckey.get(), + EcdsaGetExDataIndex(), + global_key.Release()); + + EVP_PKEY_assign_EC_KEY(pkey, eckey.release()); + return true; +} + +} // namespace + +EVP_PKEY* GetOpenSSLPrivateKeyWrapper(jobject private_key) { + // Create new empty EVP_PKEY instance. + ScopedEVP_PKEY pkey(EVP_PKEY_new()); + if (!pkey.get()) + return NULL; + + // Create sub key type, depending on private key's algorithm type. + PrivateKeyType key_type = GetPrivateKeyType(private_key); + switch (key_type) { + case PRIVATE_KEY_TYPE_RSA: + { + // Route around platform bug: if Android < 4.2, then + // base::android::RawSignDigestWithPrivateKey() cannot work, so + // instead, obtain a raw EVP_PKEY* to the system object + // backing this PrivateKey object. + const int kAndroid42ApiLevel = 17; + if (base::android::BuildInfo::GetInstance()->sdk_int() < + kAndroid42ApiLevel) { + EVP_PKEY* legacy_key = GetRsaLegacyKey(private_key); + if (legacy_key == NULL) + return NULL; + pkey.reset(legacy_key); + } else { + // Running on Android 4.2. + if (!GetRsaPkeyWrapper(private_key, pkey.get())) + return NULL; + } + } + break; + case PRIVATE_KEY_TYPE_DSA: + if (!GetDsaPkeyWrapper(private_key, pkey.get())) + return NULL; + break; + case PRIVATE_KEY_TYPE_ECDSA: + if (!GetEcdsaPkeyWrapper(private_key, pkey.get())) + return NULL; + break; + default: + LOG(WARNING) + << "GetOpenSSLPrivateKeyWrapper() called with invalid key type"; + return NULL; + } + return pkey.release(); +} + +} // namespace android +} // namespace net diff --git a/net/android/keystore_openssl.h b/net/android/keystore_openssl.h new file mode 100644 index 0000000..ceb900c --- /dev/null +++ b/net/android/keystore_openssl.h @@ -0,0 +1,48 @@ +// Copyright (c) 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. + +#ifndef NET_ANDROID_KEYSTORE_OPENSSL_H +#define NET_ANDROID_KEYSTORE_OPENSSL_H + +#include <jni.h> +#include <openssl/evp.h> + +#include "net/base/net_export.h" + +// OpenSSL-specific functions to use the Android platform keystore. +// The features provided here are highly specific to OpenSSL and are +// segregated from net/android/keystore.h because the latter only provides +// simply JNI stubs to call Java code which only uses platform APIs. + +namespace net { +namespace android { + +// Create a custom OpenSSL EVP_PKEY instance that wraps a platform +// java.security.PrivateKey object, and will call the platform APIs +// through JNI to implement signing (and only signing). +// +// This method can be called from any thread. It shall only be used +// to implement client certificate handling though. +// +// |private_key| is a JNI local (or global) reference to the Java +// PrivateKey object. +// +// Returns a new EVP_PKEY* object with the following features: +// +// - Only contains a private key. +// +// - Owns its own _global_ JNI reference to the object. This means the +// caller can free |private_key| safely after the call, and that the +// the returned EVP_PKEY instance can be used from any thread. +// +// - Uses a custom method to implement the minimum functions required to +// *sign* the digest that is part of the "Verify Certificate" message +// during the OpenSSL handshake. Anything else will result in undefined +// behaviour. +NET_EXPORT EVP_PKEY* GetOpenSSLPrivateKeyWrapper(jobject private_key); + +} // namespace android +} // namespace net + +#endif // NET_ANDROID_KEYSTORE_OPENSSL_H diff --git a/net/android/keystore_unittest.cc b/net/android/keystore_unittest.cc new file mode 100644 index 0000000..b4c0504 --- /dev/null +++ b/net/android/keystore_unittest.cc @@ -0,0 +1,725 @@ +// Copyright (c) 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. + +#include <openssl/bn.h> +#include <openssl/dsa.h> +#include <openssl/ecdsa.h> +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/pem.h> +#include <openssl/rsa.h> +#include <openssl/x509.h> + +#include "base/android/build_info.h" +#include "base/android/jni_android.h" +#include "base/android/jni_array.h" +#include "base/android/scoped_java_ref.h" +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/memory/scoped_handle.h" +#include "base/string_util.h" +#include "base/strings/string_number_conversions.h" +#include "crypto/openssl_util.h" +#include "jni/AndroidKeyStoreTestUtil_jni.h" +#include "net/android/keystore.h" +#include "net/android/keystore_openssl.h" +#include "net/base/test_data_directory.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Technical note: +// +// This source file not only checks that signing with +// RawSignDigestWithPrivateKey() works correctly, it also verifies that +// the generated signature matches 100% of what OpenSSL generates when +// calling RSA_sign(NID_md5_sha1,...), DSA_sign(0, ...) or +// ECDSA_sign(0, ...). +// +// That's crucial to ensure that this function can later be used to +// implement client certificate support. More specifically, that it is +// possible to create a custom EVP_PKEY that uses +// RawSignDigestWithPrivateKey() internally to perform RSA/DSA/ECDSA +// signing, as invoked by the OpenSSL code at +// openssl/ssl/s3_clnt.c:ssl3_send_client_verify(). +// +// For more details, read the comments in AndroidKeyStore.java. +// +// Finally, it also checks that using the EVP_PKEY generated with +// GetOpenSSLPrivateKeyWrapper() works correctly. + +namespace net { +namespace android { + +namespace { + +typedef crypto::ScopedOpenSSL<EVP_PKEY, EVP_PKEY_free> ScopedEVP_PKEY; +typedef crypto::ScopedOpenSSL<RSA, RSA_free> ScopedRSA; +typedef crypto::ScopedOpenSSL<DSA, DSA_free> ScopedDSA; +typedef crypto::ScopedOpenSSL<EC_KEY, EC_KEY_free> ScopedEC_KEY; +typedef crypto::ScopedOpenSSL<BIGNUM, BN_free> ScopedBIGNUM; + +typedef crypto::ScopedOpenSSL< + PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free> + ScopedPKCS8_PRIV_KEY_INFO; + +typedef base::android::ScopedJavaLocalRef<jobject> ScopedJava; + +JNIEnv* InitEnv() { + JNIEnv* env = base::android::AttachCurrentThread(); + static bool inited = false; + if (!inited) { + RegisterNativesImpl(env); + inited = true; + } + return env; +} + +// Returns true if running on an Android version older than 4.2 +bool IsOnAndroidOlderThan_4_2(void) { + const int kAndroid42ApiLevel = 17; + int level = base::android::BuildInfo::GetInstance()->sdk_int(); + return level < kAndroid42ApiLevel; +} + +// Implements the callback expected by ERR_print_errors_cb(). +// used by GetOpenSSLErrorString below. +int openssl_print_error_callback(const char* msg, size_t msglen, void* u) { + std::string* result = reinterpret_cast<std::string*>(u); + result->append(msg, msglen); + return 1; +} + +// Retrieves the OpenSSL error as a string +std::string GetOpenSSLErrorString(void) { + std::string result; + ERR_print_errors_cb(openssl_print_error_callback, &result); + return result; +} + +// Resize a string to |size| bytes of data, then return its data buffer +// address cast as an 'unsigned char*', as expected by OpenSSL functions. +// |str| the target string. +// |size| the number of bytes to write into the string. +// Return the string's new buffer in memory, as an 'unsigned char*' +// pointer. +unsigned char* OpenSSLWriteInto(std::string* str, size_t size) { + return reinterpret_cast<unsigned char*>(WriteInto(str, size + 1)); +} + +// Load a given private key file into an EVP_PKEY. +// |filename| is the key file path. +// Returns a new EVP_PKEY on success, NULL on failure. +EVP_PKEY* ImportPrivateKeyFile(const char* filename) { + // Load file in memory. + FilePath certs_dir = GetTestCertsDirectory(); + FilePath file_path = certs_dir.AppendASCII(filename); + ScopedStdioHandle handle( + file_util::OpenFile(file_path, "rb")); + if (!handle.get()) { + LOG(ERROR) << "Could not open private key file: " << filename; + return NULL; + } + // Assume it is PEM_encoded. Load it as an EVP_PKEY. + EVP_PKEY* pkey = PEM_read_PrivateKey(handle.get(), NULL, NULL, NULL); + if (!pkey) { + LOG(ERROR) << "Could not load public key file: " << filename + << ", " << GetOpenSSLErrorString(); + return NULL; + } + return pkey; +} + +// Convert a private key into its PKCS#8 encoded representation. +// |pkey| is the EVP_PKEY handle for the private key. +// |pkcs8| will receive the PKCS#8 bytes. +// Returns true on success, false otherwise. +bool GetPrivateKeyPkcs8Bytes(const ScopedEVP_PKEY& pkey, + std::string* pkcs8) { + // Convert to PKCS#8 object. + ScopedPKCS8_PRIV_KEY_INFO p8_info(EVP_PKEY2PKCS8(pkey.get())); + if (!p8_info.get()) { + LOG(ERROR) << "Can't get PKCS#8 private key from EVP_PKEY: " + << GetOpenSSLErrorString(); + return false; + } + + // Then convert it + int len = i2d_PKCS8_PRIV_KEY_INFO(p8_info.get(), NULL); + unsigned char* p = OpenSSLWriteInto(pkcs8, static_cast<size_t>(len)); + i2d_PKCS8_PRIV_KEY_INFO(p8_info.get(), &p); + return true; +} + +bool ImportPrivateKeyFileAsPkcs8(const char* filename, + std::string* pkcs8) { + ScopedEVP_PKEY pkey(ImportPrivateKeyFile(filename)); + if (!pkey.get()) + return false; + return GetPrivateKeyPkcs8Bytes(pkey, pkcs8); +} + +// Same as ImportPrivateKey, but for public ones. +EVP_PKEY* ImportPublicKeyFile(const char* filename) { + // Load file as PEM data. + FilePath certs_dir = GetTestCertsDirectory(); + FilePath file_path = certs_dir.AppendASCII(filename); + ScopedStdioHandle handle(file_util::OpenFile(file_path, "rb")); + if (!handle.get()) { + LOG(ERROR) << "Could not open public key file: " << filename; + return NULL; + } + EVP_PKEY* pkey = PEM_read_PUBKEY(handle.get(), NULL, NULL, NULL); + if (!pkey) { + LOG(ERROR) << "Could not load public key file: " << filename + << ", " << GetOpenSSLErrorString(); + return NULL; + } + return pkey; +} + +// Retrieve a JNI local ref from encoded PKCS#8 data. +ScopedJava GetPKCS8PrivateKeyJava(PrivateKeyType key_type, + const std::string& pkcs8_key) { + JNIEnv* env = InitEnv(); + base::android::ScopedJavaLocalRef<jbyteArray> bytes( + base::android::ToJavaByteArray( + env, + reinterpret_cast<const uint8*>(pkcs8_key.data()), + pkcs8_key.size())); + + ScopedJava key( + Java_AndroidKeyStoreTestUtil_createPrivateKeyFromPKCS8( + env, key_type, bytes.obj())); + + return key; +} + +const char kTestRsaKeyFile[] = "android-test-key-rsa.pem"; + +// The RSA test hash must be 36 bytes exactly. +const char kTestRsaHash[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +// Retrieve a JNI local ref for our test RSA key. +ScopedJava GetRSATestKeyJava() { + std::string key; + if (!ImportPrivateKeyFileAsPkcs8(kTestRsaKeyFile, &key)) + return ScopedJava(); + return GetPKCS8PrivateKeyJava(PRIVATE_KEY_TYPE_RSA, key); +} + +const char kTestDsaKeyFile[] = "android-test-key-dsa.pem"; +const char kTestDsaPublicKeyFile[] = "android-test-key-dsa-public.pem"; + +// The DSA test hash must be 20 bytes exactly. +const char kTestDsaHash[] = "0123456789ABCDEFGHIJ"; + +// Retrieve a JNI local ref for our test DSA key. +ScopedJava GetDSATestKeyJava() { + std::string key; + if (!ImportPrivateKeyFileAsPkcs8(kTestDsaKeyFile, &key)) + return ScopedJava(); + return GetPKCS8PrivateKeyJava(PRIVATE_KEY_TYPE_DSA, key); +} + +// Call this function to verify that one message signed with our +// test DSA private key is correct. Since DSA signing introduces +// random elements in the signature, it is not possible to compare +// signature bits directly. However, one can use the public key +// to do the check. +bool VerifyTestDSASignature(const base::StringPiece& message, + const base::StringPiece& signature) { + ScopedEVP_PKEY pkey(ImportPublicKeyFile(kTestDsaPublicKeyFile)); + if (!pkey.get()) + return false; + + ScopedDSA pub_key(EVP_PKEY_get1_DSA(pkey.get())); + if (!pub_key.get()) { + LOG(ERROR) << "Could not get DSA public key: " + << GetOpenSSLErrorString(); + return false; + } + + const unsigned char* digest = + reinterpret_cast<const unsigned char*>(message.data()); + int digest_len = static_cast<int>(message.size()); + const unsigned char* sigbuf = + reinterpret_cast<const unsigned char*>(signature.data()); + int siglen = static_cast<int>(signature.size()); + + int ret = DSA_verify( + 0, digest, digest_len, sigbuf, siglen, pub_key.get()); + if (ret != 1) { + LOG(ERROR) << "DSA_verify() failed: " << GetOpenSSLErrorString(); + return false; + } + return true; +} + +const char kTestEcdsaKeyFile[] = "android-test-key-ecdsa.pem"; +const char kTestEcdsaPublicKeyFile[] = "android-test-key-ecdsa-public.pem"; + +// The test hash for ECDSA keys must be 20 bytes exactly. +const char kTestEcdsaHash[] = "0123456789ABCDEFGHIJ"; + +// Retrieve a JNI local ref for our test ECDSA key. +ScopedJava GetECDSATestKeyJava() { + std::string key; + if (!ImportPrivateKeyFileAsPkcs8(kTestEcdsaKeyFile, &key)) + return ScopedJava(); + return GetPKCS8PrivateKeyJava(PRIVATE_KEY_TYPE_ECDSA, key); +} + +// Call this function to verify that one message signed with our +// test DSA private key is correct. Since DSA signing introduces +// random elements in the signature, it is not possible to compare +// signature bits directly. However, one can use the public key +// to do the check. +bool VerifyTestECDSASignature(const base::StringPiece& message, + const base::StringPiece& signature) { + ScopedEVP_PKEY pkey(ImportPublicKeyFile(kTestEcdsaPublicKeyFile)); + if (!pkey.get()) + return false; + ScopedEC_KEY pub_key(EVP_PKEY_get1_EC_KEY(pkey.get())); + if (!pub_key.get()) { + LOG(ERROR) << "Could not get ECDSA public key: " + << GetOpenSSLErrorString(); + return false; + } + + const unsigned char* digest = + reinterpret_cast<const unsigned char*>(message.data()); + int digest_len = static_cast<int>(message.size()); + const unsigned char* sigbuf = + reinterpret_cast<const unsigned char*>(signature.data()); + int siglen = static_cast<int>(signature.size()); + + int ret = ECDSA_verify( + 0, digest, digest_len, sigbuf, siglen, pub_key.get()); + if (ret != 1) { + LOG(ERROR) << "ECDSA_verify() failed: " << GetOpenSSLErrorString(); + return false; + } + return true; +} + +// Sign a message with OpenSSL, return the result as a string. +// |message| is the message to be signed. +// |openssl_key| is an OpenSSL EVP_PKEY to use. +// |result| receives the result. +// Returns true on success, false otherwise. +bool SignWithOpenSSL(const base::StringPiece& message, + EVP_PKEY* openssl_key, + std::string* result) { + const unsigned char* digest = + reinterpret_cast<const unsigned char*>(message.data()); + unsigned int digest_len = static_cast<unsigned int>(message.size()); + std::string signature; + size_t signature_size; + size_t max_signature_size; + int key_type = EVP_PKEY_id(openssl_key); + switch (key_type) { + case EVP_PKEY_RSA: + { + ScopedRSA rsa(EVP_PKEY_get1_RSA(openssl_key)); + if (!rsa.get()) { + LOG(ERROR) << "Could not get RSA from EVP_PKEY: " + << GetOpenSSLErrorString(); + return false; + } + // With RSA, the signature will always be RSA_size() bytes. + max_signature_size = static_cast<size_t>(RSA_size(rsa.get())); + unsigned char* p = OpenSSLWriteInto(&signature, + max_signature_size); + unsigned int p_len = 0; + int ret = RSA_sign( + NID_md5_sha1, digest, digest_len, p, &p_len, rsa.get()); + if (ret != 1) { + LOG(ERROR) << "RSA_sign() failed: " << GetOpenSSLErrorString(); + return false; + } + signature_size = static_cast<size_t>(p_len); + break; + } + case EVP_PKEY_DSA: + { + ScopedDSA dsa(EVP_PKEY_get1_DSA(openssl_key)); + if (!dsa.get()) { + LOG(ERROR) << "Could not get DSA from EVP_PKEY: " + << GetOpenSSLErrorString(); + return false; + } + // Note, the actual signature can be smaller than DSA_size() + max_signature_size = static_cast<size_t>(DSA_size(dsa.get())); + unsigned char* p = OpenSSLWriteInto(&signature, + max_signature_size); + unsigned int p_len = 0; + // Note: first parameter is ignored by function. + int ret = DSA_sign(0, digest, digest_len, p, &p_len, dsa.get()); + if (ret != 1) { + LOG(ERROR) << "DSA_sign() failed: " << GetOpenSSLErrorString(); + return false; + } + signature_size = static_cast<size_t>(p_len); + break; + } + case EVP_PKEY_EC: + { + ScopedEC_KEY ecdsa(EVP_PKEY_get1_EC_KEY(openssl_key)); + if (!ecdsa.get()) { + LOG(ERROR) << "Could not get EC_KEY from EVP_PKEY: " + << GetOpenSSLErrorString(); + return false; + } + // Note, the actual signature can be smaller than ECDSA_size() + max_signature_size = ECDSA_size(ecdsa.get()); + unsigned char* p = OpenSSLWriteInto(&signature, + max_signature_size); + unsigned int p_len = 0; + // Note: first parameter is ignored by function. + int ret = ECDSA_sign( + 0, digest, digest_len, p, &p_len, ecdsa.get()); + if (ret != 1) { + LOG(ERROR) << "ECDSA_sign() fialed: " << GetOpenSSLErrorString(); + return false; + } + signature_size = static_cast<size_t>(p_len); + break; + } + default: + LOG(WARNING) << "Invalid OpenSSL key type: " << key_type; + return false; + } + + if (signature_size == 0) { + LOG(ERROR) << "Signature is empty!"; + return false; + } + if (signature_size > max_signature_size) { + LOG(ERROR) << "Signature size mismatch, actual " << signature_size + << ", expected <= " << max_signature_size; + return false; + } + signature.resize(signature_size); + result->swap(signature); + return true; +} + +// Check that a generated signature for a given message matches +// OpenSSL output byte-by-byte. +// |message| is the input message. +// |signature| is the generated signature for the message. +// |openssl_key| is a raw EVP_PKEY for the same private key than the +// one which was used to generate the signature. +// Returns true on success, false otherwise. +bool CompareSignatureWithOpenSSL(const base::StringPiece& message, + const base::StringPiece& signature, + EVP_PKEY* openssl_key) { + std::string openssl_signature; + SignWithOpenSSL(message, openssl_key, &openssl_signature); + + if (signature.size() != openssl_signature.size()) { + LOG(ERROR) << "Signature size mismatch, actual " + << signature.size() << ", expected " + << openssl_signature.size(); + return false; + } + for (size_t n = 0; n < signature.size(); ++n) { + if (openssl_signature[n] != signature[n]) { + LOG(ERROR) << "Signature byte mismatch at index " << n + << "actual " << signature[n] << ", expected " + << openssl_signature[n]; + LOG(ERROR) << "Actual signature : " + << base::HexEncode(signature.data(), signature.size()); + LOG(ERROR) << "Expected signature: " + << base::HexEncode(openssl_signature.data(), + openssl_signature.size()); + return false; + } + } + return true; +} + +// Sign a message with our platform API. +// +// |android_key| is a JNI reference to the platform PrivateKey object. +// |openssl_key| is a pointer to an OpenSSL key object for the exact +// same key content. +// |message| is a message. +// |result| will receive the result. +void DoKeySigning(jobject android_key, + EVP_PKEY* openssl_key, + const base::StringPiece& message, + std::string* result) { + // First, get the platform signature. + std::vector<uint8> android_signature; + ASSERT_TRUE( + RawSignDigestWithPrivateKey(android_key, + message, + &android_signature)); + + result->assign( + reinterpret_cast<const char*>(&android_signature[0]), + android_signature.size()); +} + +// Sign a message with our OpenSSL EVP_PKEY wrapper around platform +// APIS. +// +// |android_key| is a JNI reference to the platform PrivateKey object. +// |openssl_key| is a pointer to an OpenSSL key object for the exact +// same key content. +// |message| is a message. +// |result| will receive the result. +void DoKeySigningWithWrapper(EVP_PKEY* wrapper_key, + EVP_PKEY* openssl_key, + const base::StringPiece& message, + std::string* result) { + // First, get the platform signature. + std::string wrapper_signature; + SignWithOpenSSL(message, wrapper_key, &wrapper_signature); + ASSERT_NE(0U, wrapper_signature.size()); + + result->assign( + reinterpret_cast<const char*>(&wrapper_signature[0]), + wrapper_signature.size()); +} + +} // namespace + +TEST(AndroidKeyStore,GetRSAKeyModulus) { + crypto::OpenSSLErrStackTracer err_trace(FROM_HERE); + InitEnv(); + + // Load the test RSA key. + ScopedEVP_PKEY pkey(ImportPrivateKeyFile(kTestRsaKeyFile)); + ASSERT_TRUE(pkey.get()); + + // Convert it to encoded PKCS#8 bytes. + std::string pkcs8_data; + ASSERT_TRUE(GetPrivateKeyPkcs8Bytes(pkey, &pkcs8_data)); + + // Create platform PrivateKey object from it. + ScopedJava key_java = GetPKCS8PrivateKeyJava(PRIVATE_KEY_TYPE_RSA, + pkcs8_data); + ASSERT_FALSE(key_java.is_null()); + + // Retrieve the corresponding modulus through JNI + std::vector<uint8> modulus_java; + ASSERT_TRUE(GetRSAKeyModulus(key_java.obj(), &modulus_java)); + + // Create an OpenSSL BIGNUM from it. + ScopedBIGNUM bn( + BN_bin2bn( + reinterpret_cast<const unsigned char*>(&modulus_java[0]), + static_cast<int>(modulus_java.size()), + NULL)); + ASSERT_TRUE(bn.get()); + + // Compare it to the one in the RSA key, they must be identical. + ScopedRSA rsa(EVP_PKEY_get1_RSA(pkey.get())); + ASSERT_TRUE(rsa.get()) << GetOpenSSLErrorString(); + + ASSERT_EQ(0, BN_cmp(bn.get(), rsa.get()->n)); +} + +TEST(AndroidKeyStore,GetDSAKeyParamQ) { + crypto::OpenSSLErrStackTracer err_trace(FROM_HERE); + InitEnv(); + + // Load the test DSA key. + ScopedEVP_PKEY pkey(ImportPrivateKeyFile(kTestDsaKeyFile)); + ASSERT_TRUE(pkey.get()); + + // Convert it to encoded PKCS#8 bytes. + std::string pkcs8_data; + ASSERT_TRUE(GetPrivateKeyPkcs8Bytes(pkey, &pkcs8_data)); + + // Create platform PrivateKey object from it. + ScopedJava key_java = GetPKCS8PrivateKeyJava(PRIVATE_KEY_TYPE_DSA, + pkcs8_data); + ASSERT_FALSE(key_java.is_null()); + + // Retrieve the corresponding Q parameter through JNI + std::vector<uint8> q_java; + ASSERT_TRUE(GetDSAKeyParamQ(key_java.obj(), &q_java)); + + // Create an OpenSSL BIGNUM from it. + ScopedBIGNUM bn( + BN_bin2bn( + reinterpret_cast<const unsigned char*>(&q_java[0]), + static_cast<int>(q_java.size()), + NULL)); + ASSERT_TRUE(bn.get()); + + // Compare it to the one in the RSA key, they must be identical. + ScopedDSA dsa(EVP_PKEY_get1_DSA(pkey.get())); + ASSERT_TRUE(dsa.get()) << GetOpenSSLErrorString(); + + ASSERT_EQ(0, BN_cmp(bn.get(), dsa.get()->q)); +} + +TEST(AndroidKeyStore,GetPrivateKeyTypeRSA) { + crypto::OpenSSLErrStackTracer err_trace(FROM_HERE); + + ScopedJava rsa_key = GetRSATestKeyJava(); + ASSERT_FALSE(rsa_key.is_null()); + EXPECT_EQ(PRIVATE_KEY_TYPE_RSA, + GetPrivateKeyType(rsa_key.obj())); +} + +TEST(AndroidKeyStore,SignWithPrivateKeyRSA) { + ScopedJava rsa_key = GetRSATestKeyJava(); + ASSERT_FALSE(rsa_key.is_null()); + + if (IsOnAndroidOlderThan_4_2()) { + LOG(INFO) << "This test can't run on Android < 4.2"; + return; + } + + ScopedEVP_PKEY openssl_key(ImportPrivateKeyFile(kTestRsaKeyFile)); + ASSERT_TRUE(openssl_key.get()); + + std::string message = kTestRsaHash; + ASSERT_EQ(36U, message.size()); + + std::string signature; + DoKeySigning(rsa_key.obj(), openssl_key.get(), message, &signature); + ASSERT_TRUE( + CompareSignatureWithOpenSSL(message, signature, openssl_key.get())); + // All good. +} + +TEST(AndroidKeyStore,SignWithWrapperKeyRSA) { + crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); + + ScopedJava rsa_key = GetRSATestKeyJava(); + ASSERT_FALSE(rsa_key.is_null()); + + ScopedEVP_PKEY wrapper_key(GetOpenSSLPrivateKeyWrapper(rsa_key.obj())); + ASSERT_TRUE(wrapper_key.get() != NULL); + + ScopedEVP_PKEY openssl_key(ImportPrivateKeyFile(kTestRsaKeyFile)); + ASSERT_TRUE(openssl_key.get()); + + // Check that RSA_size() works properly on the wrapper key. + EXPECT_EQ(EVP_PKEY_size(openssl_key.get()), + EVP_PKEY_size(wrapper_key.get())); + + // Message size must be 36 for RSA_sign(NID_md5_sha1,...) to return + // without an error. + std::string message = kTestRsaHash; + ASSERT_EQ(36U, message.size()); + + std::string signature; + DoKeySigningWithWrapper(wrapper_key.get(), + openssl_key.get(), + message, + &signature); + ASSERT_TRUE( + CompareSignatureWithOpenSSL(message, signature, openssl_key.get())); +} + +TEST(AndroidKeyStore,GetPrivateKeyTypeDSA) { + crypto::OpenSSLErrStackTracer err_trace(FROM_HERE); + + ScopedJava dsa_key = GetDSATestKeyJava(); + ASSERT_FALSE(dsa_key.is_null()); + EXPECT_EQ(PRIVATE_KEY_TYPE_DSA, + GetPrivateKeyType(dsa_key.obj())); +} + +TEST(AndroidKeyStore,SignWithPrivateKeyDSA) { + ScopedJava dsa_key = GetDSATestKeyJava(); + ASSERT_FALSE(dsa_key.is_null()); + + ScopedEVP_PKEY openssl_key(ImportPrivateKeyFile(kTestDsaKeyFile)); + ASSERT_TRUE(openssl_key.get()); + + std::string message = kTestDsaHash; + ASSERT_EQ(20U, message.size()); + + std::string signature; + DoKeySigning(dsa_key.obj(), openssl_key.get(), message, &signature); + ASSERT_TRUE(VerifyTestDSASignature(message, signature)); +} + +TEST(AndroidKeyStore,SignWithWrapperKeyDSA) { + crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); + + ScopedJava dsa_key = GetDSATestKeyJava(); + ASSERT_FALSE(dsa_key.is_null()); + + ScopedEVP_PKEY wrapper_key( + GetOpenSSLPrivateKeyWrapper(dsa_key.obj())); + ASSERT_TRUE(wrapper_key.get()); + + ScopedEVP_PKEY openssl_key(ImportPrivateKeyFile(kTestDsaKeyFile)); + ASSERT_TRUE(openssl_key.get()); + + // Check that DSA_size() works correctly on the wrapper. + EXPECT_EQ(EVP_PKEY_size(openssl_key.get()), + EVP_PKEY_size(wrapper_key.get())); + + std::string message = kTestDsaHash; + std::string signature; + DoKeySigningWithWrapper(wrapper_key.get(), + openssl_key.get(), + message, + &signature); + ASSERT_TRUE(VerifyTestDSASignature(message, signature)); +} + +TEST(AndroidKeyStore,GetPrivateKeyTypeECDSA) { + crypto::OpenSSLErrStackTracer err_trace(FROM_HERE); + + ScopedJava ecdsa_key = GetECDSATestKeyJava(); + ASSERT_FALSE(ecdsa_key.is_null()); + EXPECT_EQ(PRIVATE_KEY_TYPE_ECDSA, + GetPrivateKeyType(ecdsa_key.obj())); +} + +TEST(AndroidKeyStore,SignWithPrivateKeyECDSA) { + ScopedJava ecdsa_key = GetECDSATestKeyJava(); + ASSERT_FALSE(ecdsa_key.is_null()); + + ScopedEVP_PKEY openssl_key(ImportPrivateKeyFile(kTestEcdsaKeyFile)); + ASSERT_TRUE(openssl_key.get()); + + std::string message = kTestEcdsaHash; + std::string signature; + DoKeySigning(ecdsa_key.obj(), openssl_key.get(), message, &signature); + ASSERT_TRUE(VerifyTestECDSASignature(message, signature)); +} + +TEST(AndroidKeyStore, SignWithWrapperKeyECDSA) { + crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); + + ScopedJava ecdsa_key = GetECDSATestKeyJava(); + ASSERT_FALSE(ecdsa_key.is_null()); + + ScopedEVP_PKEY wrapper_key( + GetOpenSSLPrivateKeyWrapper(ecdsa_key.obj())); + ASSERT_TRUE(wrapper_key.get()); + + ScopedEVP_PKEY openssl_key(ImportPrivateKeyFile(kTestEcdsaKeyFile)); + ASSERT_TRUE(openssl_key.get()); + + // Check that ECDSA size works correctly on the wrapper. + EXPECT_EQ(EVP_PKEY_size(openssl_key.get()), + EVP_PKEY_size(wrapper_key.get())); + + std::string message = kTestEcdsaHash; + std::string signature; + DoKeySigningWithWrapper(wrapper_key.get(), + openssl_key.get(), + message, + &signature); + ASSERT_TRUE(VerifyTestECDSASignature(message, signature)); +} + +} // namespace android +} // namespace net diff --git a/net/android/net_jni_registrar.cc b/net/android/net_jni_registrar.cc index cfc3bb1..a6e09b6 100644 --- a/net/android/net_jni_registrar.cc +++ b/net/android/net_jni_registrar.cc @@ -8,6 +8,7 @@ #include "base/android/jni_android.h" #include "base/android/jni_registrar.h" #include "net/android/gurl_utils.h" +#include "net/android/keystore.h" #include "net/android/network_change_notifier_android.h" #include "net/android/network_library.h" #include "net/proxy/proxy_config_service_android.h" @@ -16,6 +17,7 @@ namespace net { namespace android { static base::android::RegistrationMethod kNetRegisteredMethods[] = { + { "AndroidKeyStore", net::android::RegisterKeyStore }, { "AndroidNetworkLibrary", net::android::RegisterNetworkLibrary }, { "GURLUtils", net::RegisterGURLUtils }, { "NetworkChangeNotifierAndroid", diff --git a/net/android/private_key_type_list.h b/net/android/private_key_type_list.h new file mode 100644 index 0000000..1eeea80 --- /dev/null +++ b/net/android/private_key_type_list.h @@ -0,0 +1,12 @@ +// Copyright (c) 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. + +#ifndef DEFINE_PRIVATE_KEY_TYPE +#error "Please define DEFINE_PRIVATE_KEY_TYPE before including this file." +#endif + +DEFINE_PRIVATE_KEY_TYPE(RSA, 0) +DEFINE_PRIVATE_KEY_TYPE(DSA, 1) +DEFINE_PRIVATE_KEY_TYPE(ECDSA, 2) +DEFINE_PRIVATE_KEY_TYPE(INVALID, 255) diff --git a/net/base/openssl_private_key_store.h b/net/base/openssl_private_key_store.h index ec79646..edd54f3 100644 --- a/net/base/openssl_private_key_store.h +++ b/net/base/openssl_private_key_store.h @@ -7,6 +7,7 @@ #include "base/basictypes.h" +// Avoid including <openssl/evp.h> here. typedef struct evp_pkey_st EVP_PKEY; class GURL; diff --git a/net/data/ssl/certificates/README b/net/data/ssl/certificates/README index 61d6357..f249728 100644 --- a/net/data/ssl/certificates/README +++ b/net/data/ssl/certificates/README @@ -136,3 +136,12 @@ unit tests. and a private key created for WebSocket testing. The password is "". This file is used in SSLUITest.TestWSSClientCert. +- android-test-key-rsa.pem +- android-test-key-dsa.pem +- android-test-key-dsa-public.pem +- android-test-key-ecdsa.pem +- android-test-key-ecdsa-public.pem + This is a set of test RSA/DSA/ECDSA keys used by the Android-specific + unit test in net/android/keystore_unittest.c. They are used to verify + that the OpenSSL-specific wrapper for platform PrivateKey objects + works properly. See the generate-android-test-keys.sh script. diff --git a/net/data/ssl/certificates/android-test-key-dsa-public.pem b/net/data/ssl/certificates/android-test-key-dsa-public.pem new file mode 100644 index 0000000..f996809 --- /dev/null +++ b/net/data/ssl/certificates/android-test-key-dsa-public.pem @@ -0,0 +1,20 @@ +-----BEGIN PUBLIC KEY----- +MIIDRjCCAjkGByqGSM44BAEwggIsAoIBAQDaJruwQmE1uMS2WnGl6L2UQy8iHmcs +74gWjym/8HVVdLXsC3wGoXGj0vj3Zr7DpgzrXBAWj2JILVApJgbFIbEFLAHcv6Bd +/3PjbRrva4PMQypul1o+j9IiFn2oemeVoCz77mz5aczfq7AoiMnMpdP501qcAOF2 +gDR61r1BO8QRV9gkbOLpZHX101NB9GW+PKR7C+SJBN5rVVX2YgtxPe/Km5jFgIuS +P76eSvxGh1kUApFDhqEDh7Gck5Slt6lJDPj399MZC74XrrdbjrZbO/jt5TsLxRac +SvI0waX+49+UnHYb3cjPynMkBgLDl7itMluTm3nS8IkgKKgYPiEAXlKbAiEAu0Ip +TvTF7SfYMd5me26T40Fa2mAWYzWQLF10WvqheOcCggEAarH6R0LvhcWInCB1Yr4U +FA3QjynYWfxZZ6g+Tf/NxPQmVj720SEm+QfiZnrtSReTctBHJPkcrzHVU53LNVPx +CxvoVJUEdUhxWUV0o0PXBUfJZB3JdVXLGEu4MlXkwFh87+S3Y/+9yeMog3SaaqDA +cISNUQSWd+q5oiAeFdYiaTjY+qD+UGBChL/w8QSrr0BRiHF1EskKI3bFmqQzDjD7 +9xm4wEGUiMvF+dHa50rjIzD+EyWvbkq3HonB7N7DDciVQ1V/qGmI6dvP5yf/Ml90 +YDL8QIrXWP2p81FlvfU3QE9tjI6Bo5cjTavRU7WORN0it9B3pmvyL++E1EGd6hBr +qwOCAQUAAoIBAFR3VCzbhpt1iR/LpAlN2QIeQ4r2vkng6CJaxSQZBKBc1CjFHAXy +4QIThVpagyH+QM4I5pPIxkcXpFwEPHzuZow8/4DMA6IlPJ6R8ih1IgWGqzASzXYv +ZrlasBUjTS4no54L9PueAfZ2G0Rc9ePZ8snH9zsQVu5Rd2+zUkAUzR0jCT/zveeQ +t9c/px8356C4beEUAFoiXbSNMDruVdFemEwC5oCIwMBamPUxf//Og74Wg0W/0DNW +j8ffKNht51i+537vjBjVlLd/ETXARhAZg83jcqKKG0I4hECFZE5xBl0flU82pQAf +eKk0vNRiuAXKgxNor+pnpXC3qqMkxuwQBls= +-----END PUBLIC KEY----- diff --git a/net/data/ssl/certificates/android-test-key-dsa.pem b/net/data/ssl/certificates/android-test-key-dsa.pem new file mode 100644 index 0000000..3e0ec61 --- /dev/null +++ b/net/data/ssl/certificates/android-test-key-dsa.pem @@ -0,0 +1,20 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIDVQIBAAKCAQEA2ia7sEJhNbjEtlpxpei9lEMvIh5nLO+IFo8pv/B1VXS17At8 +BqFxo9L492a+w6YM61wQFo9iSC1QKSYGxSGxBSwB3L+gXf9z420a72uDzEMqbpda +Po/SIhZ9qHpnlaAs++5s+WnM36uwKIjJzKXT+dNanADhdoA0eta9QTvEEVfYJGzi +6WR19dNTQfRlvjykewvkiQTea1VV9mILcT3vypuYxYCLkj++nkr8RodZFAKRQ4ah +A4exnJOUpbepSQz49/fTGQu+F663W462Wzv47eU7C8UWnEryNMGl/uPflJx2G93I +z8pzJAYCw5e4rTJbk5t50vCJICioGD4hAF5SmwIhALtCKU70xe0n2DHeZntuk+NB +WtpgFmM1kCxddFr6oXjnAoIBAGqx+kdC74XFiJwgdWK+FBQN0I8p2Fn8WWeoPk3/ +zcT0JlY+9tEhJvkH4mZ67UkXk3LQRyT5HK8x1VOdyzVT8Qsb6FSVBHVIcVlFdKND +1wVHyWQdyXVVyxhLuDJV5MBYfO/kt2P/vcnjKIN0mmqgwHCEjVEElnfquaIgHhXW +Imk42Pqg/lBgQoS/8PEEq69AUYhxdRLJCiN2xZqkMw4w+/cZuMBBlIjLxfnR2udK +4yMw/hMlr25Ktx6Jwezeww3IlUNVf6hpiOnbz+cn/zJfdGAy/ECK11j9qfNRZb31 +N0BPbYyOgaOXI02r0VO1jkTdIrfQd6Zr8i/vhNRBneoQa6sCggEAVHdULNuGm3WJ +H8ukCU3ZAh5Diva+SeDoIlrFJBkEoFzUKMUcBfLhAhOFWlqDIf5Azgjmk8jGRxek +XAQ8fO5mjDz/gMwDoiU8npHyKHUiBYarMBLNdi9muVqwFSNNLiejngv0+54B9nYb +RFz149nyycf3OxBW7lF3b7NSQBTNHSMJP/O955C31z+nHzfnoLht4RQAWiJdtI0w +Ou5V0V6YTALmgIjAwFqY9TF//86DvhaDRb/QM1aPx98o2G3nWL7nfu+MGNWUt38R +NcBGEBmDzeNyooobQjiEQIVkTnEGXR+VTzalAB94qTS81GK4BcqDE2iv6melcLeq +oyTG7BAGWwIgUF1cgjzMuvW665RmAumSL2vvkRuNm54i8cHSq53ZVq8= +-----END DSA PRIVATE KEY----- diff --git a/net/data/ssl/certificates/android-test-key-ecdsa-public.pem b/net/data/ssl/certificates/android-test-key-ecdsa-public.pem new file mode 100644 index 0000000..4512622 --- /dev/null +++ b/net/data/ssl/certificates/android-test-key-ecdsa-public.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJH4uX5l3CYLPIQ7tXxBjtPZN7HVf +l4uyPAs6VCPitxLjEcKq3w/wwnPAbhbXN7bnC6lq1Yro/5vlpa1RGB46yQ== +-----END PUBLIC KEY----- diff --git a/net/data/ssl/certificates/android-test-key-ecdsa.pem b/net/data/ssl/certificates/android-test-key-ecdsa.pem new file mode 100644 index 0000000..6f128bd --- /dev/null +++ b/net/data/ssl/certificates/android-test-key-ecdsa.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBBw== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIFb6/5kje8LB6bKDjQbfr2d4wfvLjy+SNs7j4J1eEF+FoAoGCCqGSM49 +AwEHoUQDQgAEJH4uX5l3CYLPIQ7tXxBjtPZN7HVfl4uyPAs6VCPitxLjEcKq3w/w +wnPAbhbXN7bnC6lq1Yro/5vlpa1RGB46yQ== +-----END EC PRIVATE KEY----- diff --git a/net/data/ssl/certificates/android-test-key-rsa.pem b/net/data/ssl/certificates/android-test-key-rsa.pem new file mode 100644 index 0000000..cd771fb6 --- /dev/null +++ b/net/data/ssl/certificates/android-test-key-rsa.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAtGeaaS0gTqtgxhVXHEvwq04nAj8G0VCC8QjJW1ULVA576lhy +3IfdP3CxSLUYaJ4QNlXswDkbV8U8VPwresHqd1OTcCII774hLNFw7QtTmi1dM3rL +kOHApeCUa32VzZSuzCyu9f2/T4HvEH367GDTIxk1NEoXKSuehjMUFiqmtJEZjHSK +agq3p8NLTP/WpMm1WfEg5QRBQaQ82hOYN8CDaRSOW2GuHw+qZAtA7n033yX8cu3H +khPT5INk04/byZVDMNK1hbf90y+0XeZRZAKZ9rklMnz3jPJvLLsVwSMexJnRbe/G +cQyE9V1jHN9aGeBlm+9xvv3VjddNhaxhYLDYuQIDAQABAoIBAF5MCRoQzGJSkjL3 +1KCl0Ra5swoph5bBTrBOt3FV8qXtLDhCI0fCfJM8hG5MuoV0mWTNZQLU1sX6Ap8p +cFCqK7RTqy1hnOozp4OVtkExOnHMZHsUJHOGjPwnd2z4J+VdYkC22n0aNXWJpTwp +nY8QzUv7USQT1ide9W2QJV+wy5J1pQt4U/TH44FD6ceYXzEKCwHb1FXK4YOS0xgR +mW3gKFdHAYwk6OTyJOTLoa8zRL+8w7cTx2uNbf5EXeSMPjVl6SsVqJxOoBxVhi6M +Hj/pV4xs2sF2iqRlRZLpR5nOeJEwiELCymDgSzvSh0PZKAQzEvdYbH91vSEu6d+Q +gSGgkAECgYEA1wepPWDJyYPXPfemqeNrq7uVMwO3EI9IwlI+ouZ1sPHDEcQ3dNGw +5/QK7mgG++Wn+EfDkD1U26qGaGiWlCHTC/YlQ1tyrw3KqqV4QT+AIHJnr52PMUTu +vpSUJEfLMEboa8nsdHRHvScqqhijgI5fY4RJa20EVAjA0DRB/BSyEuECgYEA1scO +Q7s0jXWIfK/Y0z8WFfcHd1+xdIEueGqfI/Qx57Gsaen6mOKC6nESzq2GOiiXdX4X +NzdqPhZ1TWt+8FtSjDx61xFYldVf/0mSMn0skUSshKkBxZoKT65mmH2aRn9Mh1W7 +PaIN/JCi20kaZqhbaNhPi/Dd6SK2CIJco9Dx2NkCgYEAjHYqrTdeWM5QeeAd9Hfk +S4f7TBmvKZgPVTBYThzw4Cbs39wmxZ58Suh1g4pclYtND7gBHWWS2vMnXWiEhDsc +G4IskTVZUtRVgOcaCLUsQwW4iVUIxoxa0A9KPfDP37dR96ctWFzkx8Cf9ACoPT/D +O8ScGRpba3FUUizwtXPnZsECgYA7ltjPU/ZdtRlcNtG6sosnJvWsWiF7CIhjInnq +2Mqr1PDYJfHAT0AxWZP1QdG2+yIimAxK5pYUidib1VJPz5aUkAco+ogQcjYDN19X +oMEnwNz4pYd3Uqi/uMyATIDsRE9wUQn1LKwiweJdYufvSZCrAzD2y6pWD6pfrAOV +89fV6QKBgQDADTt1uVUPm2XpAkRAgKT7hncHnMcVqXvQsrqbHlbC7SARTbX0lUga +eO18L/k3h5XGXSQbVeVHdo5EZOEqQkiGcQbOgtRosMp/+6XtPMyvMMxWRhjIcyAJ +5czOAcXm8iVv7m89w+QnK+zMsLkY0j0X70lDXq7Tui+VH9lXa4wjtw== +-----END RSA PRIVATE KEY----- diff --git a/net/data/ssl/scripts/generate-android-test-keys.sh b/net/data/ssl/scripts/generate-android-test-keys.sh new file mode 100755 index 0000000..1c297e3 --- /dev/null +++ b/net/data/ssl/scripts/generate-android-test-keys.sh @@ -0,0 +1,56 @@ +#!/bin/sh + +# Copyright (c) 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. + +# This script is used to generate the test keys for the unit test in +# android/keystore_unittest.c. +# +# These are test RSA / DSA / ECDSA private keys in PKCS#8 format, as well +# as the corresponding DSA / ECDSA public keys. +# + +# Exit script as soon a something fails. +set -e + +mkdir -p out +rm -rf out/* + +# Generate a single 2048-bits RSA private key in PKCS#8 format. +KEY=android-test-key-rsa +openssl genrsa \ + -out out/$KEY.pem \ + 2048 + +# Generate a 2048-bits DSA private key in PKCS#8 format, +# as well as its public key in X.509 DER format. +KEY=android-test-key-dsa +openssl dsaparam \ + -out out/$KEY.param.pem \ + 2048 + +openssl gendsa \ + -out out/$KEY.pem \ + out/$KEY.param.pem + +openssl dsa \ + -in out/$KEY.pem \ + -outform PEM \ + -out out/$KEY-public.pem \ + -pubout + +rm out/$KEY.param.pem + +# Generate an ECDSA private key, in PKCS#8 format, +# as well as its public key in X.509 DER format. +KEY=android-test-key-ecdsa +openssl ecparam -genkey -name prime256v1 -out out/$KEY.pem + +openssl ec \ + -in out/$KEY.pem \ + -outform PEM \ + -out out/$KEY-public.pem \ + -pubout + +# We're done here. diff --git a/net/net.gyp b/net/net.gyp index 9cb1251..51cdaf6 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -56,6 +56,10 @@ 'net_resources', ], 'sources': [ + 'android/keystore.cc', + 'android/keystore.h', + 'android/keystore_openssl.cc', + 'android/keystore_openssl.h', 'android/gurl_utils.cc', 'android/gurl_utils.h', 'android/net_jni_registrar.cc', @@ -1240,6 +1244,11 @@ 'base/openssl_memory_private_key_store.cc', 'base/test_root_certs_openssl.cc', ], + # The net/android/keystore_openssl.cc source file needs to + # access an OpenSSL-internal header. + 'include_dirs': [ + '../third_party/openssl', + ], }, { # else OS != "android" 'defines': [ # These are the features Android doesn't support. @@ -1303,6 +1312,7 @@ 'net_test_support', ], 'sources': [ + 'android/keystore_unittest.cc', 'android/network_change_notifier_android_unittest.cc', 'base/address_list_unittest.cc', 'base/address_tracker_linux_unittest.cc', @@ -1612,6 +1622,10 @@ 'dns/dns_config_service_posix_unittest.cc', 'base/client_cert_store_impl_unittest.cc', ], + 'dependencies': [ + 'net_javatests', + 'net_test_jni_headers', + ], }], [ 'use_glib == 1', { 'dependencies': [ @@ -2327,6 +2341,7 @@ 'target_name': 'net_jni_headers', 'type': 'none', 'sources': [ + 'android/java/src/org/chromium/net/AndroidKeyStore.java', 'android/java/src/org/chromium/net/AndroidNetworkLibrary.java', 'android/java/src/org/chromium/net/GURLUtils.java', 'android/java/src/org/chromium/net/NetworkChangeNotifier.java', @@ -2335,6 +2350,27 @@ 'variables': { 'jni_gen_dir': 'net', }, + 'direct_dependent_settings': { + 'include_dirs': [ + '<(SHARED_INTERMEDIATE_DIR)/net', + ], + }, + 'includes': [ '../build/jni_generator.gypi' ], + }, + { + 'target_name': 'net_test_jni_headers', + 'type': 'none', + 'sources': [ + 'android/javatests/src/org/chromium/net/AndroidKeyStoreTestUtil.java', + ], + 'variables': { + 'jni_gen_dir': 'net', + }, + 'direct_dependent_settings': { + 'include_dirs': [ + '<(SHARED_INTERMEDIATE_DIR)/net', + ], + }, 'includes': [ '../build/jni_generator.gypi' ], }, { @@ -2348,6 +2384,7 @@ '../base/base.gyp:base', 'net_errors_java', 'certificate_mime_types_java', + 'private_key_types_java', ], 'includes': [ '../build/java.gypi' ], }, @@ -2398,6 +2435,18 @@ }, 'includes': [ '../build/android/java_cpp_template.gypi' ], }, + { + 'target_name': 'private_key_types_java', + 'type': 'none', + 'sources': [ + 'android/java/PrivateKeyType.template', + ], + 'variables': { + 'package_name': 'org.chromium.net', + 'template_deps': ['android/private_key_type_list.h'], + }, + 'includes': [ '../build/android/java_cpp_template.gypi' ], + }, ], }], # Special target to wrap a gtest_target_type==shared_library @@ -2410,6 +2459,7 @@ 'type': 'none', 'dependencies': [ 'net_java', + 'net_javatests', 'net_unittests', ], 'variables': { diff --git a/net/socket/ssl_client_socket_openssl.h b/net/socket/ssl_client_socket_openssl.h index 12d9229..74ada9a 100644 --- a/net/socket/ssl_client_socket_openssl.h +++ b/net/socket/ssl_client_socket_openssl.h @@ -16,9 +16,14 @@ #include "net/socket/ssl_client_socket.h" #include "net/socket/client_socket_handle.h" +// Avoid including misc OpenSSL headers, i.e.: +// <openssl/bio.h> typedef struct bio_st BIO; +// <openssl/evp.h> typedef struct evp_pkey_st EVP_PKEY; +// <openssl/ssl.h> typedef struct ssl_st SSL; +// <openssl/x509.h> typedef struct x509_st X509; namespace net { |