ENCODER: Add output structure support for EVP_PKEY encoding

OSSL_ENCODER_CTX_new_by_EVP_PKEY() takes one more argument to express
the desired outermost structure for the output.

This also adds OSSL_ENCODER_CTX_prune_encoders(), which is used to
reduce the stack of encoders found according to criteria formed from
the combination of desired selection, output type and output
structure.

squash! ENCODER: Add output structure support for EVP_PKEY encoding

Replace the paragraph talking about OSSL_ENCODER_CTX_prune_encoders() with:

The encoding processor encoder_process() is enhanced with better
analysis of the stack of encoder implementations.  To avoid having to
keep an on the side array of information, it uses recursion.

Reviewed-by: Paul Dale <paul.dale@oracle.com>
(Merged from https://github.com/openssl/openssl/pull/13167)
This commit is contained in:
Richard Levitte 2020-10-17 08:23:43 +02:00
parent cd861ab73d
commit b9a2afdfe6
4 changed files with 291 additions and 213 deletions

View File

@ -16,11 +16,38 @@
#include <openssl/provider.h>
#include "encoder_local.h"
static int encoder_process(OSSL_ENCODER_CTX *ctx, BIO *out);
struct encoder_process_data_st {
OSSL_ENCODER_CTX *ctx;
/* Current BIO */
BIO *bio;
/* Index of the current encoder instance to be processed */
int current_encoder_inst_index;
/* Processing data passed down through recursion */
int level; /* Recursion level */
OSSL_ENCODER_INSTANCE *next_encoder_inst;
int count_output_structure;
/* Processing data passed up through recursion */
OSSL_ENCODER_INSTANCE *prev_encoder_inst;
unsigned char *running_output;
size_t running_output_length;
};
static int encoder_process(struct encoder_process_data_st *data);
int OSSL_ENCODER_to_bio(OSSL_ENCODER_CTX *ctx, BIO *out)
{
return encoder_process(ctx, out);
struct encoder_process_data_st data;
memset(&data, 0, sizeof(data));
data.ctx = ctx;
data.bio = out;
data.current_encoder_inst_index = OSSL_ENCODER_CTX_get_num_encoders(ctx);
return encoder_process(&data) > 0;
}
#ifndef OPENSSL_NO_STDIO
@ -57,7 +84,7 @@ int OSSL_ENCODER_to_data(OSSL_ENCODER_CTX *ctx, unsigned char **pdata,
int ret = 0;
if (pdata_len == NULL) {
ERR_raise(ERR_LIB_OSSL_DECODER, ERR_R_PASSED_NULL_PARAMETER);
ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_PASSED_NULL_PARAMETER);
return 0;
}
@ -336,132 +363,236 @@ OSSL_ENCODER_INSTANCE_get_output_structure(OSSL_ENCODER_INSTANCE *encoder_inst)
return encoder_inst->output_structure;
}
static int encoder_process(OSSL_ENCODER_CTX *ctx, BIO *out)
static int encoder_process(struct encoder_process_data_st *data)
{
size_t i, end;
void *latest_output = NULL;
size_t latest_output_length = 0;
const char *latest_output_type = NULL;
const char *last_input_type = NULL;
const char *last_output_structure = NULL;
int ok = 0;
OSSL_ENCODER_INSTANCE *current_encoder_inst = NULL;
OSSL_ENCODER *current_encoder = NULL;
OSSL_ENCODER_CTX *current_encoder_ctx = NULL;
BIO *allocated_out = NULL;
const void *original_data = NULL;
OSSL_PARAM abstract[10];
const OSSL_PARAM *current_abstract = NULL;
int i;
int ok = -1; /* -1 signifies that the lookup loop gave nothing */
int top = 0;
end = OSSL_ENCODER_CTX_get_num_encoders(ctx);
for (i = 0; i < end; i++) {
OSSL_ENCODER_INSTANCE *encoder_inst =
sk_OSSL_ENCODER_INSTANCE_value(ctx->encoder_insts, i);
OSSL_ENCODER *encoder = OSSL_ENCODER_INSTANCE_get_encoder(encoder_inst);
void *encoderctx = OSSL_ENCODER_INSTANCE_get_encoder_ctx(encoder_inst);
const char *current_input_type =
OSSL_ENCODER_INSTANCE_get_input_type(encoder_inst);
const char *current_output_type =
OSSL_ENCODER_INSTANCE_get_output_type(encoder_inst);
const char *current_output_structure =
OSSL_ENCODER_INSTANCE_get_output_structure(encoder_inst);
BIO *current_out;
BIO *allocated_out = NULL;
const void *current_data = NULL;
OSSL_PARAM abstract[10];
OSSL_PARAM *abstract_p;
const OSSL_PARAM *current_abstract = NULL;
if (data->next_encoder_inst == NULL) {
/* First iteration, where we prepare for what is to come */
if (latest_output_type == NULL) {
/*
* This is the first iteration, so we prepare the object to be
* encoded
*/
data->count_output_structure =
data->ctx->output_structure == NULL ? -1 : 0;
top = 1;
}
current_data = ctx->construct(encoder_inst, ctx->construct_data);
for (i = data->current_encoder_inst_index; i-- > 0;) {
OSSL_ENCODER *next_encoder = NULL;
const char *current_output_type;
const char *current_output_structure;
struct encoder_process_data_st new_data;
/* Assume that the constructor recorded an error */
if (current_data == NULL)
goto loop_end;
if (!top)
next_encoder =
OSSL_ENCODER_INSTANCE_get_encoder(data->next_encoder_inst);
current_encoder_inst =
sk_OSSL_ENCODER_INSTANCE_value(data->ctx->encoder_insts, i);
current_encoder =
OSSL_ENCODER_INSTANCE_get_encoder(current_encoder_inst);
current_encoder_ctx =
OSSL_ENCODER_INSTANCE_get_encoder_ctx(current_encoder_inst);
current_output_type =
OSSL_ENCODER_INSTANCE_get_output_type(current_encoder_inst);
current_output_structure =
OSSL_ENCODER_INSTANCE_get_output_structure(current_encoder_inst);
memset(&new_data, 0, sizeof(new_data));
new_data.ctx = data->ctx;
new_data.current_encoder_inst_index = i;
new_data.next_encoder_inst = current_encoder_inst;
new_data.count_output_structure = data->count_output_structure;
new_data.level = data->level + 1;
/*
* If this is the top call, we check if the output type of the current
* encoder matches the desired output type.
* If this isn't the top call, i.e. this is deeper in the recursion,
* we instead check if the output type of the current encoder matches
* the name of the next encoder (the one found by the parent call).
*/
if (top) {
if (data->ctx->output_type != NULL
&& strcasecmp(current_output_type,
data->ctx->output_type) != 0)
continue;
} else {
/*
* Check that the latest output type matches the currently
* considered encoder
*/
if (!OSSL_ENCODER_is_a(encoder, latest_output_type))
if (!OSSL_ENCODER_is_a(next_encoder, current_output_type))
continue;
}
/*
* If the caller and the current encoder specify an output structure,
* Check if they match. If they do, count the match, otherwise skip
* the current encoder.
*/
if (data->ctx->output_structure != NULL
&& current_output_structure != NULL) {
if (strcasecmp(data->ctx->output_structure,
current_output_structure) != 0)
continue;
/*
* If there is a latest output type, there should be a latest output
*/
if (!ossl_assert(latest_output != NULL)) {
ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_INTERNAL_ERROR);
goto loop_end;
}
/*
* Create an object abstraction from the latest output, which was
* stolen from the previous round.
*/
abstract_p = abstract;
if (last_input_type != NULL)
*abstract_p++ =
OSSL_PARAM_construct_utf8_string(OSSL_OBJECT_PARAM_DATA_TYPE,
(char *)last_input_type, 0);
if (last_output_structure != NULL)
*abstract_p++ =
OSSL_PARAM_construct_utf8_string(OSSL_OBJECT_PARAM_DATA_STRUCTURE,
(char *)last_output_structure,
0);
*abstract_p++ =
OSSL_PARAM_construct_octet_string(OSSL_OBJECT_PARAM_DATA,
latest_output,
latest_output_length);
*abstract_p = OSSL_PARAM_construct_end();
current_abstract = abstract;
data->count_output_structure++;
}
/*
* If the desired output type matches the output type of the currently
* considered encoder, we're setting up final output. Otherwise, set
* up an intermediary memory output.
* Recurse to process the encoder implementations before the current
* one.
*/
if (strcasecmp(ctx->output_type, current_output_type) == 0)
current_out = out;
else if ((current_out = allocated_out = BIO_new(BIO_s_mem())) == NULL)
goto loop_end; /* Assume BIO_new() recorded an error */
ok = encoder_process(&new_data);
ok = encoder->encode(encoderctx, (OSSL_CORE_BIO *)current_out,
current_data, current_abstract, ctx->selection,
ossl_pw_passphrase_callback_enc, &ctx->pwdata);
if (current_input_type != NULL)
last_input_type = current_input_type;
if (current_output_structure != NULL)
last_output_structure = current_output_structure;
if (!ok)
goto loop_end;
OPENSSL_free(latest_output);
data->prev_encoder_inst = new_data.prev_encoder_inst;
data->running_output = new_data.running_output;
data->running_output_length = new_data.running_output_length;
/*
* Steal the output from the BIO_s_mem, if we did allocate one.
* That'll be the data for an object abstraction in the next round.
* ok == -1 means that the recursion call above gave no further
* encoders, and that the one we're currently at should
* be tried.
* ok == 0 means that something failed in the recursion call
* above, making the result unsuitable for a chain.
* In this case, we simply continue to try finding a
* suitable encoder at this recursion level.
* ok == 1 means that the recursion call was successful, and we
* try to use the result at this recursion level.
*/
if (allocated_out != NULL) {
BUF_MEM *buf;
BIO_get_mem_ptr(allocated_out, &buf);
latest_output = buf->data;
latest_output_length = buf->length;
memset(buf, 0, sizeof(*buf));
BIO_free(allocated_out);
}
latest_output_type = encoder_inst->output_type;
loop_end:
if (current_data != NULL)
ctx->cleanup(ctx->construct_data);
if (ok)
if (ok != 0)
break;
}
OPENSSL_free(latest_output);
/*
* If |i < 0|, we didn't find any useful encoder in this recursion, so
* we do the rest of the process only if |i >= 0|.
*/
if (i < 0) {
ok = -1;
} else {
/* Preparations */
switch (ok) {
case 0:
break;
case -1:
/*
* We have reached the beginning of the encoder instance sequence,
* so we prepare the object to be encoded.
*/
/*
* |data->count_output_structure| is one of these values:
*
* -1 There is no desired output structure
* 0 There is a desired output structure, and it wasn't
* matched by any of the encoder instances that were
* considered
* >0 There is a desired output structure, and at least one
* of the encoder instances matched it
*/
if (data->count_output_structure == 0)
return 0;
original_data =
data->ctx->construct(current_encoder_inst,
data->ctx->construct_data);
/* Assume that the constructor recorded an error */
if (original_data != NULL)
ok = 1;
else
ok = 0;
break;
case 1:
if (!ossl_assert(data->running_output != NULL)) {
ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_INTERNAL_ERROR);
ok = 0;
break;
}
{
/*
* Create an object abstraction from the latest output, which
* was stolen from the previous round.
*/
OSSL_PARAM *abstract_p = abstract;
const char *prev_input_type =
OSSL_ENCODER_INSTANCE_get_input_type(data->prev_encoder_inst);
const char *prev_output_structure =
OSSL_ENCODER_INSTANCE_get_output_structure(data->prev_encoder_inst);
if (prev_input_type != NULL)
*abstract_p++ =
OSSL_PARAM_construct_utf8_string(OSSL_OBJECT_PARAM_DATA_TYPE,
(char *)prev_input_type, 0);
if (prev_output_structure != NULL)
*abstract_p++ =
OSSL_PARAM_construct_utf8_string(OSSL_OBJECT_PARAM_DATA_STRUCTURE,
(char *)prev_output_structure,
0);
*abstract_p++ =
OSSL_PARAM_construct_octet_string(OSSL_OBJECT_PARAM_DATA,
data->running_output,
data->running_output_length);
*abstract_p = OSSL_PARAM_construct_end();
current_abstract = abstract;
}
break;
}
/* Calling the encoder implementation */
if (ok) {
BIO *current_out = NULL;
/*
* If we're at the last encoder instance to use, we're setting up
* final output. Otherwise, set up an intermediary memory output.
*/
if (top)
current_out = data->bio;
else if ((current_out = allocated_out = BIO_new(BIO_s_mem()))
== NULL)
ok = 0; /* Assume BIO_new() recorded an error */
if (ok)
ok = current_encoder->encode(current_encoder_ctx,
(OSSL_CORE_BIO *)current_out,
original_data, current_abstract,
data->ctx->selection,
ossl_pw_passphrase_callback_enc,
&data->ctx->pwdata);
data->prev_encoder_inst = current_encoder_inst;
}
}
/* Cleanup and collecting the result */
OPENSSL_free(data->running_output);
data->running_output = NULL;
/*
* Steal the output from the BIO_s_mem, if we did allocate one.
* That'll be the data for an object abstraction in the next round.
*/
if (allocated_out != NULL) {
BUF_MEM *buf;
BIO_get_mem_ptr(allocated_out, &buf);
data->running_output = (unsigned char *)buf->data;
data->running_output_length = buf->length;
memset(buf, 0, sizeof(*buf));
}
BIO_free(allocated_out);
if (original_data != NULL)
data->ctx->cleanup(data->ctx->construct_data);
return ok;
}

