/* * Copyright 2019-2021 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 "e_os.h" /* strcasecmp on Windows */ #include #include #include #include #include #include #include #include "internal/bio.h" #include "encoder_local.h" 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) { 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 static BIO *bio_from_file(FILE *fp) { BIO *b; if ((b = BIO_new(BIO_s_file())) == NULL) { ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_BUF_LIB); return NULL; } BIO_set_fp(b, fp, BIO_NOCLOSE); return b; } int OSSL_ENCODER_to_fp(OSSL_ENCODER_CTX *ctx, FILE *fp) { BIO *b = bio_from_file(fp); int ret = 0; if (b != NULL) ret = OSSL_ENCODER_to_bio(ctx, b); BIO_free(b); return ret; } #endif int OSSL_ENCODER_to_data(OSSL_ENCODER_CTX *ctx, unsigned char **pdata, size_t *pdata_len) { BIO *out = BIO_new(BIO_s_mem()); BUF_MEM *buf = NULL; int ret = 0; if (pdata_len == NULL) { ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_PASSED_NULL_PARAMETER); return 0; } if (OSSL_ENCODER_to_bio(ctx, out) && BIO_get_mem_ptr(out, &buf) > 0) { ret = 1; /* Hope for the best. A too small buffer will clear this */ if (pdata != NULL && *pdata != NULL) { if (*pdata_len < buf->length) /* * It's tempting to do |*pdata_len = (size_t)buf->length| * However, it's believed to be confusing more than helpful, * so we don't. */ ret = 0; else *pdata_len -= buf->length; } else { /* The buffer with the right size is already allocated for us */ *pdata_len = (size_t)buf->length; } if (ret) { if (pdata != NULL) { if (*pdata != NULL) { memcpy(*pdata, buf->data, buf->length); *pdata += buf->length; } else { /* In this case, we steal the data from BIO_s_mem() */ *pdata = (unsigned char *)buf->data; buf->data = NULL; } } } } BIO_free(out); return ret; } int OSSL_ENCODER_CTX_set_selection(OSSL_ENCODER_CTX *ctx, int selection) { if (!ossl_assert(ctx != NULL)) { ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_PASSED_NULL_PARAMETER); return 0; } if (!ossl_assert(selection != 0)) { ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_PASSED_INVALID_ARGUMENT); return 0; } ctx->selection = selection; return 1; } int OSSL_ENCODER_CTX_set_output_type(OSSL_ENCODER_CTX *ctx, const char *output_type) { if (!ossl_assert(ctx != NULL) || !ossl_assert(output_type != NULL)) { ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_PASSED_NULL_PARAMETER); return 0; } ctx->output_type = output_type; return 1; } int OSSL_ENCODER_CTX_set_output_structure(OSSL_ENCODER_CTX *ctx, const char *output_structure) { if (!ossl_assert(ctx != NULL) || !ossl_assert(output_structure != NULL)) { ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_PASSED_NULL_PARAMETER); return 0; } ctx->output_structure = output_structure; return 1; } static OSSL_ENCODER_INSTANCE *ossl_encoder_instance_new(OSSL_ENCODER *encoder, void *encoderctx) { OSSL_ENCODER_INSTANCE *encoder_inst = NULL; OSSL_PARAM params[4]; if (!ossl_assert(encoder != NULL)) { ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_PASSED_NULL_PARAMETER); return 0; } if (encoder->get_params == NULL) { ERR_raise(ERR_LIB_OSSL_ENCODER, OSSL_ENCODER_R_MISSING_GET_PARAMS); return 0; } if ((encoder_inst = OPENSSL_zalloc(sizeof(*encoder_inst))) == NULL) { ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_MALLOC_FAILURE); return 0; } /* * Cache the input and output types for this encoder. The output type * is mandatory. */ params[0] = OSSL_PARAM_construct_utf8_ptr(OSSL_ENCODER_PARAM_OUTPUT_TYPE, (char **)&encoder_inst->output_type, 0); params[1] = OSSL_PARAM_construct_utf8_ptr(OSSL_ENCODER_PARAM_OUTPUT_STRUCTURE, (char **)&encoder_inst->output_structure, 0); params[2] = OSSL_PARAM_construct_utf8_ptr(OSSL_ENCODER_PARAM_INPUT_TYPE, (char **)&encoder_inst->input_type, 0); params[3] = OSSL_PARAM_construct_end(); if (!encoder->get_params(params) || !OSSL_PARAM_modified(¶ms[0])) goto err; if (!OSSL_ENCODER_up_ref(encoder)) { ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_INTERNAL_ERROR); goto err; } encoder_inst->encoder = encoder; encoder_inst->encoderctx = encoderctx; return encoder_inst; err: ossl_encoder_instance_free(encoder_inst); return NULL; } void ossl_encoder_instance_free(OSSL_ENCODER_INSTANCE *encoder_inst) { if (encoder_inst != NULL) { if (encoder_inst->encoder != NULL) encoder_inst->encoder->freectx(encoder_inst->encoderctx); encoder_inst->encoderctx = NULL; OSSL_ENCODER_free(encoder_inst->encoder); encoder_inst->encoder = NULL; OPENSSL_free(encoder_inst); } } static int ossl_encoder_ctx_add_encoder_inst(OSSL_ENCODER_CTX *ctx, OSSL_ENCODER_INSTANCE *ei) { int ok; if (ctx->encoder_insts == NULL && (ctx->encoder_insts = sk_OSSL_ENCODER_INSTANCE_new_null()) == NULL) { ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_MALLOC_FAILURE); return 0; } ok = (sk_OSSL_ENCODER_INSTANCE_push(ctx->encoder_insts, ei) > 0); if (ok) { OSSL_TRACE_BEGIN(ENCODER) { BIO_printf(trc_out, "(ctx %p) Added encoder instance %p (encoder %p) with:\n", (void *)ctx, (void *)ei, (void *)ei->encoder); BIO_printf(trc_out, " output type: %s, output structure: %s, input type :%s\n", ei->output_type, ei->output_structure, ei->input_type); } OSSL_TRACE_END(ENCODER); } return ok; } int OSSL_ENCODER_CTX_add_encoder(OSSL_ENCODER_CTX *ctx, OSSL_ENCODER *encoder) { OSSL_ENCODER_INSTANCE *encoder_inst = NULL; const OSSL_PROVIDER *prov = NULL; void *encoderctx = NULL; void *provctx = NULL; if (!ossl_assert(ctx != NULL) || !ossl_assert(encoder != NULL)) { ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_PASSED_NULL_PARAMETER); return 0; } prov = OSSL_ENCODER_provider(encoder); provctx = OSSL_PROVIDER_get0_provider_ctx(prov); if ((encoderctx = encoder->newctx(provctx)) == NULL || (encoder_inst = ossl_encoder_instance_new(encoder, encoderctx)) == NULL) goto err; /* Avoid double free of encoderctx on further errors */ encoderctx = NULL; if (!ossl_encoder_ctx_add_encoder_inst(ctx, encoder_inst)) goto err; return 1; err: ossl_encoder_instance_free(encoder_inst); if (encoderctx != NULL) encoder->freectx(encoderctx); return 0; } int OSSL_ENCODER_CTX_add_extra(OSSL_ENCODER_CTX *ctx, OSSL_LIB_CTX *libctx, const char *propq) { return 1; } int OSSL_ENCODER_CTX_get_num_encoders(OSSL_ENCODER_CTX *ctx) { if (ctx == NULL || ctx->encoder_insts == NULL) return 0; return sk_OSSL_ENCODER_INSTANCE_num(ctx->encoder_insts); } int OSSL_ENCODER_CTX_set_construct(OSSL_ENCODER_CTX *ctx, OSSL_ENCODER_CONSTRUCT *construct) { if (!ossl_assert(ctx != NULL)) { ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_PASSED_NULL_PARAMETER); return 0; } ctx->construct = construct; return 1; } int OSSL_ENCODER_CTX_set_construct_data(OSSL_ENCODER_CTX *ctx, void *construct_data) { if (!ossl_assert(ctx != NULL)) { ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_PASSED_NULL_PARAMETER); return 0; } ctx->construct_data = construct_data; return 1; } int OSSL_ENCODER_CTX_set_cleanup(OSSL_ENCODER_CTX *ctx, OSSL_ENCODER_CLEANUP *cleanup) { if (!ossl_assert(ctx != NULL)) { ERR_raise(ERR_LIB_OSSL_ENCODER, ERR_R_PASSED_NULL_PARAMETER); return 0; } ctx->cleanup = cleanup; return 1; } OSSL_ENCODER * OSSL_ENCODER_INSTANCE_get_encoder(OSSL_ENCODER_INSTANCE *encoder_inst) { if (encoder_inst == NULL) return NULL; return encoder_inst->encoder; } void * OSSL_ENCODER_INSTANCE_get_encoder_ctx(OSSL_ENCODER_INSTANCE *encoder_inst) { if (encoder_inst == NULL) return NULL; return encoder_inst->encoderctx; } const char * OSSL_ENCODER_INSTANCE_get_input_type(OSSL_ENCODER_INSTANCE *encoder_inst) { if (encoder_inst == NULL) return NULL; return encoder_inst->input_type; } const char * OSSL_ENCODER_INSTANCE_get_output_type(OSSL_ENCODER_INSTANCE *encoder_inst) { if (encoder_inst == NULL) return NULL; return encoder_inst->output_type; } const char * OSSL_ENCODER_INSTANCE_get_output_structure(OSSL_ENCODER_INSTANCE *encoder_inst) { if (encoder_inst == NULL) return NULL; return encoder_inst->output_structure; } static int encoder_process(struct encoder_process_data_st *data) { 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; if (data->next_encoder_inst == NULL) { /* First iteration, where we prepare for what is to come */ data->count_output_structure = data->ctx->output_structure == NULL ? -1 : 0; top = 1; } 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; 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; OSSL_TRACE_BEGIN(ENCODER) { BIO_printf(trc_out, "[%d] (ctx %p) Considering encoder instance %p (encoder %p)\n", data->level, (void *)data->ctx, (void *)current_encoder_inst, (void *)current_encoder); } OSSL_TRACE_END(ENCODER); /* * 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) { OSSL_TRACE_BEGIN(ENCODER) { BIO_printf(trc_out, "[%d] Skipping because current encoder output type (%s) != desired output type (%s)\n", data->level, current_output_type, data->ctx->output_type); } OSSL_TRACE_END(ENCODER); continue; } } else { if (!OSSL_ENCODER_is_a(next_encoder, current_output_type)) { OSSL_TRACE_BEGIN(ENCODER) { BIO_printf(trc_out, "[%d] Skipping because current encoder output type (%s) != name of encoder %p\n", data->level, current_output_type, (void *)next_encoder); } OSSL_TRACE_END(ENCODER); 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) { OSSL_TRACE_BEGIN(ENCODER) { BIO_printf(trc_out, "[%d] Skipping because current encoder output structure (%s) != ctx output structure (%s)\n", data->level, current_output_structure, data->ctx->output_structure); } OSSL_TRACE_END(ENCODER); continue; } data->count_output_structure++; } /* * Recurse to process the encoder implementations before the current * one. */ ok = encoder_process(&new_data); 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; /* * 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 (ok != 0) break; OSSL_TRACE_BEGIN(ENCODER) { BIO_printf(trc_out, "[%d] Skipping because recusion level %d failed\n", data->level, new_data.level); } OSSL_TRACE_END(ENCODER); } /* * 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; OSSL_TRACE_BEGIN(ENCODER) { BIO_printf(trc_out, "[%d] (ctx %p) No suitable encoder found\n", data->level, (void *)data->ctx); } OSSL_TRACE_END(ENCODER); } 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) { OSSL_CORE_BIO *cbio = NULL; 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 = (cbio = ossl_core_bio_new_from_bio(current_out)) != NULL; if (ok) { ok = current_encoder->encode(current_encoder_ctx, cbio, original_data, current_abstract, data->ctx->selection, ossl_pw_passphrase_callback_enc, &data->ctx->pwdata); OSSL_TRACE_BEGIN(ENCODER) { BIO_printf(trc_out, "[%d] (ctx %p) Running encoder instance %p => %d\n", data->level, (void *)data->ctx, (void *)current_encoder_inst, ok); } OSSL_TRACE_END(ENCODER); } ossl_core_bio_free(cbio); 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; }