| /* |
| * 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; |
| |
| /* Likewise the data type, naming the encoder that produced the data */ |
| p = OSSL_PARAM_locate_const(obj_abstract, OSSL_OBJECT_PARAM_DATA_TYPE); |
| if (p == NULL || p->data_type != OSSL_PARAM_UTF8_STRING |
| || p->data == NULL || p->data_size != strlen(KEY_NAME) |
| || memcmp(p->data, KEY_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 = 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); |
| } |