diff options
| author | Jakub Zelenka <[email protected]> | 2026-07-20 21:35:48 +0200 |
|---|---|---|
| committer | Tomas Mraz <[email protected]> | 2026-08-27 15:44:59 +0200 |
| commit | b542f2545cc10f6b481fd70600bcb0b8523e09a9 (patch) | |
| tree | affe47d1450370c8f22799d7e3ae0970e1e843b3 /test/endecode_api_test.c | |
| parent | siv128.siv128.c: refactor encrypt/decrypt to a single exit point (diff) | |
| download | openssl-b542f2545cc10f6b481fd70600bcb0b8523e09a9.tar.gz openssl-b542f2545cc10f6b481fd70600bcb0b8523e09a9.zip | |
test: add low-level OSSL_ENCODER / OSSL_DECODER API test
Add a test that exercises the encoder and decoder chain processing in
encoder_lib.c and decoder_lib.c through a built-in test provider
implementing a two-stage encoder chain ("TEST-KEY" -> "inter" -> "pem")
and the mirrored decoder chain.
This covers previously untested code paths:
- encoder chaining through abstract objects, including the intermediate
memory BIO handling in encoder_process()
- OSSL_ENCODER_to_fp() and OSSL_DECODER_from_fp()
- OSSL_ENCODER_to_data() edge cases (size query, pre-allocated buffer,
too small buffer)
- OSSL_DECODER_CTX_add_decoder() and OSSL_DECODER_export()
- decoding from a non-seekable BIO (BIO_f_readbuffer wrapping)
- various error paths and NULL argument checks in both APIs
Assisted-by: Claude:claude-fable-5
Reviewed-by: Matt Caswell <[email protected]>
Reviewed-by: Tomas Mraz <[email protected]>
Merge-date: Thu Aug 27 13:45:07 2026
Merged-from: https://github.com/openssl/openssl/pull/32019
Diffstat (limited to 'test/endecode_api_test.c')
| -rw-r--r-- | test/endecode_api_test.c | 986 |
1 files changed, 986 insertions, 0 deletions
diff --git a/test/endecode_api_test.c b/test/endecode_api_test.c new file mode 100644 index 00000000000..768bf215c04 --- /dev/null +++ b/test/endecode_api_test.c @@ -0,0 +1,986 @@ +/* + * Copyright 2026 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 + */ + +/* + * Low-level OSSL_ENCODER / OSSL_DECODER API tests, using a test provider + * that implements a two-stage encoder chain ("TEST-KEY" -> "inter" -> "pem") + * and the corresponding decoder chain in the opposite direction. + */ + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include <openssl/bio.h> +#include <openssl/core.h> +#include <openssl/core_dispatch.h> +#include <openssl/core_names.h> +#include <openssl/crypto.h> +#include <openssl/decoder.h> +#include <openssl/encoder.h> +#include <openssl/params.h> +#include <openssl/provider.h> +#include "testutil.h" + +#define KEY_NAME "TEST-KEY" +#define INTER_NAME "inter" +#define PEM_NAME "pem" +#define STRUCTURE_NAME "test-structure" + +#define PEM_HEADER "-----BEGIN " KEY_NAME "-----\n" +#define PEM_FOOTER "\n-----END " KEY_NAME "-----\n" +#define INTER_PREFIX "test-key-v1:" + +#define TEST_SELECTION 0x07 +/* A seed value that makes the "TEST-KEY" encoder implementation fail */ +#define FAIL_SEED 666 + +static OSSL_LIB_CTX *testctx = NULL; +static OSSL_PROVIDER *testprov = NULL; + +struct test_key_st { + unsigned int seed; + char label[32]; +}; + +/* + * The test provider. + * + * It supplies two encoders: one encoding a test key into an intermediate + * "inter" format, and one wrapping "inter" data into a PEM-like format. + * OSSL_ENCODER_to_bio() is expected to chain them, giving the deeper + * encoder an intermediate memory BIO and passing its output to the outer + * encoder as an abstract object. The decoders mirror the encoders. + * + * The provider context is a child library context, so that the codec + * implementations can use BIO_new_from_core_bio(). + */ + +static void *codec_newctx(void *provctx) +{ + return provctx; +} + +static void codec_freectx(void *vctx) +{ +} + +static int key2inter_encode(void *vctx, OSSL_CORE_BIO *cout, + const void *obj_raw, const OSSL_PARAM obj_abstract[], + int selection, + OSSL_PASSPHRASE_CALLBACK *cb, void *cbarg) +{ + const struct test_key_st *key = obj_raw; + BIO *out; + int ok; + + /* The deepest encoder is given the object as passed by the constructor */ + if (key == NULL || obj_abstract != NULL || selection != TEST_SELECTION) + return 0; + if (key->seed == FAIL_SEED) + return 0; + if ((out = BIO_new_from_core_bio(vctx, cout)) == NULL) + return 0; + ok = BIO_printf(out, INTER_PREFIX "%u:%s", key->seed, key->label) > 0; + BIO_free(out); + return ok; +} + +static const OSSL_DISPATCH key2inter_encoder_functions[] = { + { OSSL_FUNC_ENCODER_NEWCTX, (void (*)(void))codec_newctx }, + { OSSL_FUNC_ENCODER_FREECTX, (void (*)(void))codec_freectx }, + { OSSL_FUNC_ENCODER_ENCODE, (void (*)(void))key2inter_encode }, + OSSL_DISPATCH_END +}; + +static int inter2pem_encode(void *vctx, OSSL_CORE_BIO *cout, + const void *obj_raw, const OSSL_PARAM obj_abstract[], + int selection, + OSSL_PASSPHRASE_CALLBACK *cb, void *cbarg) +{ + const OSSL_PARAM *p; + BIO *out; + int ok; + + /* A chained encoder is given an abstract object, not a raw one */ + if (obj_abstract == NULL || obj_raw != NULL) + return 0; + + /* The data structure of the previous encoding round must be passed on */ + p = OSSL_PARAM_locate_const(obj_abstract, OSSL_OBJECT_PARAM_DATA_STRUCTURE); + if (p == NULL || p->data_type != OSSL_PARAM_UTF8_STRING + || p->data_size != strlen(STRUCTURE_NAME) + || memcmp(p->data, STRUCTURE_NAME, p->data_size) != 0) + return 0; + + p = OSSL_PARAM_locate_const(obj_abstract, OSSL_OBJECT_PARAM_DATA); + if (p == NULL || p->data_type != OSSL_PARAM_OCTET_STRING) + return 0; + + if ((out = BIO_new_from_core_bio(vctx, cout)) == NULL) + return 0; + ok = BIO_printf(out, "%s", PEM_HEADER) > 0 + && BIO_write(out, p->data, (int)p->data_size) == (int)p->data_size + && BIO_printf(out, "%s", PEM_FOOTER) > 0; + BIO_free(out); + return ok; +} + +static const OSSL_DISPATCH inter2pem_encoder_functions[] = { + { OSSL_FUNC_ENCODER_NEWCTX, (void (*)(void))codec_newctx }, + { OSSL_FUNC_ENCODER_FREECTX, (void (*)(void))codec_freectx }, + { OSSL_FUNC_ENCODER_ENCODE, (void (*)(void))inter2pem_encode }, + OSSL_DISPATCH_END +}; + +static const OSSL_ALGORITHM test_encoders[] = { + { KEY_NAME, "provider=apitest,output=" INTER_NAME ",structure=" STRUCTURE_NAME, + key2inter_encoder_functions }, + { INTER_NAME, "provider=apitest,output=" PEM_NAME, + inter2pem_encoder_functions }, + { NULL, NULL, NULL } +}; + +/* Read everything from a core BIO into |buf| as a NUL terminated string */ +static int read_core_bio(void *provctx, OSSL_CORE_BIO *cin, + char *buf, size_t bufsz, size_t *readlen) +{ + BIO *in; + size_t total = 0; + int l; + + if ((in = BIO_new_from_core_bio(provctx, cin)) == NULL) + return 0; + while (total < bufsz - 1 + && (l = BIO_read(in, buf + total, (int)(bufsz - 1 - total))) > 0) + total += l; + BIO_free(in); + buf[total] = '\0'; + *readlen = total; + return 1; +} + +static int pem2inter_decode(void *vctx, OSSL_CORE_BIO *cin, int selection, + OSSL_CALLBACK *data_cb, void *data_cbarg, + OSSL_PASSPHRASE_CALLBACK *pw_cb, void *pw_cbarg) +{ + char buf[1024]; + size_t len, payload_len; + OSSL_PARAM params[5]; + + if (!read_core_bio(vctx, cin, buf, sizeof(buf), &len)) + return 0; + + /* If it isn't wrapped in our PEM-like format, it's not for us */ + if (len < sizeof(PEM_HEADER) - 1 + sizeof(PEM_FOOTER) - 1 + || strncmp(buf, PEM_HEADER, sizeof(PEM_HEADER) - 1) != 0 + || strcmp(buf + len - (sizeof(PEM_FOOTER) - 1), PEM_FOOTER) != 0) + return 1; + + payload_len = len - (sizeof(PEM_HEADER) - 1) - (sizeof(PEM_FOOTER) - 1); + params[0] = OSSL_PARAM_construct_octet_string(OSSL_OBJECT_PARAM_DATA, + buf + sizeof(PEM_HEADER) - 1, + payload_len); + params[1] = OSSL_PARAM_construct_utf8_string(OSSL_OBJECT_PARAM_DATA_TYPE, + (char *)KEY_NAME, 0); + params[2] = OSSL_PARAM_construct_utf8_string(OSSL_OBJECT_PARAM_DATA_STRUCTURE, + (char *)STRUCTURE_NAME, 0); + params[3] = OSSL_PARAM_construct_utf8_string(OSSL_OBJECT_PARAM_INPUT_TYPE, + (char *)INTER_NAME, 0); + params[4] = OSSL_PARAM_construct_end(); + + return data_cb(params, data_cbarg); +} + +static const OSSL_DISPATCH pem2inter_decoder_functions[] = { + { OSSL_FUNC_DECODER_NEWCTX, (void (*)(void))codec_newctx }, + { OSSL_FUNC_DECODER_FREECTX, (void (*)(void))codec_freectx }, + { OSSL_FUNC_DECODER_DECODE, (void (*)(void))pem2inter_decode }, + OSSL_DISPATCH_END +}; + +static int inter2key_decode(void *vctx, OSSL_CORE_BIO *cin, int selection, + OSSL_CALLBACK *data_cb, void *data_cbarg, + OSSL_PASSPHRASE_CALLBACK *pw_cb, void *pw_cbarg) +{ + char buf[1024]; + size_t len; + unsigned long seed; + const char *label; + char *end; + struct test_key_st *key; + OSSL_PARAM params[4]; + int ok; + + if (!read_core_bio(vctx, cin, buf, sizeof(buf), &len)) + return 0; + + /* Data that doesn't parse simply isn't ours; no fatal error */ + if (strncmp(buf, INTER_PREFIX, sizeof(INTER_PREFIX) - 1) != 0) + return 1; + seed = strtoul(buf + sizeof(INTER_PREFIX) - 1, &end, 10); + if (end == buf + sizeof(INTER_PREFIX) - 1 || *end != ':') + return 1; + label = end + 1; + + if ((key = OPENSSL_zalloc(sizeof(*key))) == NULL) + return 0; + key->seed = (unsigned int)seed; + OPENSSL_strlcpy(key->label, label, sizeof(key->label)); + + params[0] = OSSL_PARAM_construct_octet_string(OSSL_OBJECT_PARAM_REFERENCE, + &key, sizeof(key)); + params[1] = OSSL_PARAM_construct_utf8_string(OSSL_OBJECT_PARAM_DATA_TYPE, + (char *)KEY_NAME, 0); + params[2] = OSSL_PARAM_construct_utf8_string(OSSL_OBJECT_PARAM_DATA_STRUCTURE, + (char *)STRUCTURE_NAME, 0); + params[3] = OSSL_PARAM_construct_end(); + + ok = data_cb(params, data_cbarg); + OPENSSL_free(key); + return ok; +} + +static int key_export_object(void *vctx, const void *objref, size_t objref_sz, + OSSL_CALLBACK *export_cb, void *export_cbarg) +{ + struct test_key_st *key; + OSSL_PARAM params[3]; + + if (objref_sz != sizeof(key)) + return 0; + memcpy(&key, objref, sizeof(key)); + + params[0] = OSSL_PARAM_construct_uint("seed", &key->seed); + params[1] = OSSL_PARAM_construct_utf8_string("label", key->label, 0); + params[2] = OSSL_PARAM_construct_end(); + + return export_cb(params, export_cbarg); +} + +static const OSSL_DISPATCH inter2key_decoder_functions[] = { + { OSSL_FUNC_DECODER_NEWCTX, (void (*)(void))codec_newctx }, + { OSSL_FUNC_DECODER_FREECTX, (void (*)(void))codec_freectx }, + { OSSL_FUNC_DECODER_DECODE, (void (*)(void))inter2key_decode }, + { OSSL_FUNC_DECODER_EXPORT_OBJECT, (void (*)(void))key_export_object }, + OSSL_DISPATCH_END +}; + +static const OSSL_ALGORITHM test_decoders[] = { + { KEY_NAME, "provider=apitest,input=" INTER_NAME ",structure=" STRUCTURE_NAME, + inter2key_decoder_functions }, + { INTER_NAME, "provider=apitest,input=" PEM_NAME, + pem2inter_decoder_functions }, + { NULL, NULL, NULL } +}; + +static const OSSL_ALGORITHM *apitest_query(void *provctx, int operation_id, + int *no_cache) +{ + *no_cache = 0; + switch (operation_id) { + case OSSL_OP_ENCODER: + return test_encoders; + case OSSL_OP_DECODER: + return test_decoders; + } + return NULL; +} + +static const OSSL_DISPATCH apitest_dispatch_table[] = { + { OSSL_FUNC_PROVIDER_QUERY_OPERATION, (void (*)(void))apitest_query }, + { OSSL_FUNC_PROVIDER_TEARDOWN, (void (*)(void))OSSL_LIB_CTX_free }, + OSSL_DISPATCH_END +}; + +static int apitest_provider_init(const OSSL_CORE_HANDLE *handle, + const OSSL_DISPATCH *in, + const OSSL_DISPATCH **out, void **provctx) +{ + OSSL_LIB_CTX *libctx = OSSL_LIB_CTX_new_child(handle, in); + + if (libctx == NULL) + return 0; + *provctx = libctx; + *out = apitest_dispatch_table; + return 1; +} + +/* + * The encoder/decoder context callbacks used by the tests + */ + +struct enc_construct_data_st { + const struct test_key_st *key; + int fail_construct; + int cleanup_calls; +}; + +static const void *enc_construct(OSSL_ENCODER_INSTANCE *encoder_inst, + void *arg) +{ + struct enc_construct_data_st *data = arg; + + if (data->fail_construct) + return NULL; + /* The constructor gets the deepest matching encoder instance */ + if (!TEST_true(OSSL_ENCODER_is_a( + OSSL_ENCODER_INSTANCE_get_encoder(encoder_inst), KEY_NAME)) + || !TEST_ptr(OSSL_ENCODER_INSTANCE_get_encoder_ctx(encoder_inst)) + || !TEST_str_eq(OSSL_ENCODER_INSTANCE_get_output_type(encoder_inst), + INTER_NAME) + || !TEST_str_eq(OSSL_ENCODER_INSTANCE_get_output_structure( + encoder_inst), + STRUCTURE_NAME)) + return NULL; + return data->key; +} + +static void enc_cleanup(void *arg) +{ + struct enc_construct_data_st *data = arg; + + data->cleanup_calls++; +} + +struct dec_construct_data_st { + unsigned int seed; + char label[64]; + int constructed; + int construct_calls; + int cleanup_calls; +}; + +static int dec_export_cb(const OSSL_PARAM params[], void *arg) +{ + struct dec_construct_data_st *data = arg; + const OSSL_PARAM *p; + char *label = data->label; + + if (!TEST_ptr(p = OSSL_PARAM_locate_const(params, "seed")) + || !TEST_true(OSSL_PARAM_get_uint(p, &data->seed)) + || !TEST_ptr(p = OSSL_PARAM_locate_const(params, "label")) + || !TEST_true(OSSL_PARAM_get_utf8_string(p, &label, + sizeof(data->label)))) + return 0; + data->constructed = 1; + return 1; +} + +static int dec_construct(OSSL_DECODER_INSTANCE *decoder_inst, + const OSSL_PARAM *params, void *arg) +{ + struct dec_construct_data_st *data = arg; + const OSSL_PARAM *p; + int was_set = 0; + + data->construct_calls++; + + p = OSSL_PARAM_locate_const(params, OSSL_OBJECT_PARAM_REFERENCE); + if (p == NULL) { + /* + * An intermediate decoding result (from the "inter" decoder); + * report it as not constructible so that processing continues + * with the next decoder in the chain. + */ + return 0; + } + + if (!TEST_true(OSSL_DECODER_is_a( + OSSL_DECODER_INSTANCE_get_decoder(decoder_inst), KEY_NAME)) + || !TEST_ptr(OSSL_DECODER_INSTANCE_get_decoder_ctx(decoder_inst)) + || !TEST_str_eq(OSSL_DECODER_INSTANCE_get_input_type(decoder_inst), + INTER_NAME) + || !TEST_str_eq(OSSL_DECODER_INSTANCE_get_input_structure(decoder_inst, + &was_set), + STRUCTURE_NAME)) + return 0; + if (!TEST_int_eq(p->data_type, OSSL_PARAM_OCTET_STRING)) + return 0; + + return OSSL_DECODER_export(decoder_inst, p->data, p->data_size, + dec_export_cb, data); +} + +static void dec_cleanup(void *arg) +{ + struct dec_construct_data_st *data = arg; + + data->cleanup_calls++; +} + +/* + * Test helpers + */ + +/* + * Create an encoder context with the "TEST-KEY" -> "inter" -> "pem" encoder + * chain set up. The construct data is allocated here because + * OSSL_ENCODER_CTX_free() frees it; |*cdata_out| is an alias that stays + * valid for as long as the returned context lives. + */ +static OSSL_ENCODER_CTX *make_encoder_ctx(const struct test_key_st *key, + const char *structure, + struct enc_construct_data_st **cdata_out) +{ + OSSL_ENCODER_CTX *ctx = NULL; + OSSL_ENCODER *e_key = NULL, *e_inter = NULL; + struct enc_construct_data_st *cdata = NULL; + int ok = 0; + + if (!TEST_ptr(ctx = OSSL_ENCODER_CTX_new()) + || !TEST_ptr(cdata = OPENSSL_zalloc(sizeof(*cdata))) + || !TEST_true(OSSL_ENCODER_CTX_set_construct_data(ctx, cdata))) + goto end; + cdata->key = key; + *cdata_out = cdata; + cdata = NULL; /* Owned by ctx now */ + + if (!TEST_ptr(e_key = OSSL_ENCODER_fetch(testctx, KEY_NAME, NULL)) + || !TEST_ptr(e_inter = OSSL_ENCODER_fetch(testctx, INTER_NAME, NULL))) + goto end; + + /* + * The encoder chain is walked from the last added encoder towards the + * first one, so the deepest encoder must be added first. + */ + if (!TEST_true(OSSL_ENCODER_CTX_add_encoder(ctx, e_key)) + || !TEST_true(OSSL_ENCODER_CTX_add_encoder(ctx, e_inter)) + || !TEST_int_eq(OSSL_ENCODER_CTX_get_num_encoders(ctx), 2) + || !TEST_true(OSSL_ENCODER_CTX_add_extra(ctx, testctx, NULL)) + || !TEST_true(OSSL_ENCODER_CTX_set_output_type(ctx, PEM_NAME)) + || (structure != NULL + && !TEST_true(OSSL_ENCODER_CTX_set_output_structure(ctx, + structure))) + || !TEST_true(OSSL_ENCODER_CTX_set_selection(ctx, TEST_SELECTION)) + || !TEST_true(OSSL_ENCODER_CTX_set_construct(ctx, enc_construct)) + || !TEST_true(OSSL_ENCODER_CTX_set_cleanup(ctx, enc_cleanup))) + goto end; + ok = 1; +end: + OSSL_ENCODER_free(e_key); + OSSL_ENCODER_free(e_inter); + OPENSSL_free(cdata); + if (!ok) { + OSSL_ENCODER_CTX_free(ctx); + ctx = NULL; + } + return ctx; +} + +static OSSL_DECODER_CTX *make_decoder_ctx(struct dec_construct_data_st *cdata) +{ + OSSL_DECODER_CTX *ctx = NULL; + OSSL_DECODER *d_key = NULL, *d_inter = NULL; + int ok = 0; + + if (!TEST_ptr(ctx = OSSL_DECODER_CTX_new()) + || !TEST_int_eq(OSSL_DECODER_CTX_get_num_decoders(ctx), 0) + || !TEST_ptr(d_key = OSSL_DECODER_fetch(testctx, KEY_NAME, NULL)) + || !TEST_ptr(d_inter = OSSL_DECODER_fetch(testctx, INTER_NAME, NULL))) + goto end; + + /* + * The decoder chain is walked from the last added decoder towards the + * first one, so the final decoder must be added first. + */ + if (!TEST_true(OSSL_DECODER_CTX_add_decoder(ctx, d_key)) + || !TEST_true(OSSL_DECODER_CTX_add_decoder(ctx, d_inter)) + || !TEST_int_eq(OSSL_DECODER_CTX_get_num_decoders(ctx), 2) + || !TEST_true(OSSL_DECODER_CTX_set_input_type(ctx, PEM_NAME)) + || !TEST_true(OSSL_DECODER_CTX_set_input_structure(ctx, + STRUCTURE_NAME)) + || !TEST_true(OSSL_DECODER_CTX_set_selection(ctx, TEST_SELECTION)) + || !TEST_true(OSSL_DECODER_CTX_set_construct(ctx, dec_construct)) + || !TEST_true(OSSL_DECODER_CTX_set_construct_data(ctx, cdata)) + || !TEST_true(OSSL_DECODER_CTX_set_cleanup(ctx, dec_cleanup))) + goto end; + ok = 1; +end: + OSSL_DECODER_free(d_key); + OSSL_DECODER_free(d_inter); + if (!ok) { + OSSL_DECODER_CTX_free(ctx); + ctx = NULL; + } + return ctx; +} + +static int make_expected(const struct test_key_st *key, char *buf, + size_t bufsz, size_t *len) +{ + int n = BIO_snprintf(buf, bufsz, + PEM_HEADER INTER_PREFIX "%u:%s" PEM_FOOTER, key->seed, key->label); + + if (n <= 0) + return 0; + *len = (size_t)n; + return 1; +} + +/* + * Encode a test key through the two-stage encoder chain into a BIO and + * check the result. + */ +static int test_chain_encode_to_bio(void) +{ + const struct test_key_st key = { 12345, "chained" }; + struct enc_construct_data_st *cdata = NULL; + OSSL_ENCODER_CTX *ectx = NULL; + BIO *out = NULL; + BUF_MEM *mem = NULL; + char expected[256]; + size_t expected_len = 0; + int ok = 0; + + if (!TEST_true(make_expected(&key, expected, sizeof(expected), &expected_len)) + || !TEST_ptr(ectx = make_encoder_ctx(&key, STRUCTURE_NAME, &cdata))) + goto end; + + if (!TEST_ptr(out = BIO_new(BIO_s_mem())) + || !TEST_true(OSSL_ENCODER_to_bio(ectx, out)) + || !TEST_true(BIO_get_mem_ptr(out, &mem) > 0) + || !TEST_mem_eq(mem->data, mem->length, expected, expected_len)) + goto end; + + /* The cleanup callback must have been called exactly once */ + if (!TEST_int_eq(cdata->cleanup_calls, 1)) + goto end; + + ok = 1; +end: + BIO_free(out); + OSSL_ENCODER_CTX_free(ectx); + return ok; +} + +/* Exercise the various OSSL_ENCODER_to_data() modes of operation */ +static int test_chain_encode_to_data(void) +{ + const struct test_key_st key = { 999, "to-data" }; + struct enc_construct_data_st *cdata = NULL; + OSSL_ENCODER_CTX *ectx = NULL; + unsigned char *buf = NULL, *p, *allocated = NULL; + char expected[256]; + size_t expected_len = 0, len, sz; + int ok = 0; + + if (!TEST_true(make_expected(&key, expected, sizeof(expected), &expected_len)) + /* No output structure this time; the chain works without one too */ + || !TEST_ptr(ectx = make_encoder_ctx(&key, NULL, &cdata))) + goto end; + + /* A NULL pdata_len is an error */ + if (!TEST_false(OSSL_ENCODER_to_data(ectx, NULL, NULL))) + goto end; + + /* Size query */ + len = 0; + if (!TEST_true(OSSL_ENCODER_to_data(ectx, NULL, &len)) + || !TEST_size_t_eq(len, expected_len)) + goto end; + + /* Encoding into a pre-allocated buffer of exactly the right size */ + if (!TEST_ptr(buf = OPENSSL_malloc(len))) + goto end; + p = buf; + sz = len; + if (!TEST_true(OSSL_ENCODER_to_data(ectx, &p, &sz)) + || !TEST_size_t_eq(sz, 0) + || !TEST_ptr_eq(p, buf + expected_len) + || !TEST_mem_eq(buf, expected_len, expected, expected_len)) + goto end; + + /* A too small pre-allocated buffer is an error */ + p = buf; + sz = expected_len - 1; + if (!TEST_false(OSSL_ENCODER_to_data(ectx, &p, &sz))) + goto end; + + /* With *pdata == NULL, the buffer is allocated for us */ + sz = 0; + if (!TEST_true(OSSL_ENCODER_to_data(ectx, &allocated, &sz)) + || !TEST_mem_eq(allocated, sz, expected, expected_len)) + goto end; + + ok = 1; +end: + OPENSSL_free(buf); + OPENSSL_free(allocated); + OSSL_ENCODER_CTX_free(ectx); + return ok; +} + +/* Decode through the decoder chain and check the constructed object */ +static int test_chain_decode_from_data(void) +{ + const struct test_key_st key = { 4711, "decode" }; + struct dec_construct_data_st dcdata; + OSSL_DECODER_CTX *dctx = NULL; + char encoded[256]; + size_t encoded_len = 0; + const unsigned char *pdata; + size_t pdata_len; + int ok = 0; + + memset(&dcdata, 0, sizeof(dcdata)); + if (!TEST_true(make_expected(&key, encoded, sizeof(encoded), &encoded_len)) + || !TEST_ptr(dctx = make_decoder_ctx(&dcdata))) + goto end; + + pdata = (unsigned char *)encoded; + pdata_len = encoded_len; + if (!TEST_true(OSSL_DECODER_from_data(dctx, &pdata, &pdata_len)) + || !TEST_size_t_eq(pdata_len, 0) + || !TEST_true(dcdata.constructed) + || !TEST_uint_eq(dcdata.seed, key.seed) + || !TEST_str_eq(dcdata.label, key.label) + /* Once for the intermediate decoder, once for the final one */ + || !TEST_int_eq(dcdata.construct_calls, 2)) + goto end; + + OSSL_DECODER_CTX_free(dctx); + dctx = NULL; + /* The cleanup callback is called when the context is freed */ + if (!TEST_int_eq(dcdata.cleanup_calls, 1)) + goto end; + + ok = 1; +end: + OSSL_DECODER_CTX_free(dctx); + return ok; +} + +#ifndef OPENSSL_NO_STDIO +/* Round-trip via OSSL_ENCODER_to_fp() and OSSL_DECODER_from_fp() */ +static int test_chain_roundtrip_fp(void) +{ + const struct test_key_st key = { 31337, "stdio" }; + struct enc_construct_data_st *ecdata = NULL; + struct dec_construct_data_st dcdata; + OSSL_ENCODER_CTX *ectx = NULL; + OSSL_DECODER_CTX *dctx = NULL; + const char *fname = "endecode_api_test.tmp"; + FILE *fp = NULL; + int ok = 0; + + memset(&dcdata, 0, sizeof(dcdata)); + if (!TEST_ptr(ectx = make_encoder_ctx(&key, STRUCTURE_NAME, &ecdata)) + || !TEST_ptr(dctx = make_decoder_ctx(&dcdata))) + goto end; + + if (!TEST_ptr(fp = fopen(fname, "w+b")) + || !TEST_true(OSSL_ENCODER_to_fp(ectx, fp))) + goto end; + rewind(fp); + if (!TEST_true(OSSL_DECODER_from_fp(dctx, fp)) + || !TEST_true(dcdata.constructed) + || !TEST_uint_eq(dcdata.seed, key.seed) + || !TEST_str_eq(dcdata.label, key.label)) + goto end; + + ok = 1; +end: + if (fp != NULL) { + fclose(fp); + remove(fname); + } + OSSL_ENCODER_CTX_free(ectx); + OSSL_DECODER_CTX_free(dctx); + return ok; +} +#endif + +/* + * A source BIO that doesn't support BIO_tell(), forcing + * OSSL_DECODER_from_bio() to wrap it with a read buffer filter BIO. + */ + +struct nonseek_state_st { + const unsigned char *data; + size_t len; + size_t pos; +}; + +static int nonseek_read(BIO *b, char *out, int outl) +{ + struct nonseek_state_st *st = BIO_get_data(b); + size_t n = st->len - st->pos; + + if (n == 0) + return 0; + if (n > (size_t)outl) + n = (size_t)outl; + memcpy(out, st->data + st->pos, n); + st->pos += n; + return (int)n; +} + +static long nonseek_ctrl(BIO *b, int cmd, long num, void *ptr) +{ + struct nonseek_state_st *st = BIO_get_data(b); + + switch (cmd) { + case BIO_CTRL_EOF: + return st->pos >= st->len; + case BIO_C_FILE_TELL: + return -1; + } + return 0; +} + +static BIO_METHOD *nonseek_meth = NULL; + +static int test_decode_non_seekable_bio(void) +{ + const struct test_key_st key = { 2222, "nonseek" }; + struct dec_construct_data_st dcdata; + OSSL_DECODER_CTX *dctx = NULL; + struct nonseek_state_st st; + BIO *in = NULL; + char encoded[256]; + size_t encoded_len = 0; + int ok = 0; + + memset(&dcdata, 0, sizeof(dcdata)); + if (!TEST_true(make_expected(&key, encoded, sizeof(encoded), &encoded_len)) + || !TEST_ptr(dctx = make_decoder_ctx(&dcdata))) + goto end; + + st.data = (unsigned char *)encoded; + st.len = encoded_len; + st.pos = 0; + if (!TEST_ptr(in = BIO_new(nonseek_meth))) + goto end; + BIO_set_data(in, &st); + BIO_set_init(in, 1); + + if (!TEST_int_lt(BIO_tell(in), 0) + || !TEST_true(OSSL_DECODER_from_bio(dctx, in)) + || !TEST_true(dcdata.constructed) + || !TEST_uint_eq(dcdata.seed, key.seed) + || !TEST_str_eq(dcdata.label, key.label)) + goto end; + + ok = 1; +end: + BIO_free(in); + OSSL_DECODER_CTX_free(dctx); + return ok; +} + +/* Various ways an encoding attempt can fail */ +static int test_encode_failures(void) +{ + const struct test_key_st key = { 1, "fail" }; + const struct test_key_st failkey = { FAIL_SEED, "fail" }; + struct enc_construct_data_st *cdata = NULL; + OSSL_ENCODER_CTX *ectx = NULL; + OSSL_ENCODER *encoder = NULL; + BIO *out = NULL; + int ok = 0; + + if (!TEST_ptr(out = BIO_new(BIO_s_mem()))) + goto end; + + /* An empty encoder context finds no encoders */ + if (!TEST_ptr(ectx = OSSL_ENCODER_CTX_new()) + || !TEST_false(OSSL_ENCODER_to_bio(ectx, out))) + goto end; + OSSL_ENCODER_CTX_free(ectx); + ectx = NULL; + + /* A context without a constructor cannot encode */ + if (!TEST_ptr(ectx = OSSL_ENCODER_CTX_new()) + || !TEST_ptr(encoder = OSSL_ENCODER_fetch(testctx, INTER_NAME, NULL)) + || !TEST_true(OSSL_ENCODER_CTX_add_encoder(ectx, encoder)) + || !TEST_false(OSSL_ENCODER_to_bio(ectx, out))) + goto end; + OSSL_ENCODER_CTX_free(ectx); + ectx = NULL; + + /* An output structure that no encoder implements */ + if (!TEST_ptr(ectx = make_encoder_ctx(&key, "unknown-structure", &cdata)) + || !TEST_false(OSSL_ENCODER_to_bio(ectx, out))) + goto end; + OSSL_ENCODER_CTX_free(ectx); + ectx = NULL; + + /* A failing constructor makes the encoding fail */ + if (!TEST_ptr(ectx = make_encoder_ctx(&key, STRUCTURE_NAME, &cdata))) + goto end; + cdata->fail_construct = 1; + if (!TEST_false(OSSL_ENCODER_to_bio(ectx, out)) + || !TEST_int_eq(cdata->cleanup_calls, 0)) + goto end; + OSSL_ENCODER_CTX_free(ectx); + ectx = NULL; + + /* A failing encoder implementation makes the encoding fail */ + if (!TEST_ptr(ectx = make_encoder_ctx(&failkey, STRUCTURE_NAME, &cdata)) + || !TEST_false(OSSL_ENCODER_to_bio(ectx, out)) + /* The constructor succeeded, so the cleanup must have been called */ + || !TEST_int_eq(cdata->cleanup_calls, 1)) + goto end; + + ok = 1; +end: + OSSL_ENCODER_free(encoder); + OSSL_ENCODER_CTX_free(ectx); + BIO_free(out); + return ok; +} + +/* Undecodable input must fail without constructing anything */ +static int test_decode_garbage(void) +{ + struct dec_construct_data_st dcdata; + OSSL_DECODER_CTX *dctx = NULL; + static const char garbage[] = "this is not an encoded test key"; + const unsigned char *pdata = (const unsigned char *)garbage; + size_t pdata_len = sizeof(garbage) - 1; + int ok = 0; + + memset(&dcdata, 0, sizeof(dcdata)); + if (!TEST_ptr(dctx = make_decoder_ctx(&dcdata))) + goto end; + + if (!TEST_false(OSSL_DECODER_from_data(dctx, &pdata, &pdata_len)) + || !TEST_false(dcdata.constructed)) + goto end; + + ok = 1; +end: + OSSL_DECODER_CTX_free(dctx); + return ok; +} + +/* NULL argument checks on the encoder API */ +static int test_encoder_null_args(void) +{ + OSSL_ENCODER_CTX *ectx = NULL; + OSSL_ENCODER *encoder = NULL; + unsigned char *pdata = NULL; + int ok = 0; + + if (!TEST_ptr(ectx = OSSL_ENCODER_CTX_new()) + || !TEST_ptr(encoder = OSSL_ENCODER_fetch(testctx, KEY_NAME, NULL))) + goto end; + + if (!TEST_false(OSSL_ENCODER_CTX_set_selection(NULL, TEST_SELECTION)) + || !TEST_false(OSSL_ENCODER_CTX_set_selection(ectx, 0)) + || !TEST_false(OSSL_ENCODER_CTX_set_output_type(NULL, PEM_NAME)) + || !TEST_false(OSSL_ENCODER_CTX_set_output_type(ectx, NULL)) + || !TEST_false(OSSL_ENCODER_CTX_set_output_structure(NULL, STRUCTURE_NAME)) + || !TEST_false(OSSL_ENCODER_CTX_set_output_structure(ectx, NULL)) + || !TEST_false(OSSL_ENCODER_CTX_add_encoder(NULL, encoder)) + || !TEST_false(OSSL_ENCODER_CTX_add_encoder(ectx, NULL)) + || !TEST_false(OSSL_ENCODER_CTX_add_extra(NULL, testctx, NULL)) + || !TEST_false(OSSL_ENCODER_CTX_set_construct(NULL, enc_construct)) + || !TEST_false(OSSL_ENCODER_CTX_set_construct_data(NULL, NULL)) + || !TEST_false(OSSL_ENCODER_CTX_set_cleanup(NULL, enc_cleanup)) + || !TEST_int_eq(OSSL_ENCODER_CTX_get_num_encoders(NULL), 0) + || !TEST_false(OSSL_ENCODER_to_data(ectx, &pdata, NULL))) + goto end; + + if (!TEST_ptr_null(OSSL_ENCODER_INSTANCE_get_encoder(NULL)) + || !TEST_ptr_null(OSSL_ENCODER_INSTANCE_get_encoder_ctx(NULL)) + || !TEST_ptr_null(OSSL_ENCODER_INSTANCE_get_output_type(NULL)) + || !TEST_ptr_null(OSSL_ENCODER_INSTANCE_get_output_structure(NULL))) + goto end; + + ok = 1; +end: + OSSL_ENCODER_free(encoder); + OSSL_ENCODER_CTX_free(ectx); + return ok; +} + +/* NULL argument checks on the decoder API */ +static int test_decoder_null_args(void) +{ + OSSL_DECODER_CTX *dctx = NULL; + OSSL_DECODER *decoder = NULL; + const unsigned char *pdata = NULL; + unsigned char buf[16]; + const unsigned char *pbuf = buf; + size_t pdata_len = 0; + int was_set = 0; + int ok = 0; + + if (!TEST_ptr(dctx = OSSL_DECODER_CTX_new()) + || !TEST_ptr(decoder = OSSL_DECODER_fetch(testctx, KEY_NAME, NULL))) + goto end; + + if (!TEST_false(OSSL_DECODER_CTX_set_selection(NULL, TEST_SELECTION)) + || !TEST_false(OSSL_DECODER_CTX_set_input_type(NULL, PEM_NAME)) + || !TEST_false(OSSL_DECODER_CTX_set_input_structure(NULL, STRUCTURE_NAME)) + || !TEST_false(OSSL_DECODER_CTX_add_decoder(NULL, decoder)) + || !TEST_false(OSSL_DECODER_CTX_add_decoder(dctx, NULL)) + || !TEST_false(OSSL_DECODER_CTX_add_extra(NULL, testctx, NULL)) + || !TEST_false(OSSL_DECODER_CTX_set_construct(NULL, dec_construct)) + || !TEST_false(OSSL_DECODER_CTX_set_construct_data(NULL, NULL)) + || !TEST_false(OSSL_DECODER_CTX_set_cleanup(NULL, dec_cleanup)) + || !TEST_int_eq(OSSL_DECODER_CTX_get_num_decoders(NULL), 0) + || !TEST_false(OSSL_DECODER_from_data(dctx, NULL, &pdata_len)) + || !TEST_false(OSSL_DECODER_from_data(dctx, &pdata, &pdata_len)) + || !TEST_false(OSSL_DECODER_from_data(dctx, &pbuf, NULL)) + || !TEST_false(OSSL_DECODER_export(NULL, NULL, 0, NULL, NULL))) + goto end; + + if (!TEST_true(OSSL_DECODER_CTX_get_construct(NULL) == NULL) + || !TEST_ptr_null(OSSL_DECODER_CTX_get_construct_data(NULL)) + || !TEST_true(OSSL_DECODER_CTX_get_cleanup(NULL) == NULL) + || !TEST_ptr_null(OSSL_DECODER_INSTANCE_get_decoder(NULL)) + || !TEST_ptr_null(OSSL_DECODER_INSTANCE_get_decoder_ctx(NULL)) + || !TEST_ptr_null(OSSL_DECODER_INSTANCE_get_input_type(NULL)) + || !TEST_ptr_null(OSSL_DECODER_INSTANCE_get_input_structure(NULL, &was_set))) + goto end; + + ok = 1; +end: + OSSL_DECODER_free(decoder); + OSSL_DECODER_CTX_free(dctx); + return ok; +} + +int setup_tests(void) +{ + if (!TEST_ptr(testctx = OSSL_LIB_CTX_new()) + || !TEST_true(OSSL_PROVIDER_add_builtin(testctx, "apitest-prov", + apitest_provider_init)) + || !TEST_ptr(testprov = OSSL_PROVIDER_load(testctx, "apitest-prov"))) + return 0; + + if (!TEST_ptr(nonseek_meth = BIO_meth_new(BIO_TYPE_SOURCE_SINK, + "non-seekable source")) + || !TEST_true(BIO_meth_set_read(nonseek_meth, nonseek_read)) + || !TEST_true(BIO_meth_set_ctrl(nonseek_meth, nonseek_ctrl))) + return 0; + + ADD_TEST(test_chain_encode_to_bio); + ADD_TEST(test_chain_encode_to_data); + ADD_TEST(test_chain_decode_from_data); +#ifndef OPENSSL_NO_STDIO + ADD_TEST(test_chain_roundtrip_fp); +#endif + ADD_TEST(test_decode_non_seekable_bio); + ADD_TEST(test_encode_failures); + ADD_TEST(test_decode_garbage); + ADD_TEST(test_encoder_null_args); + ADD_TEST(test_decoder_null_args); + return 1; +} + +void cleanup_tests(void) +{ + BIO_meth_free(nonseek_meth); + OSSL_PROVIDER_unload(testprov); + OSSL_LIB_CTX_free(testctx); +} |
