1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
|
/*
* 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;
}
|