From: Richard Levitte Date: Wed, 8 Jul 2020 21:19:13 +0000 (+0200) Subject: SERIALIZER: Add functions to deserialize into an EVP_PKEY X-Git-Tag: openssl-3.0.0-alpha6~65 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=072a9fde7d67a621ebd2c8d1ba22ab6e17da5a88;p=thirdparty%2Fopenssl.git SERIALIZER: Add functions to deserialize into an EVP_PKEY EVP_PKEY is the fundamental type for provider side code, so we implement specific support for it, in form of a special context constructor. This constructor looks up and collects all available KEYMGMT implementations, and then uses those names to collect deserializer implementations, as described in the previous commit. Reviewed-by: Matt Caswell Reviewed-by: Shane Lontis (Merged from https://github.com/openssl/openssl/pull/12410) --- diff --git a/crypto/serializer/build.info b/crypto/serializer/build.info index 066eec5cdd6..c0222785e94 100644 --- a/crypto/serializer/build.info +++ b/crypto/serializer/build.info @@ -1,5 +1,6 @@ SOURCE[../../libcrypto]=serializer_meth.c serializer_lib.c serializer_pkey.c -SOURCE[../../libcrypto]=deserializer_meth.c deserializer_lib.c +SOURCE[../../libcrypto]=deserializer_meth.c deserializer_lib.c \ + deserializer_pkey.c SOURCE[../../libcrypto]=serializer_err.c SOURCE[../../libcrypto]=deserializer_err.c diff --git a/crypto/serializer/deserializer_pkey.c b/crypto/serializer/deserializer_pkey.c new file mode 100644 index 00000000000..1dc35b76a7a --- /dev/null +++ b/crypto/serializer/deserializer_pkey.c @@ -0,0 +1,290 @@ +/* + * Copyright 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 +#include +#include +#include +#include "crypto/evp.h" +#include "serializer_local.h" + +/* + * Support for OSSL_DESERIALIZER_CTX_new_by_EVP_PKEY: + * Handle an object reference + */ + +DEFINE_STACK_OF(EVP_KEYMGMT) + +struct deser_EVP_PKEY_data_st { + char *object_type; /* recorded object data type, may be NULL */ + void **object; /* Where the result should end up */ + STACK_OF(EVP_KEYMGMT) *keymgmts; /* The EVP_KEYMGMTs we handle */ +}; + +static int deser_finalize_EVP_PKEY(OSSL_DESERIALIZER_INSTANCE *deser_inst, + const OSSL_PARAM *params, + void *finalize_arg) +{ + struct deser_EVP_PKEY_data_st *data = finalize_arg; + OSSL_DESERIALIZER *deser = + OSSL_DESERIALIZER_INSTANCE_deserializer(deser_inst); + void *deserctx = OSSL_DESERIALIZER_INSTANCE_deserializer_ctx(deser_inst); + size_t i, end_i; + /* + * |object_ref| points to a provider reference to an object, its exact + * contents entirely opaque to us, but may be passed to any provider + * function that expects this (such as OSSL_FUNC_keymgmt_load(). + * + * This pointer is considered volatile, i.e. whatever it points at + * is assumed to be freed as soon as this function returns. + */ + void *object_ref = NULL; + size_t object_ref_sz = 0; + const OSSL_PARAM *p; + + p = OSSL_PARAM_locate_const(params, OSSL_DESERIALIZER_PARAM_DATA_TYPE); + if (p != NULL) { + char *object_type = NULL; + + if (!OSSL_PARAM_get_utf8_string(p, &object_type, 0)) + return 0; + OPENSSL_free(data->object_type); + data->object_type = object_type; + } + + /* + * For stuff that should end up in an EVP_PKEY, we only accept an object + * reference for the moment. This enforces that the key data itself + * remains with the provider. + */ + p = OSSL_PARAM_locate_const(params, OSSL_DESERIALIZER_PARAM_REFERENCE); + if (p == NULL || p->data_type != OSSL_PARAM_OCTET_STRING) + return 0; + object_ref = p->data; + object_ref_sz = p->data_size; + + /* We may have reached one of the goals, let's find out! */ + end_i = sk_EVP_KEYMGMT_num(data->keymgmts); + for (i = 0; end_i; i++) { + EVP_KEYMGMT *keymgmt = sk_EVP_KEYMGMT_value(data->keymgmts, i); + + /* + * There are two ways to find a matching KEYMGMT: + * + * 1. If the object data type (recorded in |data->object_type|) + * is defined, by checking it using EVP_KEYMGMT_is_a(). + * 2. If the object data type is NOT defined, by comparing the + * EVP_KEYMGMT and OSSL_DESERIALIZER method numbers. Since + * EVP_KEYMGMT and OSSL_DESERIALIZE operate with the same + * namemap, we know that the method numbers must match. + * + * This allows individual deserializers to specify variants of keys, + * such as a DER to RSA deserializer finding a RSA-PSS key, without + * having to deserialize the exact same DER blob into the exact same + * internal structure twice. This is, of course, entirely at the + * discretion of the deserializer implementations. + */ + if (data->object_type != NULL + ? EVP_KEYMGMT_is_a(keymgmt, data->object_type) + : EVP_KEYMGMT_number(keymgmt) == OSSL_DESERIALIZER_number(deser)) { + EVP_PKEY *pkey = NULL; + void *keydata = NULL; + const OSSL_PROVIDER *keymgmt_prov = + EVP_KEYMGMT_provider(keymgmt); + const OSSL_PROVIDER *deser_prov = + OSSL_DESERIALIZER_provider(deser); + + /* + * If the EVP_KEYMGMT and the OSSL_DDESERIALIZER are from the + * same provider, we assume that the KEYMGMT has a key loading + * function that can handle the provider reference we hold. + * + * Otherwise, we export from the deserializer and import the + * result in the keymgmt. + */ + if (keymgmt_prov == deser_prov) { + keydata = evp_keymgmt_load(keymgmt, object_ref, object_ref_sz); + } else { + struct evp_keymgmt_util_try_import_data_st import_data; + + import_data.keymgmt = keymgmt; + import_data.keydata = NULL; + import_data.selection = OSSL_KEYMGMT_SELECT_ALL_PARAMETERS; + + /* + * No need to check for errors here, the value of + * |import_data.keydata| is as much an indicator. + */ + (void)deser->export_object(deserctx, object_ref, object_ref_sz, + &evp_keymgmt_util_try_import, + &import_data); + keydata = import_data.keydata; + import_data.keydata = NULL; + } + + if (keydata != NULL + && (pkey = + evp_keymgmt_util_make_pkey(keymgmt, keydata)) == NULL) + evp_keymgmt_freedata(keymgmt, keydata); + + *data->object = pkey; + + break; + } + } + /* + * We successfully looked through, |*ctx->object| determines if we + * actually found something. + */ + return (*data->object != NULL); +} + +static void deser_clean_EVP_PKEY(void *finalize_arg) +{ + struct deser_EVP_PKEY_data_st *data = finalize_arg; + + sk_EVP_KEYMGMT_pop_free(data->keymgmts, EVP_KEYMGMT_free); + OPENSSL_free(data->object_type); + OPENSSL_free(data); +} + +DEFINE_STACK_OF_CSTRING() + +struct collected_data_st { + struct deser_EVP_PKEY_data_st *process_data; + STACK_OF(OPENSSL_CSTRING) *names; + + unsigned int error_occured:1; +}; + +static void collect_keymgmt(EVP_KEYMGMT *keymgmt, void *arg) +{ + struct collected_data_st *data = arg; + + if (data->error_occured) + return; + + data->error_occured = 1; /* Assume the worst */ + + if (!EVP_KEYMGMT_up_ref(keymgmt) /* ref++ */) + return; + if (sk_EVP_KEYMGMT_push(data->process_data->keymgmts, keymgmt) <= 0) { + EVP_KEYMGMT_free(keymgmt); /* ref-- */ + return; + } + + data->error_occured = 0; /* All is good now */ +} + +static void collect_name(const char *name, void *arg) +{ + struct collected_data_st *data = arg; + + if (data->error_occured) + return; + + data->error_occured = 1; /* Assume the worst */ + + if (sk_OPENSSL_CSTRING_push(data->names, name) <= 0) + return; + + data->error_occured = 0; /* All is good now */ +} + +OSSL_DESERIALIZER_CTX * +OSSL_DESERIALIZER_CTX_new_by_EVP_PKEY(EVP_PKEY **pkey, + const char *input_type, + OPENSSL_CTX *libctx, + const char *propquery) +{ + OSSL_DESERIALIZER_CTX *ctx = NULL; + struct collected_data_st *data = NULL; + size_t i, end_i; + + if ((ctx = OSSL_DESERIALIZER_CTX_new()) == NULL + || (data = OPENSSL_zalloc(sizeof(*data))) == NULL + || (data->process_data = + OPENSSL_zalloc(sizeof(*data->process_data))) == NULL + || (data->process_data->keymgmts + = sk_EVP_KEYMGMT_new_null()) == NULL + || (data->names = sk_OPENSSL_CSTRING_new_null()) == NULL) { + ERR_raise(ERR_LIB_OSSL_DESERIALIZER, ERR_R_MALLOC_FAILURE); + goto err; + } + data->process_data->object = (void **)pkey; + OSSL_DESERIALIZER_CTX_set_input_type(ctx, input_type); + + /* First, find all keymgmts to form goals */ + EVP_KEYMGMT_do_all_provided(libctx, collect_keymgmt, data); + + if (data->error_occured) + goto err; + + /* + * Then, use the names of those keymgmts to find the first set of + * derializers. + */ + ERR_set_mark(); + end_i = sk_EVP_KEYMGMT_num(data->process_data->keymgmts); + for (i = 0; i < end_i; i++) { + EVP_KEYMGMT *keymgmt = + sk_EVP_KEYMGMT_value(data->process_data->keymgmts, i); + size_t j; + OSSL_DESERIALIZER *deser = NULL; + + EVP_KEYMGMT_names_do_all(keymgmt, collect_name, data); + + for (j = sk_OPENSSL_CSTRING_num(data->names); + j-- > 0 && deser == NULL;) { + const char *name = sk_OPENSSL_CSTRING_pop(data->names); + + ERR_set_mark(); + deser = OSSL_DESERIALIZER_fetch(libctx, name, propquery); + ERR_pop_to_mark(); + } + + /* + * The names in |data->names| aren't allocated for the stack, + * so we can simply clear it and let it be re-used. + */ + sk_OPENSSL_CSTRING_zero(data->names); + + /* + * If we found a matching serializer, try to add it to the context. + */ + if (deser != NULL) { + (void)OSSL_DESERIALIZER_CTX_add_deserializer(ctx, deser); + OSSL_DESERIALIZER_free(deser); + } + } + /* If we found no deserializers to match the keymgmts, we err */ + if (OSSL_DESERIALIZER_CTX_num_deserializers(ctx) == 0) { + ERR_clear_last_mark(); + goto err; + } + ERR_pop_to_mark(); + + /* Finally, collect extra deserializers based on what we already have */ + (void)OSSL_DESERIALIZER_CTX_add_extra(ctx, libctx, propquery); + + if (!OSSL_DESERIALIZER_CTX_set_finalizer(ctx, deser_finalize_EVP_PKEY, + deser_clean_EVP_PKEY, + data->process_data)) + goto err; + + data->process_data = NULL; + err: + if (data->process_data != NULL) + sk_EVP_KEYMGMT_pop_free(data->process_data->keymgmts, + EVP_KEYMGMT_free); + OPENSSL_free(data->process_data); + sk_OPENSSL_CSTRING_free(data->names); + OPENSSL_free(data); + return ctx; +} diff --git a/doc/man3/OSSL_DESERIALIZER_CTX_new_by_EVP_PKEY.pod b/doc/man3/OSSL_DESERIALIZER_CTX_new_by_EVP_PKEY.pod new file mode 100644 index 00000000000..9ed4e5992e9 --- /dev/null +++ b/doc/man3/OSSL_DESERIALIZER_CTX_new_by_EVP_PKEY.pod @@ -0,0 +1,117 @@ +=pod + +=head1 NAME + +OSSL_DESERIALIZER_CTX_new_by_EVP_PKEY, +OSSL_DESERIALIZER_CTX_set_cipher, +OSSL_DESERIALIZER_CTX_set_passphrase, +OSSL_DESERIALIZER_CTX_set_passphrase_cb, +OSSL_DESERIALIZER_CTX_set_passphrase_ui +- Deserializer routines to deserialize EVP_PKEYs + +=head1 SYNOPSIS + + #include + + OSSL_DESERIALIZER_CTX * + OSSL_DESERIALIZER_CTX_new_by_EVP_PKEY(const EVP_PKEY *pkey, + const char *input_type, + OPENSSL_CTX *libctx, + const char *propquery); + + int OSSL_DESERIALIZER_CTX_set_cipher(OSSL_DESERIALIZER_CTX *ctx, + const char *cipher_name, + const char *propquery); + int OSSL_DESERIALIZER_CTX_set_passphrase(OSSL_DESERIALIZER_CTX *ctx, + const unsigned char *kstr, + size_t klen); + int OSSL_DESERIALIZER_CTX_set_passphrase_cb(OSSL_DESERIALIZER_CTX *ctx, + pem_password_cb *cb, void *cbarg); + int OSSL_DESERIALIZER_CTX_set_passphrase_ui(OSSL_DESERIALIZER_CTX *ctx, + const UI_METHOD *ui_method, + void *ui_data); + +=head1 DESCRIPTION + +OSSL_DESERIALIZER_CTX_new_by_EVP_PKEY() is a utility function that +creates a B, finds all applicable deserializer +implementations and sets them up, so all the caller has to do next is +call functions like OSSL_DESERIALIZE_from_bio(). + +Internally OSSL_DESERIALIZER_CTX_new_by_EVP_PKEY() searches for all +available L implementations, and then builds a list of all +potential deserializer implementations that may be able to process the +serialized input into data suitable for Bs. All these +implementations are implicitly fetched using I and I. + +The search of deserializer implementations can be limited with +I, which specifies a starting input type. This is further +explained in L. + +If no suitable deserializer was found, OSSL_DESERIALIZER_CTX_new_by_EVP_PKEY() +still creates a B, but with no associated +deserializer (L returns +zero). This helps the caller distinguish between an error when +creating the B, and the lack the deserializer +support and act accordingly. + +OSSL_DESERIALIZER_CTX_set_cipher() tells the implementation what cipher +should be used to decrypt serialized keys. The cipher is given by +name I. The interpretation of that I is +implementation dependent. The implementation may implement the cipher +directly itself, or it may choose to fetch it. If the implementation +supports fetching the cipher, then it may use I as +properties to be queried for when fetching. I may also +be NULL, which will result in failure if the serialized input is an +encrypted key. + +OSSL_DESERIALIZER_CTX_set_passphrase() gives the implementation a +pass phrase to use when decrypting the serialized private key. +Alternatively, a pass phrase callback may be specified with the +following functions. + +OSSL_DESERIALIZER_CTX_set_passphrase_cb() and +OSSL_DESERIALIZER_CTX_set_passphrase_ui() sets up a callback method that +the implementation can use to prompt for a pass phrase. + +=for comment Note that the callback method is called indirectly, +through an internal B function. + +=head1 RETURN VALUES + +OSSL_DESERIALIZER_CTX_new_by_EVP_PKEY() returns a pointer to a +B, or NULL if it couldn't be created. + +OSSL_DESERIALIZER_CTX_set_cipher(), +OSSL_DESERIALIZER_CTX_set_passphrase(), +OSSL_DESERIALIZER_CTX_set_passphrase_cb(), and +OSSL_DESERIALIZER_CTX_set_passphrase_ui() all return 1 on success, or 0 +on failure. + +=head1 NOTES + +Parts of the function names are made to match already existing OpenSSL +names. + +B in OSSL_DESERIALIZER_CTX_new_by_EVP_PKEY() matches the type +name, thus making for the naming pattern +B>() when new types are handled. + +=head1 SEE ALSO + +L, L, L + +=head1 HISTORY + +The functions described here were added in OpenSSL 3.0. + +=head1 COPYRIGHT + +Copyright 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 +L. + +=cut diff --git a/include/openssl/deserializer.h b/include/openssl/deserializer.h index 89a230c1a63..e8752a6d1cd 100644 --- a/include/openssl/deserializer.h +++ b/include/openssl/deserializer.h @@ -105,6 +105,15 @@ int OSSL_DESERIALIZER_from_bio(OSSL_DESERIALIZER_CTX *ctx, BIO *in); int OSSL_DESERIALIZER_from_fp(OSSL_DESERIALIZER_CTX *ctx, FILE *in); #endif +/* + * Create the OSSL_DESERIALIZER_CTX with an associated type. This will perform + * an implicit OSSL_DESERIALIZER_fetch(), suitable for the object of that type. + */ +OSSL_DESERIALIZER_CTX * +OSSL_DESERIALIZER_CTX_new_by_EVP_PKEY(EVP_PKEY **pkey, const char *input_type, + OPENSSL_CTX *libctx, + const char *propquery); + # ifdef __cplusplus } # endif diff --git a/util/libcrypto.num b/util/libcrypto.num index 30d500f18f1..a4642f19735 100644 --- a/util/libcrypto.num +++ b/util/libcrypto.num @@ -5179,3 +5179,4 @@ OSSL_DESERIALIZER_INSTANCE_deserializer_ctx ? 3_0_0 EXIST::FUNCTION: ERR_load_OSSL_DESERIALIZER_strings ? 3_0_0 EXIST::FUNCTION: OSSL_DESERIALIZER_gettable_params ? 3_0_0 EXIST::FUNCTION: OSSL_DESERIALIZER_get_params ? 3_0_0 EXIST::FUNCTION: +OSSL_DESERIALIZER_CTX_new_by_EVP_PKEY ? 3_0_0 EXIST::FUNCTION: