/* * 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 #include #include #include #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; }