mirror of
https://github.com/openssl/openssl.git
synced 2024-12-03 05:41:46 +08:00
64954e2f34
This fixes a race condition where the index to the cache location was found under a read lock and a later write lock set the cache entry. The issue being that two threads could get the same location index and then fight each other over writing the cache entry. The most likely outcome is a memory leak, however it would be possible to set up an invalid cache entry. The operation cache was a fixed sized array, once full an assertion failed. The other fix here is to convert this to a stack. The code is simplified and it avoids a cache overflow condition. Reviewed-by: Richard Levitte <levitte@openssl.org> (Merged from https://github.com/openssl/openssl/pull/14062)
560 lines
18 KiB
C
560 lines
18 KiB
C
/*
|
|
* Copyright 2019-2020 The OpenSSL Project Authors. All Rights Reserved.
|
|
*
|
|
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
* this file except in compliance with the License. You can obtain a copy
|
|
* in the file LICENSE in the source distribution or at
|
|
* https://www.openssl.org/source/license.html
|
|
*/
|
|
|
|
#include <openssl/core_names.h>
|
|
#include "internal/cryptlib.h"
|
|
#include "internal/nelem.h"
|
|
#include "crypto/evp.h"
|
|
#include "crypto/asn1.h"
|
|
#include "internal/core.h"
|
|
#include "internal/provider.h"
|
|
#include "evp_local.h"
|
|
|
|
/*
|
|
* match_type() checks if two EVP_KEYMGMT are matching key types. This
|
|
* function assumes that the caller has made all the necessary NULL checks.
|
|
*/
|
|
static int match_type(const EVP_KEYMGMT *keymgmt1, const EVP_KEYMGMT *keymgmt2)
|
|
{
|
|
const OSSL_PROVIDER *prov2 = EVP_KEYMGMT_provider(keymgmt2);
|
|
const char *name2 = evp_first_name(prov2, EVP_KEYMGMT_number(keymgmt2));
|
|
|
|
return EVP_KEYMGMT_is_a(keymgmt1, name2);
|
|
}
|
|
|
|
int evp_keymgmt_util_try_import(const OSSL_PARAM params[], void *arg)
|
|
{
|
|
struct evp_keymgmt_util_try_import_data_st *data = arg;
|
|
|
|
/* Just in time creation of keydata */
|
|
if (data->keydata == NULL
|
|
&& (data->keydata = evp_keymgmt_newdata(data->keymgmt)) == NULL) {
|
|
ERR_raise(ERR_LIB_EVP, ERR_R_MALLOC_FAILURE);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* It's fine if there was no data to transfer, we just end up with an
|
|
* empty destination key.
|
|
*/
|
|
if (params[0].key == NULL)
|
|
return 1;
|
|
|
|
return evp_keymgmt_import(data->keymgmt, data->keydata, data->selection,
|
|
params);
|
|
}
|
|
|
|
int evp_keymgmt_util_assign_pkey(EVP_PKEY *pkey, EVP_KEYMGMT *keymgmt,
|
|
void *keydata)
|
|
{
|
|
if (pkey == NULL || keymgmt == NULL || keydata == NULL
|
|
|| !EVP_PKEY_set_type_by_keymgmt(pkey, keymgmt)) {
|
|
ERR_raise(ERR_LIB_EVP, ERR_R_INTERNAL_ERROR);
|
|
return 0;
|
|
}
|
|
pkey->keydata = keydata;
|
|
evp_keymgmt_util_cache_keyinfo(pkey);
|
|
return 1;
|
|
}
|
|
|
|
EVP_PKEY *evp_keymgmt_util_make_pkey(EVP_KEYMGMT *keymgmt, void *keydata)
|
|
{
|
|
EVP_PKEY *pkey = NULL;
|
|
|
|
if (keymgmt == NULL
|
|
|| keydata == NULL
|
|
|| (pkey = EVP_PKEY_new()) == NULL
|
|
|| !evp_keymgmt_util_assign_pkey(pkey, keymgmt, keydata)) {
|
|
EVP_PKEY_free(pkey);
|
|
return NULL;
|
|
}
|
|
return pkey;
|
|
}
|
|
|
|
int evp_keymgmt_util_export(const EVP_PKEY *pk, int selection,
|
|
OSSL_CALLBACK *export_cb, void *export_cbarg)
|
|
{
|
|
return evp_keymgmt_export(pk->keymgmt, pk->keydata, selection,
|
|
export_cb, export_cbarg);
|
|
}
|
|
|
|
void *evp_keymgmt_util_export_to_provider(EVP_PKEY *pk, EVP_KEYMGMT *keymgmt)
|
|
{
|
|
struct evp_keymgmt_util_try_import_data_st import_data;
|
|
OP_CACHE_ELEM *op;
|
|
|
|
/* Export to where? */
|
|
if (keymgmt == NULL)
|
|
return NULL;
|
|
|
|
/* If we have an unassigned key, give up */
|
|
if (pk->keydata == NULL)
|
|
return NULL;
|
|
|
|
/* If |keymgmt| matches the "origin" |keymgmt|, no more to do */
|
|
if (pk->keymgmt == keymgmt)
|
|
return pk->keydata;
|
|
|
|
CRYPTO_THREAD_read_lock(pk->lock);
|
|
/*
|
|
* If the provider native "origin" hasn't changed since last time, we
|
|
* try to find our keymgmt in the operation cache. If it has changed
|
|
* and our keymgmt isn't found, we will clear the cache further down.
|
|
*/
|
|
if (pk->dirty_cnt == pk->dirty_cnt_copy) {
|
|
/* If this key is already exported to |keymgmt|, no more to do */
|
|
op = evp_keymgmt_util_find_operation_cache(pk, keymgmt);
|
|
if (op != NULL && op->keymgmt != NULL) {
|
|
void *ret = op->keydata;
|
|
|
|
CRYPTO_THREAD_unlock(pk->lock);
|
|
return ret;
|
|
}
|
|
}
|
|
CRYPTO_THREAD_unlock(pk->lock);
|
|
|
|
/* If the "origin" |keymgmt| doesn't support exporting, give up */
|
|
/*
|
|
* TODO(3.0) consider an evp_keymgmt_export() return value that indicates
|
|
* that the method is unsupported.
|
|
*/
|
|
if (pk->keymgmt->export == NULL)
|
|
return NULL;
|
|
|
|
/*
|
|
* Make sure that the type of the keymgmt to export to matches the type
|
|
* of the "origin"
|
|
*/
|
|
if (!ossl_assert(match_type(pk->keymgmt, keymgmt)))
|
|
return NULL;
|
|
|
|
/*
|
|
* We look at the already cached provider keys, and import from the
|
|
* first that supports it (i.e. use its export function), and export
|
|
* the imported data to the new provider.
|
|
*/
|
|
|
|
/* Setup for the export callback */
|
|
import_data.keydata = NULL; /* evp_keymgmt_util_try_import will create it */
|
|
import_data.keymgmt = keymgmt;
|
|
import_data.selection = OSSL_KEYMGMT_SELECT_ALL;
|
|
|
|
/*
|
|
* The export function calls the callback (evp_keymgmt_util_try_import),
|
|
* which does the import for us. If successful, we're done.
|
|
*/
|
|
if (!evp_keymgmt_util_export(pk, OSSL_KEYMGMT_SELECT_ALL,
|
|
&evp_keymgmt_util_try_import, &import_data)) {
|
|
/* If there was an error, bail out */
|
|
evp_keymgmt_freedata(keymgmt, import_data.keydata);
|
|
return NULL;
|
|
}
|
|
|
|
CRYPTO_THREAD_write_lock(pk->lock);
|
|
/* Check to make sure some other thread didn't get there first */
|
|
op = evp_keymgmt_util_find_operation_cache(pk, keymgmt);
|
|
if (op != NULL && op->keydata != NULL) {
|
|
void *ret = op->keydata;
|
|
|
|
CRYPTO_THREAD_unlock(pk->lock);
|
|
|
|
/*
|
|
* Another thread seemms to have already exported this so we abandon
|
|
* all the work we just did.
|
|
*/
|
|
evp_keymgmt_freedata(keymgmt, import_data.keydata);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* If the dirty counter changed since last time, then clear the
|
|
* operation cache. In that case, we know that |i| is zero.
|
|
*/
|
|
if (pk->dirty_cnt != pk->dirty_cnt_copy)
|
|
evp_keymgmt_util_clear_operation_cache(pk, 0);
|
|
|
|
/* Add the new export to the operation cache */
|
|
if (!evp_keymgmt_util_cache_keydata(pk, keymgmt, import_data.keydata)) {
|
|
evp_keymgmt_freedata(keymgmt, import_data.keydata);
|
|
return NULL;
|
|
}
|
|
|
|
/* Synchronize the dirty count */
|
|
pk->dirty_cnt_copy = pk->dirty_cnt;
|
|
|
|
CRYPTO_THREAD_unlock(pk->lock);
|
|
|
|
return import_data.keydata;
|
|
}
|
|
|
|
static void op_cache_free(OP_CACHE_ELEM *e)
|
|
{
|
|
evp_keymgmt_freedata(e->keymgmt, e->keydata);
|
|
EVP_KEYMGMT_free(e->keymgmt);
|
|
OPENSSL_free(e);
|
|
}
|
|
|
|
int evp_keymgmt_util_clear_operation_cache(EVP_PKEY *pk, int locking)
|
|
{
|
|
if (pk != NULL) {
|
|
if (locking && pk->lock != NULL && !CRYPTO_THREAD_write_lock(pk->lock))
|
|
return 0;
|
|
sk_OP_CACHE_ELEM_pop_free(pk->operation_cache, op_cache_free);
|
|
pk->operation_cache = NULL;
|
|
if (locking && pk->lock != NULL)
|
|
CRYPTO_THREAD_unlock(pk->lock);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
OP_CACHE_ELEM *evp_keymgmt_util_find_operation_cache(EVP_PKEY *pk,
|
|
EVP_KEYMGMT *keymgmt)
|
|
{
|
|
int i, end = sk_OP_CACHE_ELEM_num(pk->operation_cache);
|
|
OP_CACHE_ELEM *p;
|
|
|
|
/*
|
|
* A comparison and sk_P_CACHE_ELEM_find() are avoided to not cause
|
|
* problems when we've only a read lock.
|
|
*/
|
|
for (i = 0; i < end; i++) {
|
|
p = sk_OP_CACHE_ELEM_value(pk->operation_cache, i);
|
|
if (keymgmt == p->keymgmt)
|
|
return p;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int evp_keymgmt_util_cache_keydata(EVP_PKEY *pk,
|
|
EVP_KEYMGMT *keymgmt, void *keydata)
|
|
{
|
|
OP_CACHE_ELEM *p = NULL;
|
|
|
|
if (keydata != NULL) {
|
|
if (pk->operation_cache == NULL) {
|
|
pk->operation_cache = sk_OP_CACHE_ELEM_new_null();
|
|
if (pk->operation_cache == NULL)
|
|
return 0;
|
|
}
|
|
|
|
p = OPENSSL_malloc(sizeof(*p));
|
|
if (p == NULL)
|
|
return 0;
|
|
p->keydata = keydata;
|
|
p->keymgmt = keymgmt;
|
|
|
|
if (!EVP_KEYMGMT_up_ref(keymgmt)) {
|
|
OPENSSL_free(p);
|
|
return 0;
|
|
}
|
|
|
|
if (!sk_OP_CACHE_ELEM_push(pk->operation_cache, p)) {
|
|
EVP_KEYMGMT_free(keymgmt);
|
|
OPENSSL_free(p);
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void evp_keymgmt_util_cache_keyinfo(EVP_PKEY *pk)
|
|
{
|
|
/*
|
|
* Cache information about the provider "origin" key.
|
|
*
|
|
* This services functions like EVP_PKEY_size, EVP_PKEY_bits, etc
|
|
*/
|
|
if (pk->keydata != NULL) {
|
|
int bits = 0;
|
|
int security_bits = 0;
|
|
int size = 0;
|
|
OSSL_PARAM params[4];
|
|
|
|
params[0] = OSSL_PARAM_construct_int(OSSL_PKEY_PARAM_BITS, &bits);
|
|
params[1] = OSSL_PARAM_construct_int(OSSL_PKEY_PARAM_SECURITY_BITS,
|
|
&security_bits);
|
|
params[2] = OSSL_PARAM_construct_int(OSSL_PKEY_PARAM_MAX_SIZE, &size);
|
|
params[3] = OSSL_PARAM_construct_end();
|
|
if (evp_keymgmt_get_params(pk->keymgmt, pk->keydata, params)) {
|
|
pk->cache.size = size;
|
|
pk->cache.bits = bits;
|
|
pk->cache.security_bits = security_bits;
|
|
}
|
|
}
|
|
}
|
|
|
|
void *evp_keymgmt_util_fromdata(EVP_PKEY *target, EVP_KEYMGMT *keymgmt,
|
|
int selection, const OSSL_PARAM params[])
|
|
{
|
|
void *keydata = NULL;
|
|
|
|
if ((keydata = evp_keymgmt_newdata(keymgmt)) == NULL
|
|
|| !evp_keymgmt_import(keymgmt, keydata, selection, params)
|
|
|| !evp_keymgmt_util_assign_pkey(target, keymgmt, keydata)) {
|
|
evp_keymgmt_freedata(keymgmt, keydata);
|
|
keydata = NULL;
|
|
}
|
|
return keydata;
|
|
}
|
|
|
|
int evp_keymgmt_util_has(EVP_PKEY *pk, int selection)
|
|
{
|
|
/* Check if key is even assigned */
|
|
if (pk->keymgmt == NULL)
|
|
return 0;
|
|
|
|
return evp_keymgmt_has(pk->keymgmt, pk->keydata, selection);
|
|
}
|
|
|
|
/*
|
|
* evp_keymgmt_util_match() doesn't just look at the provider side "origin",
|
|
* but also in the operation cache to see if there's any common keymgmt that
|
|
* supplies OP_keymgmt_match.
|
|
*
|
|
* evp_keymgmt_util_match() adheres to the return values that EVP_PKEY_eq()
|
|
* and EVP_PKEY_parameters_eq() return, i.e.:
|
|
*
|
|
* 1 same key
|
|
* 0 not same key
|
|
* -1 not same key type
|
|
* -2 unsupported operation
|
|
*/
|
|
int evp_keymgmt_util_match(EVP_PKEY *pk1, EVP_PKEY *pk2, int selection)
|
|
{
|
|
EVP_KEYMGMT *keymgmt1 = NULL, *keymgmt2 = NULL;
|
|
void *keydata1 = NULL, *keydata2 = NULL;
|
|
|
|
if (pk1 == NULL || pk2 == NULL) {
|
|
if (pk1 == NULL && pk2 == NULL)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
keymgmt1 = pk1->keymgmt;
|
|
keydata1 = pk1->keydata;
|
|
keymgmt2 = pk2->keymgmt;
|
|
keydata2 = pk2->keydata;
|
|
|
|
if (keymgmt1 != keymgmt2) {
|
|
/*
|
|
* The condition for a successful cross export is that the
|
|
* keydata to be exported is NULL (typed, but otherwise empty
|
|
* EVP_PKEY), or that it was possible to export it with
|
|
* evp_keymgmt_util_export_to_provider().
|
|
*
|
|
* We use |ok| to determine if it's ok to cross export one way,
|
|
* but also to determine if we should attempt a cross export
|
|
* the other way. There's no point doing it both ways.
|
|
*/
|
|
int ok = 1;
|
|
|
|
/* Complex case, where the keymgmt differ */
|
|
if (keymgmt1 != NULL
|
|
&& keymgmt2 != NULL
|
|
&& !match_type(keymgmt1, keymgmt2)) {
|
|
ERR_raise(ERR_LIB_EVP, EVP_R_DIFFERENT_KEY_TYPES);
|
|
return -1; /* Not the same type */
|
|
}
|
|
|
|
/*
|
|
* The key types are determined to match, so we try cross export,
|
|
* but only to keymgmt's that supply a matching function.
|
|
*/
|
|
if (keymgmt2 != NULL
|
|
&& keymgmt2->match != NULL) {
|
|
void *tmp_keydata = NULL;
|
|
|
|
ok = 1;
|
|
if (keydata1 != NULL) {
|
|
tmp_keydata =
|
|
evp_keymgmt_util_export_to_provider(pk1, keymgmt2);
|
|
ok = (tmp_keydata != NULL);
|
|
}
|
|
if (ok) {
|
|
keymgmt1 = keymgmt2;
|
|
keydata1 = tmp_keydata;
|
|
}
|
|
}
|
|
/*
|
|
* If we've successfully cross exported one way, there's no point
|
|
* doing it the other way, hence the |!ok| check.
|
|
*/
|
|
if (!ok
|
|
&& keymgmt1 != NULL
|
|
&& keymgmt1->match != NULL) {
|
|
void *tmp_keydata = NULL;
|
|
|
|
ok = 1;
|
|
if (keydata2 != NULL) {
|
|
tmp_keydata =
|
|
evp_keymgmt_util_export_to_provider(pk2, keymgmt1);
|
|
ok = (tmp_keydata != NULL);
|
|
}
|
|
if (ok) {
|
|
keymgmt2 = keymgmt1;
|
|
keydata2 = tmp_keydata;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If we still don't have matching keymgmt implementations, we give up */
|
|
if (keymgmt1 != keymgmt2)
|
|
return -2;
|
|
|
|
/* If both keydata are NULL, then they're the same key */
|
|
if (keydata1 == NULL && keydata2 == NULL)
|
|
return 1;
|
|
/* If only one of the keydata is NULL, then they're different keys */
|
|
if (keydata1 == NULL || keydata2 == NULL)
|
|
return 0;
|
|
/* If both keydata are non-NULL, we let the backend decide */
|
|
return evp_keymgmt_match(keymgmt1, keydata1, keydata2, selection);
|
|
}
|
|
|
|
int evp_keymgmt_util_copy(EVP_PKEY *to, EVP_PKEY *from, int selection)
|
|
{
|
|
/* Save copies of pointers we want to play with without affecting |to| */
|
|
EVP_KEYMGMT *to_keymgmt = to->keymgmt;
|
|
void *to_keydata = to->keydata, *alloc_keydata = NULL;
|
|
|
|
/* An unassigned key can't be copied */
|
|
if (from == NULL || from->keydata == NULL)
|
|
return 0;
|
|
|
|
/*
|
|
* If |to| is unassigned, ensure it gets the same KEYMGMT as |from|,
|
|
* Note that the final setting of KEYMGMT is done further down, with
|
|
* EVP_PKEY_set_type_by_keymgmt(); we don't want to do that prematurely.
|
|
*/
|
|
if (to_keymgmt == NULL)
|
|
to_keymgmt = from->keymgmt;
|
|
|
|
if (to_keymgmt == from->keymgmt && to_keymgmt->copy != NULL) {
|
|
/* Make sure there's somewhere to copy to */
|
|
if (to_keydata == NULL
|
|
&& ((to_keydata = alloc_keydata = evp_keymgmt_newdata(to_keymgmt))
|
|
== NULL)) {
|
|
ERR_raise(ERR_LIB_EVP, ERR_R_MALLOC_FAILURE);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* |to| and |from| have the same keymgmt, and the copy function is
|
|
* implemented, so just copy and be done
|
|
*/
|
|
if (!evp_keymgmt_copy(to_keymgmt, to_keydata, from->keydata,
|
|
selection))
|
|
return 0;
|
|
} else if (match_type(to_keymgmt, from->keymgmt)) {
|
|
struct evp_keymgmt_util_try_import_data_st import_data;
|
|
|
|
import_data.keymgmt = to_keymgmt;
|
|
import_data.keydata = to_keydata;
|
|
import_data.selection = selection;
|
|
|
|
if (!evp_keymgmt_util_export(from, selection,
|
|
&evp_keymgmt_util_try_import,
|
|
&import_data)) {
|
|
evp_keymgmt_freedata(to_keymgmt, alloc_keydata);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* In case to_keydata was previously unallocated,
|
|
* evp_keymgmt_util_try_import() may have created it for us.
|
|
*/
|
|
if (to_keydata == NULL)
|
|
to_keydata = alloc_keydata = import_data.keydata;
|
|
} else {
|
|
ERR_raise(ERR_LIB_EVP, EVP_R_DIFFERENT_KEY_TYPES);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* We only need to set the |to| type when its |keymgmt| isn't set.
|
|
* We can then just set its |keydata| to what we have, which might
|
|
* be exactly what it had when entering this function.
|
|
* This is a bit different from using evp_keymgmt_util_assign_pkey(),
|
|
* which isn't as careful with |to|'s original |keymgmt|, since it's
|
|
* meant to forcibly reassign an EVP_PKEY no matter what, which is
|
|
* why we don't use that one here.
|
|
*/
|
|
if (to->keymgmt == NULL
|
|
&& !EVP_PKEY_set_type_by_keymgmt(to, to_keymgmt)) {
|
|
evp_keymgmt_freedata(to_keymgmt, alloc_keydata);
|
|
return 0;
|
|
}
|
|
to->keydata = to_keydata;
|
|
evp_keymgmt_util_cache_keyinfo(to);
|
|
|
|
return 1;
|
|
}
|
|
|
|
void *evp_keymgmt_util_gen(EVP_PKEY *target, EVP_KEYMGMT *keymgmt,
|
|
void *genctx, OSSL_CALLBACK *cb, void *cbarg)
|
|
{
|
|
void *keydata = NULL;
|
|
|
|
if ((keydata = evp_keymgmt_gen(keymgmt, genctx, cb, cbarg)) == NULL
|
|
|| !evp_keymgmt_util_assign_pkey(target, keymgmt, keydata)) {
|
|
evp_keymgmt_freedata(keymgmt, keydata);
|
|
keydata = NULL;
|
|
}
|
|
|
|
return keydata;
|
|
}
|
|
|
|
/*
|
|
* Returns the same numbers as EVP_PKEY_get_default_digest_name()
|
|
* When the string from the EVP_KEYMGMT implementation is "", we use
|
|
* SN_undef, since that corresponds to what EVP_PKEY_get_default_nid()
|
|
* returns for no digest.
|
|
*/
|
|
int evp_keymgmt_util_get_deflt_digest_name(EVP_KEYMGMT *keymgmt,
|
|
void *keydata,
|
|
char *mdname, size_t mdname_sz)
|
|
{
|
|
OSSL_PARAM params[3];
|
|
char mddefault[100] = "";
|
|
char mdmandatory[100] = "";
|
|
char *result = NULL;
|
|
int rv = -2;
|
|
|
|
params[0] =
|
|
OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_DEFAULT_DIGEST,
|
|
mddefault, sizeof(mddefault));
|
|
params[1] =
|
|
OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_MANDATORY_DIGEST,
|
|
mdmandatory,
|
|
sizeof(mdmandatory));
|
|
params[2] = OSSL_PARAM_construct_end();
|
|
|
|
if (!evp_keymgmt_get_params(keymgmt, keydata, params))
|
|
return 0;
|
|
|
|
if (OSSL_PARAM_modified(params + 1)) {
|
|
if (params[1].return_size <= 1) /* Only a NUL byte */
|
|
result = SN_undef;
|
|
else
|
|
result = mdmandatory;
|
|
rv = 2;
|
|
} else if (OSSL_PARAM_modified(params)) {
|
|
if (params[0].return_size <= 1) /* Only a NUL byte */
|
|
result = SN_undef;
|
|
else
|
|
result = mddefault;
|
|
rv = 1;
|
|
}
|
|
if (rv > 0)
|
|
OPENSSL_strlcpy(mdname, result, mdname_sz);
|
|
return rv;
|
|
}
|