blob: fd07440dd0815ff84ba4fbb03363e2025ead0fcf [file] [log] [blame]
// 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>
#include <openssl/ec.h>
#include <openssl/engine.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/rsa.h>
#include <stdint.h>
#include <memory>
#include "base/android/build_info.h"
#include "base/android/scoped_java_ref.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "crypto/openssl_util.h"
#include "net/android/keystore.h"
#include "net/android/legacy_openssl.h"
#include "net/ssl/scoped_openssl_types.h"
#include "net/ssl/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 digest during the OpenSSL handshake for TLS.
//
// The OpenSSL EVP_PKEY type is a generic wrapper around key pairs.
// Internally, it can hold a pointer to a RSA 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 whose
// fields point 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 storing it in a |KeyExData| structure
// that's referenced by the key using |EX_DATA|.
using base::android::ScopedJavaGlobalRef;
using base::android::ScopedJavaLocalRef;
namespace net {
namespace android {
namespace {
extern const RSA_METHOD android_rsa_method;
extern const ECDSA_METHOD android_ecdsa_method;
// KeyExData contains the data that is contained in the EX_DATA of the RSA and
// EC_KEY objects that are created to wrap Android system keys.
struct KeyExData {
// private_key contains a reference to a Java, private-key object.
ScopedJavaGlobalRef<jobject> private_key;
// legacy_rsa, if not NULL, points to an RSA* in the system's OpenSSL (which
// might not be ABI compatible with Chromium).
AndroidRSA* legacy_rsa;
// cached_size contains the "size" of the key. This is the size of the
// modulus (in bytes) for RSA, or the group order size for ECDSA. This
// avoids calling into Java to calculate the size.
size_t cached_size;
};
// ExDataDup is called when one of the RSA or EC_KEY objects is duplicated. We
// don't support this and it should never happen.
int ExDataDup(CRYPTO_EX_DATA* to,
const CRYPTO_EX_DATA* from,
void** from_d,
int index,
long argl,
void* argp) {
CHECK_EQ((void*)NULL, *from_d);
return 0;
}
// ExDataFree is called when one of the RSA or EC_KEY objects is freed.
void ExDataFree(void* parent,
void* ptr,
CRYPTO_EX_DATA* ad,
int index,
long argl,
void* argp) {
// Ensure the global JNI reference created with this wrapper is
// properly destroyed with it.
KeyExData* ex_data = reinterpret_cast<KeyExData*>(ptr);
delete ex_data;
}
// BoringSSLEngine is a BoringSSL ENGINE that implements RSA and ECDSA by
// forwarding the requested operations to the Java libraries.
class BoringSSLEngine {
public:
BoringSSLEngine()
: rsa_index_(RSA_get_ex_new_index(0 /* argl */,
NULL /* argp */,
NULL /* new_func */,
ExDataDup,
ExDataFree)),
ec_key_index_(EC_KEY_get_ex_new_index(0 /* argl */,
NULL /* argp */,
NULL /* new_func */,
ExDataDup,
ExDataFree)),
engine_(ENGINE_new()) {
ENGINE_set_RSA_method(engine_, &android_rsa_method,
sizeof(android_rsa_method));
ENGINE_set_ECDSA_method(engine_, &android_ecdsa_method,
sizeof(android_ecdsa_method));
}
int rsa_ex_index() const { return rsa_index_; }
int ec_key_ex_index() const { return ec_key_index_; }
const ENGINE* engine() const { return engine_; }
private:
const int rsa_index_;
const int ec_key_index_;
ENGINE* const engine_;
};
base::LazyInstance<BoringSSLEngine>::Leaky global_boringssl_engine =
LAZY_INSTANCE_INITIALIZER;
// VectorBignumSize returns the number of bytes needed to represent the bignum
// given in |v|, i.e. the length of |v| less any leading zero bytes.
size_t VectorBignumSize(const std::vector<uint8_t>& v) {
size_t size = v.size();
// Ignore any leading zero bytes.
for (size_t i = 0; i < v.size() && v[i] == 0; i++) {
size--;
}
return size;
}
KeyExData* RsaGetExData(const RSA* rsa) {
return reinterpret_cast<KeyExData*>(
RSA_get_ex_data(rsa, global_boringssl_engine.Get().rsa_ex_index()));
}
size_t RsaMethodSize(const RSA* rsa) {
const KeyExData* ex_data = RsaGetExData(rsa);
return ex_data->cached_size;
}
int RsaMethodEncrypt(RSA* rsa,
size_t* out_len,
uint8_t* out,
size_t max_out,
const uint8_t* in,
size_t in_len,
int padding) {
NOTIMPLEMENTED();
OPENSSL_PUT_ERROR(RSA, RSA_R_UNKNOWN_ALGORITHM_TYPE);
return 0;
}
int RsaMethodSignRaw(RSA* rsa,
size_t* out_len,
uint8_t* out,
size_t max_out,
const uint8_t* in,
size_t in_len,
int padding) {
DCHECK_EQ(RSA_PKCS1_PADDING, padding);
if (padding != RSA_PKCS1_PADDING) {
// TODO(davidben): If we need to, we can implement RSA_NO_PADDING
// by using javax.crypto.Cipher and picking either the
// "RSA/ECB/NoPadding" or "RSA/ECB/PKCS1Padding" transformation as
// appropriate. I believe support for both of these was added in
// the same Android version as the "NONEwithRSA"
// java.security.Signature algorithm, so the same version checks
// for GetRsaLegacyKey should work.
OPENSSL_PUT_ERROR(RSA, RSA_R_UNKNOWN_PADDING_TYPE);
return 0;
}
// Retrieve private key JNI reference.
const KeyExData* ex_data = RsaGetExData(rsa);
if (!ex_data || !ex_data->private_key.obj()) {
LOG(WARNING) << "Null JNI reference passed to RsaMethodSignRaw!";
OPENSSL_PUT_ERROR(RSA, ERR_R_INTERNAL_ERROR);
return 0;
}
// Pre-4.2 legacy codepath.
if (ex_data->legacy_rsa) {
int ret = ex_data->legacy_rsa->meth->rsa_priv_enc(
in_len, in, out, ex_data->legacy_rsa, ANDROID_RSA_PKCS1_PADDING);
if (ret < 0) {
LOG(WARNING) << "Could not sign message in RsaMethodSignRaw!";
// System OpenSSL will use a separate error queue, so it is still
// necessary to push a new error.
//
// TODO(davidben): It would be good to also clear the system error queue
// if there were some way to convince Java to do it. (Without going
// through Java, it's difficult to get a handle on a system OpenSSL
// function; dlopen loads a second copy.)
OPENSSL_PUT_ERROR(RSA, ERR_R_INTERNAL_ERROR);
return 0;
}
*out_len = ret;
return 1;
}
base::StringPiece from_piece(reinterpret_cast<const char*>(in), in_len);
std::vector<uint8_t> result;
// For RSA keys, this function behaves as RSA_private_encrypt with
// PKCS#1 padding.
if (!RawSignDigestWithPrivateKey(ex_data->private_key.obj(), from_piece,
&result)) {
LOG(WARNING) << "Could not sign message in RsaMethodSignRaw!";
OPENSSL_PUT_ERROR(RSA, ERR_R_INTERNAL_ERROR);
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;
OPENSSL_PUT_ERROR(RSA, ERR_R_INTERNAL_ERROR);
return 0;
}
if (max_out < expected_size) {
OPENSSL_PUT_ERROR(RSA, RSA_R_DATA_TOO_LARGE);
return 0;
}
// Copy result to OpenSSL-provided buffer. RawSignDigestWithPrivateKey
// should pad with leading 0s, but if it doesn't, pad the result.
size_t zero_pad = expected_size - result.size();
memset(out, 0, zero_pad);
memcpy(out + zero_pad, &result[0], result.size());
*out_len = expected_size;
return 1;
}
int RsaMethodDecrypt(RSA* rsa,
size_t* out_len,
uint8_t* out,
size_t max_out,
const uint8_t* in,
size_t in_len,
int padding) {
NOTIMPLEMENTED();
OPENSSL_PUT_ERROR(RSA, RSA_R_UNKNOWN_ALGORITHM_TYPE);
return 0;
}
int RsaMethodVerifyRaw(RSA* rsa,
size_t* out_len,
uint8_t* out,
size_t max_out,
const uint8_t* in,
size_t in_len,
int padding) {
NOTIMPLEMENTED();
OPENSSL_PUT_ERROR(RSA, RSA_R_UNKNOWN_ALGORITHM_TYPE);
return 0;
}
const RSA_METHOD android_rsa_method = {
{
0 /* references */, 1 /* is_static */
} /* common */,
nullptr /* app_data */,
nullptr /* init */,
nullptr /* finish */,
RsaMethodSize,
nullptr /* sign */,
nullptr /* verify */,
RsaMethodEncrypt,
RsaMethodSignRaw,
RsaMethodDecrypt,
RsaMethodVerifyRaw,
nullptr /* private_transform */,
nullptr /* mod_exp */,
nullptr /* bn_mod_exp */,
RSA_FLAG_OPAQUE,
nullptr /* keygen */,
nullptr /* multi_prime_keygen */,
nullptr /* supports_digest */,
};
// Setup an EVP_PKEY to wrap an existing platform RSA PrivateKey object.
// |private_key| is the JNI reference (local or global) to the object.
// |legacy_rsa|, if non-NULL, is a pointer to the system OpenSSL RSA object
// backing |private_key|. This parameter is only used for Android < 4.2 to
// implement key operations not exposed by the platform.
// Returns a new EVP_PKEY on success, NULL 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.
crypto::ScopedEVP_PKEY CreateRsaPkeyWrapper(
jobject private_key,
AndroidRSA* legacy_rsa,
const crypto::OpenSSLErrStackTracer& tracer) {
crypto::ScopedRSA rsa(RSA_new_method(global_boringssl_engine.Get().engine()));
std::vector<uint8_t> modulus;
if (!GetRSAKeyModulus(private_key, &modulus)) {
LOG(ERROR) << "Failed to get private key modulus";
return nullptr;
}
std::unique_ptr<KeyExData> ex_data(new KeyExData);
ex_data->private_key.Reset(nullptr, private_key);
if (ex_data->private_key.is_null()) {
LOG(ERROR) << "Could not create global JNI reference";
return nullptr;
}
ex_data->legacy_rsa = legacy_rsa;
ex_data->cached_size = VectorBignumSize(modulus);
RSA_set_ex_data(rsa.get(), global_boringssl_engine.Get().rsa_ex_index(),
ex_data.release());
crypto::ScopedEVP_PKEY pkey(EVP_PKEY_new());
if (!pkey || !EVP_PKEY_set1_RSA(pkey.get(), rsa.get()))
return nullptr;
return pkey;
}
// On Android < 4.2, the libkeystore.so ENGINE uses CRYPTO_EX_DATA and is not
// added to the global engine list. If all references to it are dropped, OpenSSL
// will dlclose the module, leaving a dangling function pointer in the RSA
// CRYPTO_EX_DATA class. To work around this, leak an extra reference to the
// ENGINE we extract in GetRsaLegacyKey.
//
// In 4.2, this change avoids the problem:
// https://android.googlesource.com/platform/libcore/+/106a8928fb4249f2f3d4dba1dddbe73ca5cb3d61
//
// https://crbug.com/381465
class KeystoreEngineWorkaround {
public:
KeystoreEngineWorkaround() {}
void LeakEngine(jobject private_key) {
if (!engine_.is_null())
return;
ScopedJavaLocalRef<jobject> engine =
GetOpenSSLEngineForPrivateKey(private_key);
if (engine.is_null()) {
NOTREACHED();
return;
}
engine_.Reset(engine);
}
private:
ScopedJavaGlobalRef<jobject> engine_;
};
void LeakEngine(jobject private_key) {
static base::LazyInstance<KeystoreEngineWorkaround>::Leaky s_instance =
LAZY_INSTANCE_INITIALIZER;
s_instance.Get().LeakEngine(private_key);
}
// Creates an EVP_PKEY wrapper corresponding to the RSA key
// |private_key|. Returns nullptr on failure.
crypto::ScopedEVP_PKEY GetRsaPkeyWrapper(jobject private_key) {
const int kAndroid42ApiLevel = 17;
crypto::OpenSSLErrStackTracer tracer(FROM_HERE);
if (base::android::BuildInfo::GetInstance()->sdk_int() >=
kAndroid42ApiLevel) {
return CreateRsaPkeyWrapper(private_key, nullptr, tracer);
}
// Route around platform limitation: if Android < 4.2, then
// base::android::RawSignDigestWithPrivateKey() cannot work, so try to get the
// system OpenSSL's EVP_PKEY backing this PrivateKey object.
AndroidEVP_PKEY* sys_pkey = GetOpenSSLSystemHandleForPrivateKey(private_key);
if (sys_pkey == nullptr)
return nullptr;
if (sys_pkey->type != ANDROID_EVP_PKEY_RSA) {
LOG(ERROR) << "Private key has wrong type!";
return nullptr;
}
AndroidRSA* sys_rsa = sys_pkey->pkey.rsa;
if (sys_rsa->engine) {
// |private_key| may not have an engine if the PrivateKey did not come
// from the key store, such as in unit tests.
if (strcmp(sys_rsa->engine->id, "keystore") == 0) {
LeakEngine(private_key);
} else {
NOTREACHED();
}
}
return CreateRsaPkeyWrapper(private_key, sys_rsa, tracer);
}
// 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.
jobject EcKeyGetKey(const EC_KEY* ec_key) {
KeyExData* ex_data = reinterpret_cast<KeyExData*>(EC_KEY_get_ex_data(
ec_key, global_boringssl_engine.Get().ec_key_ex_index()));
return ex_data->private_key.obj();
}
size_t EcdsaMethodGroupOrderSize(const EC_KEY* ec_key) {
KeyExData* ex_data = reinterpret_cast<KeyExData*>(EC_KEY_get_ex_data(
ec_key, global_boringssl_engine.Get().ec_key_ex_index()));
return ex_data->cached_size;
}
int EcdsaMethodSign(const uint8_t* digest,
size_t digest_len,
uint8_t* sig,
unsigned int* sig_len,
EC_KEY* ec_key) {
// Retrieve private key JNI reference.
jobject private_key = EcKeyGetKey(ec_key);
if (!private_key) {
LOG(WARNING) << "Null JNI reference passed to EcdsaMethodSign!";
return 0;
}
// Sign message with it through JNI.
std::vector<uint8_t> signature;
base::StringPiece digest_sp(reinterpret_cast<const char*>(digest),
digest_len);
if (!RawSignDigestWithPrivateKey(private_key, digest_sp, &signature)) {
LOG(WARNING) << "Could not sign message in EcdsaMethodSign!";
return 0;
}
// Note: With ECDSA, the actual signature may be smaller than
// ECDSA_size().
size_t max_expected_size = ECDSA_size(ec_key);
if (signature.size() > max_expected_size) {
LOG(ERROR) << "ECDSA Signature size mismatch, actual: " << signature.size()
<< ", expected <= " << max_expected_size;
return 0;
}
memcpy(sig, &signature[0], signature.size());
*sig_len = signature.size();
return 1;
}
int EcdsaMethodVerify(const uint8_t* digest,
size_t digest_len,
const uint8_t* sig,
size_t sig_len,
EC_KEY* ec_key) {
NOTIMPLEMENTED();
OPENSSL_PUT_ERROR(ECDSA, ECDSA_R_NOT_IMPLEMENTED);
return 0;
}
// Setup an EVP_PKEY to wrap an existing platform PrivateKey object.
// |private_key| is the JNI reference (local or global) to the object.
// Returns a new EVP_PKEY on success, NULL 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.
crypto::ScopedEVP_PKEY GetEcdsaPkeyWrapper(jobject private_key) {
crypto::OpenSSLErrStackTracer tracer(FROM_HERE);
crypto::ScopedEC_KEY ec_key(
EC_KEY_new_method(global_boringssl_engine.Get().engine()));
std::vector<uint8_t> order;
if (!GetECKeyOrder(private_key, &order)) {
LOG(ERROR) << "Can't extract order parameter from EC private key";
return nullptr;
}
std::unique_ptr<KeyExData> ex_data(new KeyExData);
ex_data->private_key.Reset(nullptr, private_key);
if (ex_data->private_key.is_null()) {
LOG(ERROR) << "Can't create global JNI reference";
return nullptr;
}
ex_data->legacy_rsa = nullptr;
ex_data->cached_size = VectorBignumSize(order);
EC_KEY_set_ex_data(ec_key.get(),
global_boringssl_engine.Get().ec_key_ex_index(),
ex_data.release());
crypto::ScopedEVP_PKEY pkey(EVP_PKEY_new());
if (!pkey || !EVP_PKEY_set1_EC_KEY(pkey.get(), ec_key.get()))
return nullptr;
return pkey;
}
const ECDSA_METHOD android_ecdsa_method = {
{
0 /* references */, 1 /* is_static */
} /* common */,
NULL /* app_data */,
NULL /* init */,
NULL /* finish */,
EcdsaMethodGroupOrderSize,
EcdsaMethodSign,
EcdsaMethodVerify,
ECDSA_FLAG_OPAQUE,
};
} // namespace
crypto::ScopedEVP_PKEY GetOpenSSLPrivateKeyWrapper(jobject private_key) {
// 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:
return GetRsaPkeyWrapper(private_key);
case PRIVATE_KEY_TYPE_ECDSA:
return GetEcdsaPkeyWrapper(private_key);
default:
LOG(WARNING)
<< "GetOpenSSLPrivateKeyWrapper() called with invalid key type";
return nullptr;
}
}
} // namespace android
} // namespace net