View File

@ -13,6 +13,7 @@
#include <openssl/params.h>
#include <openssl/encoder.h>
#include <openssl/core_names.h>
#include <openssl/provider.h>
#include <openssl/safestack.h>
#include "internal/provider.h"
#include "internal/property.h"
@ -70,40 +71,42 @@ int OSSL_ENCODER_CTX_set_passphrase_cb(OSSL_ENCODER_CTX *ctx,
*/
struct collected_encoder_st {
STACK_OF(OPENSSL_CSTRING) *names;
const char *output_structure;
const char *output_type;
STACK_OF(OSSL_ENCODER) *encoders;
OSSL_ENCODER_CTX *ctx;
int error_occured;
};
static void collect_encoder(OSSL_ENCODER *encoder, void *arg)
{
struct collected_encoder_st *data = arg;
OSSL_PARAM params[2] = { OSSL_PARAM_END, OSSL_PARAM_END };
const char *output_type = NULL;
size_t i, end_i;
if (data->error_occured)
return;
/*
* Ask for the output type. If the encoder doesn't answer to that,
* we refuse it.
*/
params[0] =
OSSL_PARAM_construct_utf8_ptr(OSSL_ENCODER_PARAM_OUTPUT_TYPE,
(char **)&output_type, 0);
if (!encoder->get_params(params)
|| !OSSL_PARAM_modified(&params[0])
|| output_type == NULL
|| strcasecmp(output_type, data->output_type) != 0)
data->error_occured = 1; /* Assume the worst */
if (data->names == NULL)
return;
data->error_occured = 1; /* Assume the worst */
end_i = sk_OPENSSL_CSTRING_num(data->names);
for (i = 0; i < end_i; i++) {
const char *name = sk_OPENSSL_CSTRING_value(data->names, i);
const OSSL_PROVIDER *prov = OSSL_ENCODER_provider(encoder);
void *provctx = OSSL_PROVIDER_get0_provider_ctx(prov);
if (!OSSL_ENCODER_up_ref(encoder) /* ref++ */)
return;
if (sk_OSSL_ENCODER_push(data->encoders, encoder) <= 0) {
OSSL_ENCODER_free(encoder); /* ref-- */
return;
if (!OSSL_ENCODER_is_a(encoder, name)
|| (encoder->does_selection != NULL
&& !encoder->does_selection(provctx, data->ctx->selection)))
continue;
/* Only add each encoder implementation once */
if (OSSL_ENCODER_CTX_add_encoder(data->ctx, encoder))
break;
}
data->error_occured = 0; /* All is good now */
@ -218,11 +221,8 @@ static int ossl_encoder_ctx_setup_for_EVP_PKEY(OSSL_ENCODER_CTX *ctx,
}
if (pkey->keymgmt != NULL) {
OSSL_ENCODER *found = NULL;
const OSSL_PROVIDER *desired_prov = EVP_KEYMGMT_provider(pkey->keymgmt);
struct collected_encoder_st encoder_data;
struct collected_names_st keymgmt_data;
int i;
if ((data = OPENSSL_zalloc(sizeof(*data))) == NULL) {
ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_MALLOC_FAILURE);
@ -230,87 +230,28 @@ static int ossl_encoder_ctx_setup_for_EVP_PKEY(OSSL_ENCODER_CTX *ctx,
}
/*
* Select the encoder in two steps. First, collect all encoders
* that have the correct output type, as well as all keymgmt names.
* Select the first encoder implementations in two steps.
* First, collect the keymgmt names, then the encoders that match.
*/
encoder_data.output_type = ctx->output_type;
encoder_data.encoders = sk_OSSL_ENCODER_new_null();
encoder_data.error_occured = 0;
keymgmt_data.names = sk_OPENSSL_CSTRING_new_null();
keymgmt_data.error_occured = 0;
if (encoder_data.encoders == NULL || keymgmt_data.names == NULL) {
sk_OSSL_ENCODER_free(encoder_data.encoders);
sk_OPENSSL_CSTRING_free(keymgmt_data.names);
return 0;
}
OSSL_ENCODER_do_all_provided(libctx, collect_encoder, &encoder_data);
EVP_KEYMGMT_names_do_all(pkey->keymgmt, collect_name, &keymgmt_data);
/*-
* Now we look for the most desirable encoder for our |pkey|.
*
* Encoders offer two functions:
*
* - one ('encode') that encodes a given provider-native object that
* it knows intimately, so it must be from the same provider.
* - one ('import_object') that imports the parameters of an object
* of the same type from a different provider, which is used to
* create a temporary object that 'encode' can handle.
*
* It is, of course, more desirable to be able to use 'encode'
* directly without having to go through an export/import maneuver,
* but the latter allows us to have generic encoders.
*
* Of course, if |libctx| is different from |pkey|'s library context,
* we're going to have to do an export/import maneuvre no matter what.
*/
for (i = 0; i < sk_OSSL_ENCODER_num(encoder_data.encoders); i++) {
OSSL_ENCODER *encoder =
sk_OSSL_ENCODER_value(encoder_data.encoders, i);
int j;
/* Check that any of the |keymgmt| names match */
for (j = 0; j < sk_OPENSSL_CSTRING_num(keymgmt_data.names); j++) {
const char *name =
sk_OPENSSL_CSTRING_value(keymgmt_data.names, j);
if (OSSL_ENCODER_is_a(encoder, name))
break;
}
if (j == sk_OPENSSL_CSTRING_num(keymgmt_data.names))
continue;
/* We found one! Process it */
if (OSSL_ENCODER_provider(encoder) == desired_prov) {
/*
* We found one in the same provider as the keymgmt. Choose
* it and stop looking.
*/
found = encoder;
break;
}
if (found == NULL && encoder->import_object != NULL) {
/*
* We found one that's good enough. Choose it for now, but
* keep looking.
*/
found = encoder;
}
}
if (found != NULL) {
(void)OSSL_ENCODER_CTX_add_encoder(ctx, found);
} else {
if (encoder_data.error_occured)
ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_MALLOC_FAILURE);
else
ERR_raise(ERR_LIB_OSSL_ENCODER,
OSSL_ENCODER_R_ENCODER_NOT_FOUND);
if (keymgmt_data.error_occured) {
sk_OPENSSL_CSTRING_free(keymgmt_data.names);
goto err;
}
encoder_data.names = keymgmt_data.names;
encoder_data.output_type = ctx->output_type;
encoder_data.output_structure = ctx->output_structure;
encoder_data.error_occured = 0;
encoder_data.ctx = ctx;
OSSL_ENCODER_do_all_provided(libctx, collect_encoder, &encoder_data);
sk_OPENSSL_CSTRING_free(keymgmt_data.names);
sk_OSSL_ENCODER_pop_free(encoder_data.encoders, OSSL_ENCODER_free);
if (encoder_data.error_occured) {
ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_MALLOC_FAILURE);
goto err;
}
}
if (OSSL_ENCODER_CTX_get_num_encoders(ctx) != 0) {
@ -335,8 +276,9 @@ static int ossl_encoder_ctx_setup_for_EVP_PKEY(OSSL_ENCODER_CTX *ctx,
}
OSSL_ENCODER_CTX *OSSL_ENCODER_CTX_new_by_EVP_PKEY(const EVP_PKEY *pkey,
const char *output_type,
int selection,
const char *output_type,
const char *output_struct,
OSSL_LIB_CTX *libctx,
const char *propquery)
{
@ -347,6 +289,8 @@ OSSL_ENCODER_CTX *OSSL_ENCODER_CTX_new_by_EVP_PKEY(const EVP_PKEY *pkey,
return NULL;
}
if (OSSL_ENCODER_CTX_set_output_type(ctx, output_type)
&& (output_struct == NULL
|| OSSL_ENCODER_CTX_set_output_structure(ctx, output_struct))
&& OSSL_ENCODER_CTX_set_selection(ctx, selection)
&& ossl_encoder_ctx_setup_for_EVP_PKEY(ctx, pkey, selection,
libctx, propquery)

View File

@ -15,8 +15,9 @@ OSSL_ENCODER_CTX_set_passphrase_ui
#include <openssl/encoder.h>
OSSL_ENCODER_CTX *
OSSL_ENCODER_CTX_new_by_EVP_PKEY(const EVP_PKEY *pkey,
const char *output_type, int selection,
OSSL_ENCODER_CTX_new_by_EVP_PKEY(const EVP_PKEY *pkey, int selection,
const char *output_type,
const char *output_structure,
OSSL_LIB_CTX *libctx, const char *propquery);
int OSSL_ENCODER_CTX_set_cipher(OSSL_ENCODER_CTX *ctx,
@ -48,7 +49,8 @@ L</Selections>.
Internally, OSSL_ENCODER_CTX_new_by_EVP_PKEY() uses the names from the
L<EVP_KEYMGMT(3)> implementation associated with I<pkey> to build a list of
applicable encoder implementations that are used to process the I<pkey> into
the encoding named by I<output_type>. All these implementations are
the encoding named by I<output_type>, with the outermost structure named by
I<output_structure> if that's relevant. All these implementations are
implicitly fetched using I<libctx> and I<propquery>.
If no suitable encoder implementation is found,

View File

@ -114,8 +114,9 @@ int OSSL_ENCODER_to_data(OSSL_ENCODER_CTX *ctx, unsigned char **pdata,
* This is more useful than calling OSSL_ENCODER_CTX_new().
*/
OSSL_ENCODER_CTX *OSSL_ENCODER_CTX_new_by_EVP_PKEY(const EVP_PKEY *pkey,
const char *output_type,
int selection,
const char *output_type,
const char *output_struct,
OSSL_LIB_CTX *libctx,
const char *propquery);