blob: b6f3be4cdb76979c21451bd2151f869c33cf5a0f [file] [edit]
/*
* 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
*/
/*
* Tests of BIO_new_NDEF() against a minimal streamable ASN.1 structure,
* independent of CMS and PKCS7. The structure's callback implements the
* stream operations described in ASN1_aux_cb(3), and can be made to
* misbehave in ways a third-party callback plausibly would, to pin down
* how BIO_new_NDEF() holds up. See also BIO_new_NDEF(3).
*/
#include <string.h>
#include <openssl/asn1t.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include "testutil.h"
typedef struct {
ASN1_OCTET_STRING *content;
} NDEF_TEST_VAL;
DECLARE_ASN1_FUNCTIONS(NDEF_TEST_VAL)
/* How ndef_test_cb() behaves; a well written callback uses CB_NORMAL */
enum {
CB_NORMAL,
CB_FAIL_STREAM_PRE, /* fail ASN1_OP_STREAM_PRE */
CB_NO_CONTENT, /* leave ASN1_OP_GET0_STREAM_CONTENT unhandled */
CB_CONTENT_THEN_FAIL /* store the content string, then report failure */
};
static int cb_mode = CB_NORMAL;
static int ndef_test_cb(int operation, ASN1_VALUE **pval,
const ASN1_ITEM *it, void *exarg)
{
NDEF_TEST_VAL *v = (NDEF_TEST_VAL *)*pval;
ASN1_STREAM_ARG *sarg = exarg;
switch (operation) {
case ASN1_OP_STREAM_PRE:
if (cb_mode == CB_FAIL_STREAM_PRE || v->content == NULL)
return 0;
/* Nothing to digest or encrypt: stream straight through */
sarg->ndef_bio = sarg->out;
break;
case ASN1_OP_GET0_STREAM_CONTENT:
if (cb_mode == CB_NO_CONTENT)
break;
*(ASN1_STRING **)exarg = v->content;
if (cb_mode == CB_CONTENT_THEN_FAIL)
return 0;
break;
}
return 1;
}
ASN1_NDEF_SEQUENCE_cb(NDEF_TEST_VAL, ndef_test_cb) = {
ASN1_SIMPLE(NDEF_TEST_VAL, content, ASN1_OCTET_STRING_NDEF)
} ASN1_NDEF_SEQUENCE_END_cb(NDEF_TEST_VAL, NDEF_TEST_VAL)
IMPLEMENT_ASN1_FUNCTIONS(NDEF_TEST_VAL)
static const unsigned char payload[] = "0123456789abcdefghijklmnopqrstuvwxyz";
#define PAYLOAD_LEN ((int)sizeof(payload) - 1)
#define PAYLOAD_SPLIT 10
/* Free the filter BIOs down to, but not including, out */
static void free_filter_bios(BIO *bio, BIO *out)
{
while (bio != NULL && bio != out) {
BIO *next = BIO_pop(bio);
BIO_free(bio);
bio = next;
}
}
/* Stream a payload in two writes and check that it round-trips */
static int test_ndef_stream_ok(void)
{
NDEF_TEST_VAL *v = NULL, *parsed = NULL;
BIO *out = NULL, *bio = NULL;
unsigned char *encoded = NULL;
const unsigned char *der;
long encoded_len;
int ret = 0;
cb_mode = CB_NORMAL;
if (!TEST_ptr(v = NDEF_TEST_VAL_new())
|| !TEST_ptr(out = BIO_new(BIO_s_mem()))
|| !TEST_ptr(bio = BIO_new_NDEF(out, (ASN1_VALUE *)v,
ASN1_ITEM_rptr(NDEF_TEST_VAL))))
goto err;
if (!TEST_int_eq(BIO_write(bio, payload, PAYLOAD_SPLIT), PAYLOAD_SPLIT)
|| !TEST_int_eq(BIO_write(bio, payload + PAYLOAD_SPLIT,
PAYLOAD_LEN - PAYLOAD_SPLIT),
PAYLOAD_LEN - PAYLOAD_SPLIT)
|| !TEST_int_gt(BIO_flush(bio), 0))
goto err;
free_filter_bios(bio, out);
bio = NULL;
encoded_len = BIO_get_mem_data(out, &encoded);
if (!TEST_long_gt(encoded_len, 0))
goto err;
der = encoded;
if (!TEST_ptr(parsed = d2i_NDEF_TEST_VAL(NULL, &der, encoded_len))
|| !TEST_mem_eq(ASN1_STRING_get0_data(parsed->content),
ASN1_STRING_get_length(parsed->content),
payload, PAYLOAD_LEN))
goto err;
ret = 1;
err:
free_filter_bios(bio, out);
BIO_free(out);
NDEF_TEST_VAL_free(parsed);
NDEF_TEST_VAL_free(v);
return ret;
}
/* An item with no callback cannot stream; out must stay usable */
static int test_ndef_not_supported(void)
{
ASN1_OCTET_STRING *os = NULL;
BIO *out = NULL;
int ret = 0;
if (!TEST_ptr(os = ASN1_OCTET_STRING_new())
|| !TEST_ptr(out = BIO_new(BIO_s_mem())))
goto err;
ERR_clear_error();
if (!TEST_ptr_null(BIO_new_NDEF(out, (ASN1_VALUE *)os,
ASN1_ITEM_rptr(ASN1_OCTET_STRING)))
|| !TEST_int_eq(ERR_GET_REASON(ERR_get_error()),
ASN1_R_STREAMING_NOT_SUPPORTED))
goto err;
/* On failure out remains owned by the caller and usable */
if (!TEST_int_eq(BIO_write(out, "x", 1), 1))
goto err;
ret = 1;
err:
BIO_free(out);
ASN1_OCTET_STRING_free(os);
return ret;
}
/* A failed ASN1_OP_STREAM_PRE must unwind the half-built chain */
static int test_ndef_stream_pre_fails(void)
{
NDEF_TEST_VAL *v = NULL;
BIO *out = NULL;
int ret = 0;
cb_mode = CB_FAIL_STREAM_PRE;
if (!TEST_ptr(v = NDEF_TEST_VAL_new())
|| !TEST_ptr(out = BIO_new(BIO_s_mem())))
goto err;
if (!TEST_ptr_null(BIO_new_NDEF(out, (ASN1_VALUE *)v,
ASN1_ITEM_rptr(NDEF_TEST_VAL))))
goto err;
/* On failure out remains owned by the caller and usable */
if (!TEST_int_eq(BIO_write(out, "x", 1), 1))
goto err;
ret = 1;
err:
cb_mode = CB_NORMAL;
BIO_free(out);
NDEF_TEST_VAL_free(v);
return ret;
}
/*
* A callback that does not provide the content string, either by leaving
* ASN1_OP_GET0_STREAM_CONTENT unhandled (idx 0, the shape of a callback written
* before that operation existed) or by storing the string and then
* reporting failure (idx 1). Setting up the BIO chain still succeeds,
* because the stream callbacks have already modified it, but the first
* write must fail cleanly instead of encoding through a content string
* that the encoder is not recording the content position in.
*/
static int test_ndef_content_missing(int idx)
{
NDEF_TEST_VAL *v = NULL;
BIO *out = NULL, *bio = NULL;
int ret = 0;
cb_mode = idx == 0 ? CB_NO_CONTENT : CB_CONTENT_THEN_FAIL;
/* Content with data present makes a stale-position mistake detectable */
if (!TEST_ptr(v = NDEF_TEST_VAL_new())
|| !TEST_true(ASN1_OCTET_STRING_set(v->content, payload, PAYLOAD_LEN))
|| !TEST_ptr(out = BIO_new(BIO_s_mem()))
|| !TEST_ptr(bio = BIO_new_NDEF(out, (ASN1_VALUE *)v,
ASN1_ITEM_rptr(NDEF_TEST_VAL))))
goto err;
if (!TEST_int_le(BIO_write(bio, payload, PAYLOAD_LEN), 0))
goto err;
ret = 1;
err:
cb_mode = CB_NORMAL;
free_filter_bios(bio, out);
BIO_free(out);
NDEF_TEST_VAL_free(v);
return ret;
}
int setup_tests(void)
{
ADD_TEST(test_ndef_stream_ok);
ADD_TEST(test_ndef_not_supported);
ADD_TEST(test_ndef_stream_pre_fails);
ADD_ALL_TESTS(test_ndef_content_missing, 2);
return 1;
}