Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1307)

Side by Side Diff: net/android/keystore_openssl.cc

Issue 2291213002: Remove ENGINE indirection from Android SSLPrivateKey. (Closed)
Patch Set: re-delete undeleted files Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « net/android/keystore_openssl.h ('k') | net/android/keystore_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/android/keystore_openssl.h"
6
7 #include <jni.h>
8 #include <openssl/bn.h>
9 #include <openssl/ec.h>
10 #include <openssl/engine.h>
11 #include <openssl/err.h>
12 #include <openssl/evp.h>
13 #include <openssl/rsa.h>
14 #include <stdint.h>
15
16 #include <memory>
17
18 #include "base/android/build_info.h"
19 #include "base/android/scoped_java_ref.h"
20 #include "base/lazy_instance.h"
21 #include "base/logging.h"
22 #include "crypto/openssl_util.h"
23 #include "net/android/keystore.h"
24 #include "net/android/legacy_openssl.h"
25 #include "net/ssl/scoped_openssl_types.h"
26 #include "net/ssl/ssl_client_cert_type.h"
27
28 // IMPORTANT NOTE: The following code will currently only work when used
29 // to implement client certificate support with OpenSSL. That's because
30 // only the signing operations used in this use case are implemented here.
31 //
32 // Generally speaking, OpenSSL provides many different ways to sign
33 // digests. This code doesn't support all these cases, only the ones that
34 // are required to sign the digest during the OpenSSL handshake for TLS.
35 //
36 // The OpenSSL EVP_PKEY type is a generic wrapper around key pairs.
37 // Internally, it can hold a pointer to a RSA or ECDSA structure, which model
38 // keypair implementations of each respective crypto algorithm.
39 //
40 // The RSA type has a 'method' field pointer to a vtable-like structure
41 // called a RSA_METHOD. This contains several function pointers that
42 // correspond to operations on RSA keys (e.g. decode/encode with public
43 // key, decode/encode with private key, signing, validation), as well as
44 // a few flags.
45 //
46 // For example, the RSA_sign() function will call "method->rsa_sign()" if
47 // method->rsa_sign is not NULL, otherwise, it will perform a regular
48 // signing operation using the other fields in the RSA structure (which
49 // are used to hold the typical modulus / exponent / parameters for the
50 // key pair).
51 //
52 // This source file thus defines a custom RSA_METHOD structure whose
53 // fields point to static methods used to implement the corresponding
54 // RSA operation using platform Android APIs.
55 //
56 // However, the platform APIs require a jobject JNI reference to work. It must
57 // be stored in the RSA instance, or made accessible when the custom RSA
58 // methods are called. This is done by storing it in a |KeyExData| structure
59 // that's referenced by the key using |EX_DATA|.
60
61 using base::android::JavaRef;
62 using base::android::ScopedJavaGlobalRef;
63 using base::android::ScopedJavaLocalRef;
64
65 namespace net {
66 namespace android {
67
68 namespace {
69
70 extern const RSA_METHOD android_rsa_method;
71 extern const ECDSA_METHOD android_ecdsa_method;
72
73 // KeyExData contains the data that is contained in the EX_DATA of the RSA and
74 // EC_KEY objects that are created to wrap Android system keys.
75 struct KeyExData {
76 // private_key contains a reference to a Java, private-key object.
77 ScopedJavaGlobalRef<jobject> private_key;
78 // legacy_rsa, if not NULL, points to an RSA* in the system's OpenSSL (which
79 // might not be ABI compatible with Chromium).
80 AndroidRSA* legacy_rsa;
81 // cached_size contains the "size" of the key. This is the size of the
82 // modulus (in bytes) for RSA, or the group order size for ECDSA. This
83 // avoids calling into Java to calculate the size.
84 size_t cached_size;
85 };
86
87 // ExDataDup is called when one of the RSA or EC_KEY objects is duplicated. We
88 // don't support this and it should never happen.
89 int ExDataDup(CRYPTO_EX_DATA* to,
90 const CRYPTO_EX_DATA* from,
91 void** from_d,
92 int index,
93 long argl,
94 void* argp) {
95 CHECK_EQ((void*)NULL, *from_d);
96 return 0;
97 }
98
99 // ExDataFree is called when one of the RSA or EC_KEY objects is freed.
100 void ExDataFree(void* parent,
101 void* ptr,
102 CRYPTO_EX_DATA* ad,
103 int index,
104 long argl,
105 void* argp) {
106 // Ensure the global JNI reference created with this wrapper is
107 // properly destroyed with it.
108 KeyExData* ex_data = reinterpret_cast<KeyExData*>(ptr);
109 delete ex_data;
110 }
111
112 // BoringSSLEngine is a BoringSSL ENGINE that implements RSA and ECDSA by
113 // forwarding the requested operations to the Java libraries.
114 class BoringSSLEngine {
115 public:
116 BoringSSLEngine()
117 : rsa_index_(RSA_get_ex_new_index(0 /* argl */,
118 NULL /* argp */,
119 NULL /* new_func */,
120 ExDataDup,
121 ExDataFree)),
122 ec_key_index_(EC_KEY_get_ex_new_index(0 /* argl */,
123 NULL /* argp */,
124 NULL /* new_func */,
125 ExDataDup,
126 ExDataFree)),
127 engine_(ENGINE_new()) {
128 ENGINE_set_RSA_method(engine_, &android_rsa_method,
129 sizeof(android_rsa_method));
130 ENGINE_set_ECDSA_method(engine_, &android_ecdsa_method,
131 sizeof(android_ecdsa_method));
132 }
133
134 int rsa_ex_index() const { return rsa_index_; }
135 int ec_key_ex_index() const { return ec_key_index_; }
136
137 const ENGINE* engine() const { return engine_; }
138
139 private:
140 const int rsa_index_;
141 const int ec_key_index_;
142 ENGINE* const engine_;
143 };
144
145 base::LazyInstance<BoringSSLEngine>::Leaky global_boringssl_engine =
146 LAZY_INSTANCE_INITIALIZER;
147
148 // VectorBignumSize returns the number of bytes needed to represent the bignum
149 // given in |v|, i.e. the length of |v| less any leading zero bytes.
150 size_t VectorBignumSize(const std::vector<uint8_t>& v) {
151 size_t size = v.size();
152 // Ignore any leading zero bytes.
153 for (size_t i = 0; i < v.size() && v[i] == 0; i++) {
154 size--;
155 }
156 return size;
157 }
158
159 KeyExData* RsaGetExData(const RSA* rsa) {
160 return reinterpret_cast<KeyExData*>(
161 RSA_get_ex_data(rsa, global_boringssl_engine.Get().rsa_ex_index()));
162 }
163
164 size_t RsaMethodSize(const RSA* rsa) {
165 const KeyExData* ex_data = RsaGetExData(rsa);
166 return ex_data->cached_size;
167 }
168
169 int RsaMethodEncrypt(RSA* rsa,
170 size_t* out_len,
171 uint8_t* out,
172 size_t max_out,
173 const uint8_t* in,
174 size_t in_len,
175 int padding) {
176 NOTIMPLEMENTED();
177 OPENSSL_PUT_ERROR(RSA, RSA_R_UNKNOWN_ALGORITHM_TYPE);
178 return 0;
179 }
180
181 int RsaMethodSignRaw(RSA* rsa,
182 size_t* out_len,
183 uint8_t* out,
184 size_t max_out,
185 const uint8_t* in,
186 size_t in_len,
187 int padding) {
188 DCHECK_EQ(RSA_PKCS1_PADDING, padding);
189 if (padding != RSA_PKCS1_PADDING) {
190 // TODO(davidben): If we need to, we can implement RSA_NO_PADDING
191 // by using javax.crypto.Cipher and picking either the
192 // "RSA/ECB/NoPadding" or "RSA/ECB/PKCS1Padding" transformation as
193 // appropriate. I believe support for both of these was added in
194 // the same Android version as the "NONEwithRSA"
195 // java.security.Signature algorithm, so the same version checks
196 // for GetRsaLegacyKey should work.
197 OPENSSL_PUT_ERROR(RSA, RSA_R_UNKNOWN_PADDING_TYPE);
198 return 0;
199 }
200
201 // Retrieve private key JNI reference.
202 const KeyExData* ex_data = RsaGetExData(rsa);
203 if (!ex_data || !ex_data->private_key.obj()) {
204 LOG(WARNING) << "Null JNI reference passed to RsaMethodSignRaw!";
205 OPENSSL_PUT_ERROR(RSA, ERR_R_INTERNAL_ERROR);
206 return 0;
207 }
208
209 // Pre-4.2 legacy codepath.
210 if (ex_data->legacy_rsa) {
211 int ret = ex_data->legacy_rsa->meth->rsa_priv_enc(
212 in_len, in, out, ex_data->legacy_rsa, ANDROID_RSA_PKCS1_PADDING);
213 if (ret < 0) {
214 LOG(WARNING) << "Could not sign message in RsaMethodSignRaw!";
215 // System OpenSSL will use a separate error queue, so it is still
216 // necessary to push a new error.
217 //
218 // TODO(davidben): It would be good to also clear the system error queue
219 // if there were some way to convince Java to do it. (Without going
220 // through Java, it's difficult to get a handle on a system OpenSSL
221 // function; dlopen loads a second copy.)
222 OPENSSL_PUT_ERROR(RSA, ERR_R_INTERNAL_ERROR);
223 return 0;
224 }
225 *out_len = ret;
226 return 1;
227 }
228
229 base::StringPiece from_piece(reinterpret_cast<const char*>(in), in_len);
230 std::vector<uint8_t> result;
231 // For RSA keys, this function behaves as RSA_private_encrypt with
232 // PKCS#1 padding.
233 if (!RawSignDigestWithPrivateKey(ex_data->private_key, from_piece, &result)) {
234 LOG(WARNING) << "Could not sign message in RsaMethodSignRaw!";
235 OPENSSL_PUT_ERROR(RSA, ERR_R_INTERNAL_ERROR);
236 return 0;
237 }
238
239 size_t expected_size = static_cast<size_t>(RSA_size(rsa));
240 if (result.size() > expected_size) {
241 LOG(ERROR) << "RSA Signature size mismatch, actual: " << result.size()
242 << ", expected <= " << expected_size;
243 OPENSSL_PUT_ERROR(RSA, ERR_R_INTERNAL_ERROR);
244 return 0;
245 }
246
247 if (max_out < expected_size) {
248 OPENSSL_PUT_ERROR(RSA, RSA_R_DATA_TOO_LARGE);
249 return 0;
250 }
251
252 // Copy result to OpenSSL-provided buffer. RawSignDigestWithPrivateKey
253 // should pad with leading 0s, but if it doesn't, pad the result.
254 size_t zero_pad = expected_size - result.size();
255 memset(out, 0, zero_pad);
256 memcpy(out + zero_pad, &result[0], result.size());
257 *out_len = expected_size;
258
259 return 1;
260 }
261
262 int RsaMethodDecrypt(RSA* rsa,
263 size_t* out_len,
264 uint8_t* out,
265 size_t max_out,
266 const uint8_t* in,
267 size_t in_len,
268 int padding) {
269 NOTIMPLEMENTED();
270 OPENSSL_PUT_ERROR(RSA, RSA_R_UNKNOWN_ALGORITHM_TYPE);
271 return 0;
272 }
273
274 int RsaMethodVerifyRaw(RSA* rsa,
275 size_t* out_len,
276 uint8_t* out,
277 size_t max_out,
278 const uint8_t* in,
279 size_t in_len,
280 int padding) {
281 NOTIMPLEMENTED();
282 OPENSSL_PUT_ERROR(RSA, RSA_R_UNKNOWN_ALGORITHM_TYPE);
283 return 0;
284 }
285
286 const RSA_METHOD android_rsa_method = {
287 {
288 0 /* references */, 1 /* is_static */
289 } /* common */,
290 nullptr /* app_data */,
291
292 nullptr /* init */,
293 nullptr /* finish */,
294 RsaMethodSize,
295 nullptr /* sign */,
296 nullptr /* verify */,
297 RsaMethodEncrypt,
298 RsaMethodSignRaw,
299 RsaMethodDecrypt,
300 RsaMethodVerifyRaw,
301 nullptr /* private_transform */,
302 nullptr /* mod_exp */,
303 nullptr /* bn_mod_exp */,
304 RSA_FLAG_OPAQUE,
305 nullptr /* keygen */,
306 nullptr /* multi_prime_keygen */,
307 nullptr /* supports_digest */,
308 };
309
310 // Setup an EVP_PKEY to wrap an existing platform RSA PrivateKey object.
311 // |private_key| is the JNI reference (local or global) to the object.
312 // |legacy_rsa|, if non-NULL, is a pointer to the system OpenSSL RSA object
313 // backing |private_key|. This parameter is only used for Android < 4.2 to
314 // implement key operations not exposed by the platform.
315 // Returns a new EVP_PKEY on success, NULL otherwise.
316 // On success, this creates a new global JNI reference to the object
317 // that is owned by and destroyed with the EVP_PKEY. I.e. caller can
318 // free |private_key| after the call.
319 crypto::ScopedEVP_PKEY CreateRsaPkeyWrapper(
320 const JavaRef<jobject>& private_key,
321 AndroidRSA* legacy_rsa,
322 const crypto::OpenSSLErrStackTracer& tracer) {
323 crypto::ScopedRSA rsa(RSA_new_method(global_boringssl_engine.Get().engine()));
324
325 std::vector<uint8_t> modulus;
326 if (!GetRSAKeyModulus(private_key, &modulus)) {
327 LOG(ERROR) << "Failed to get private key modulus";
328 return nullptr;
329 }
330
331 std::unique_ptr<KeyExData> ex_data(new KeyExData);
332 ex_data->private_key.Reset(private_key);
333 if (ex_data->private_key.is_null()) {
334 LOG(ERROR) << "Could not create global JNI reference";
335 return nullptr;
336 }
337 ex_data->legacy_rsa = legacy_rsa;
338 ex_data->cached_size = VectorBignumSize(modulus);
339
340 RSA_set_ex_data(rsa.get(), global_boringssl_engine.Get().rsa_ex_index(),
341 ex_data.release());
342
343 crypto::ScopedEVP_PKEY pkey(EVP_PKEY_new());
344 if (!pkey || !EVP_PKEY_set1_RSA(pkey.get(), rsa.get()))
345 return nullptr;
346 return pkey;
347 }
348
349 // On Android < 4.2, the libkeystore.so ENGINE uses CRYPTO_EX_DATA and is not
350 // added to the global engine list. If all references to it are dropped, OpenSSL
351 // will dlclose the module, leaving a dangling function pointer in the RSA
352 // CRYPTO_EX_DATA class. To work around this, leak an extra reference to the
353 // ENGINE we extract in GetRsaLegacyKey.
354 //
355 // In 4.2, this change avoids the problem:
356 // https://android.googlesource.com/platform/libcore/+/106a8928fb4249f2f3d4dba1d ddbe73ca5cb3d61
357 //
358 // https://crbug.com/381465
359 class KeystoreEngineWorkaround {
360 public:
361 KeystoreEngineWorkaround() {}
362
363 void LeakEngine(const JavaRef<jobject>& private_key) {
364 if (!engine_.is_null())
365 return;
366 ScopedJavaLocalRef<jobject> engine =
367 GetOpenSSLEngineForPrivateKey(private_key);
368 if (engine.is_null()) {
369 NOTREACHED();
370 return;
371 }
372 engine_.Reset(engine);
373 }
374
375 private:
376 ScopedJavaGlobalRef<jobject> engine_;
377 };
378
379 void LeakEngine(const JavaRef<jobject>& private_key) {
380 static base::LazyInstance<KeystoreEngineWorkaround>::Leaky s_instance =
381 LAZY_INSTANCE_INITIALIZER;
382 s_instance.Get().LeakEngine(private_key);
383 }
384
385 // Creates an EVP_PKEY wrapper corresponding to the RSA key
386 // |private_key|. Returns nullptr on failure.
387 crypto::ScopedEVP_PKEY GetRsaPkeyWrapper(const JavaRef<jobject>& private_key) {
388 const int kAndroid42ApiLevel = 17;
389 crypto::OpenSSLErrStackTracer tracer(FROM_HERE);
390
391 if (base::android::BuildInfo::GetInstance()->sdk_int() >=
392 kAndroid42ApiLevel) {
393 return CreateRsaPkeyWrapper(private_key, nullptr, tracer);
394 }
395
396 // Route around platform limitation: if Android < 4.2, then
397 // base::android::RawSignDigestWithPrivateKey() cannot work, so try to get the
398 // system OpenSSL's EVP_PKEY backing this PrivateKey object.
399 AndroidEVP_PKEY* sys_pkey = GetOpenSSLSystemHandleForPrivateKey(private_key);
400 if (sys_pkey == nullptr)
401 return nullptr;
402
403 if (sys_pkey->type != ANDROID_EVP_PKEY_RSA) {
404 LOG(ERROR) << "Private key has wrong type!";
405 return nullptr;
406 }
407
408 AndroidRSA* sys_rsa = sys_pkey->pkey.rsa;
409 if (sys_rsa->engine) {
410 // |private_key| may not have an engine if the PrivateKey did not come
411 // from the key store, such as in unit tests.
412 if (strcmp(sys_rsa->engine->id, "keystore") == 0) {
413 LeakEngine(private_key);
414 } else {
415 NOTREACHED();
416 }
417 }
418
419 return CreateRsaPkeyWrapper(private_key, sys_rsa, tracer);
420 }
421
422 // Custom ECDSA_METHOD that uses the platform APIs.
423 // Note that for now, only signing through ECDSA_sign() is really supported.
424 // all other method pointers are either stubs returning errors, or no-ops.
425
426 const JavaRef<jobject>& EcKeyGetKey(const EC_KEY* ec_key) {
427 KeyExData* ex_data = reinterpret_cast<KeyExData*>(EC_KEY_get_ex_data(
428 ec_key, global_boringssl_engine.Get().ec_key_ex_index()));
429 return ex_data->private_key;
430 }
431
432 size_t EcdsaMethodGroupOrderSize(const EC_KEY* ec_key) {
433 KeyExData* ex_data = reinterpret_cast<KeyExData*>(EC_KEY_get_ex_data(
434 ec_key, global_boringssl_engine.Get().ec_key_ex_index()));
435 return ex_data->cached_size;
436 }
437
438 int EcdsaMethodSign(const uint8_t* digest,
439 size_t digest_len,
440 uint8_t* sig,
441 unsigned int* sig_len,
442 EC_KEY* ec_key) {
443 // Retrieve private key JNI reference.
444 const JavaRef<jobject>& private_key = EcKeyGetKey(ec_key);
445 if (private_key.is_null()) {
446 LOG(WARNING) << "Null JNI reference passed to EcdsaMethodSign!";
447 return 0;
448 }
449 // Sign message with it through JNI.
450 std::vector<uint8_t> signature;
451 base::StringPiece digest_sp(reinterpret_cast<const char*>(digest),
452 digest_len);
453 if (!RawSignDigestWithPrivateKey(private_key, digest_sp, &signature)) {
454 LOG(WARNING) << "Could not sign message in EcdsaMethodSign!";
455 return 0;
456 }
457
458 // Note: With ECDSA, the actual signature may be smaller than
459 // ECDSA_size().
460 size_t max_expected_size = ECDSA_size(ec_key);
461 if (signature.size() > max_expected_size) {
462 LOG(ERROR) << "ECDSA Signature size mismatch, actual: " << signature.size()
463 << ", expected <= " << max_expected_size;
464 return 0;
465 }
466
467 memcpy(sig, &signature[0], signature.size());
468 *sig_len = signature.size();
469 return 1;
470 }
471
472 int EcdsaMethodVerify(const uint8_t* digest,
473 size_t digest_len,
474 const uint8_t* sig,
475 size_t sig_len,
476 EC_KEY* ec_key) {
477 NOTIMPLEMENTED();
478 OPENSSL_PUT_ERROR(ECDSA, ECDSA_R_NOT_IMPLEMENTED);
479 return 0;
480 }
481
482 // Setup an EVP_PKEY to wrap an existing platform PrivateKey object.
483 // |private_key| is the JNI reference (local or global) to the object.
484 // Returns a new EVP_PKEY on success, NULL otherwise.
485 // On success, this creates a global JNI reference to the object that
486 // is owned by and destroyed with the EVP_PKEY. I.e. the caller shall
487 // always free |private_key| after the call.
488 crypto::ScopedEVP_PKEY GetEcdsaPkeyWrapper(
489 const JavaRef<jobject>& private_key) {
490 crypto::OpenSSLErrStackTracer tracer(FROM_HERE);
491 crypto::ScopedEC_KEY ec_key(
492 EC_KEY_new_method(global_boringssl_engine.Get().engine()));
493
494 std::vector<uint8_t> order;
495 if (!GetECKeyOrder(private_key, &order)) {
496 LOG(ERROR) << "Can't extract order parameter from EC private key";
497 return nullptr;
498 }
499
500 std::unique_ptr<KeyExData> ex_data(new KeyExData);
501 ex_data->private_key.Reset(private_key);
502 if (ex_data->private_key.is_null()) {
503 LOG(ERROR) << "Can't create global JNI reference";
504 return nullptr;
505 }
506 ex_data->legacy_rsa = nullptr;
507 ex_data->cached_size = VectorBignumSize(order);
508
509 EC_KEY_set_ex_data(ec_key.get(),
510 global_boringssl_engine.Get().ec_key_ex_index(),
511 ex_data.release());
512
513 crypto::ScopedEVP_PKEY pkey(EVP_PKEY_new());
514 if (!pkey || !EVP_PKEY_set1_EC_KEY(pkey.get(), ec_key.get()))
515 return nullptr;
516 return pkey;
517 }
518
519 const ECDSA_METHOD android_ecdsa_method = {
520 {
521 0 /* references */, 1 /* is_static */
522 } /* common */,
523 NULL /* app_data */,
524
525 NULL /* init */,
526 NULL /* finish */,
527 EcdsaMethodGroupOrderSize,
528 EcdsaMethodSign,
529 EcdsaMethodVerify,
530 ECDSA_FLAG_OPAQUE,
531 };
532
533 } // namespace
534
535 crypto::ScopedEVP_PKEY GetOpenSSLPrivateKeyWrapper(
536 const JavaRef<jobject>& private_key) {
537 // Create sub key type, depending on private key's algorithm type.
538 PrivateKeyType key_type = GetPrivateKeyType(private_key);
539 switch (key_type) {
540 case PRIVATE_KEY_TYPE_RSA:
541 return GetRsaPkeyWrapper(private_key);
542 case PRIVATE_KEY_TYPE_ECDSA:
543 return GetEcdsaPkeyWrapper(private_key);
544 default:
545 LOG(WARNING)
546 << "GetOpenSSLPrivateKeyWrapper() called with invalid key type";
547 return nullptr;
548 }
549 }
550
551 } // namespace android
552 } // namespace net
OLDNEW
« no previous file with comments | « net/android/keystore_openssl.h ('k') | net/android/keystore_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698