1/* $OpenBSD: asn1object.c,v 1.14 2024/05/29 17:23:05 tb Exp $ */
2/*
3 * Copyright (c) 2017, 2021, 2022 Joel Sing <jsing@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include <openssl/asn1.h>
19#include <openssl/err.h>
20#include <openssl/objects.h>
21
22#include <err.h>
23#include <stdio.h>
24#include <string.h>
25
26#include "asn1_local.h"
27
28static void
29hexdump(const unsigned char *buf, int len)
30{
31	int i;
32
33	if (len <= 0) {
34		fprintf(stderr, "<negative length %d>\n", len);
35		return;
36	}
37
38	for (i = 1; i <= len; i++)
39		fprintf(stderr, " 0x%02hhx,%s", buf[i - 1], i % 8 ? "" : "\n");
40
41	fprintf(stderr, "\n");
42}
43
44static int
45asn1_compare_bytes(const char *label, const unsigned char *d1, int len1,
46    const unsigned char *d2, int len2)
47{
48	if (len1 != len2) {
49		fprintf(stderr, "FAIL: %s - byte lengths differ "
50		    "(%d != %d)\n", label, len1, len2);
51		fprintf(stderr, "Got:\n");
52		hexdump(d1, len1);
53		fprintf(stderr, "Want:\n");
54		hexdump(d2, len2);
55		return 0;
56	}
57	if (memcmp(d1, d2, len1) != 0) {
58		fprintf(stderr, "FAIL: %s - bytes differ\n", label);
59		fprintf(stderr, "Got:\n");
60		hexdump(d1, len1);
61		fprintf(stderr, "Want:\n");
62		hexdump(d2, len2);
63		return 0;
64	}
65	return 1;
66}
67
68struct asn1_object_test {
69	const char *oid;
70	const char *txt;
71	const uint8_t content[255];
72	size_t content_len;
73	const uint8_t der[255];
74	size_t der_len;
75	int want_error;
76};
77
78struct asn1_object_test asn1_object_tests[] = {
79	{
80		.oid = "2.5",
81		.txt = "directory services (X.500)",
82		.content = {
83			0x55,
84		},
85		.content_len = 1,
86		.der = {
87			0x06, 0x01, 0x55,
88		},
89		.der_len = 3,
90	},
91	{
92		.oid = "2.5.4",
93		.txt = "X509",
94		.content = {
95			0x55, 0x04,
96		},
97		.content_len = 2,
98		.der = {
99			0x06, 0x02, 0x55, 0x04,
100		},
101		.der_len = 4,
102	},
103	{
104		.oid = "2.5.4.10",
105		.txt = "organizationName",
106		.content = {
107			0x55, 0x04, 0x0a,
108		},
109		.content_len = 3,
110		.der = {
111			0x06, 0x03, 0x55, 0x04, 0x0a,
112		},
113		.der_len = 5,
114	},
115	{
116		.oid = "2 5 4 10",
117		.txt = "organizationName",
118		.content = {
119			0x55, 0x04, 0x0a,
120		},
121		.content_len = 3,
122		.der = {
123			0x06, 0x03, 0x55, 0x04, 0x0a,
124		},
125		.der_len = 5,
126	},
127	{
128		.oid = "2.5.0.0",
129		.txt = "2.5.0.0",
130		.content = {
131			0x55, 0x00, 0x00,
132		},
133		.content_len = 3,
134		.der = {
135			0x06, 0x03, 0x55, 0x00, 0x00,
136		},
137		.der_len = 5,
138	},
139	{
140		.oid = "0.0.0.0",
141		.txt = "0.0.0.0",
142		.content = {
143			0x00, 0x00, 0x00,
144		},
145		.content_len = 3,
146		.der = {
147			0x06, 0x03, 0x00, 0x00, 0x00,
148		},
149		.der_len = 5,
150	},
151	{
152		.oid = "1.3.6.1.4.1.11129.2.4.5",
153		.txt = "CT Certificate SCTs",
154		.content = {
155			0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02,
156			0x04, 0x05,
157		},
158		.content_len = 10,
159		.der = {
160			0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6,
161			0x79, 0x02, 0x04, 0x05,
162		},
163		.der_len = 12,
164	},
165	{
166		.oid = "2.00005.0000000000004.10",
167		.want_error = ASN1_R_INVALID_NUMBER,
168	},
169	{
170		.oid = "2..5.4.10",
171		.want_error = ASN1_R_INVALID_NUMBER,
172	},
173	{
174		.oid = "2.5..4.10",
175		.want_error = ASN1_R_INVALID_NUMBER,
176	},
177	{
178		.oid = "2.5.4..10",
179		.want_error = ASN1_R_INVALID_NUMBER,
180	},
181	{
182		.oid = "2.5.4.10.",
183		.want_error = ASN1_R_INVALID_NUMBER,
184	},
185	{
186		.oid = "3.5.4.10",
187		.want_error = ASN1_R_FIRST_NUM_TOO_LARGE,
188	},
189	{
190		.oid = "0.40.4.10",
191		.want_error = ASN1_R_SECOND_NUMBER_TOO_LARGE,
192	},
193	{
194		.oid = "1.40.4.10",
195		.want_error = ASN1_R_SECOND_NUMBER_TOO_LARGE,
196	},
197	{
198		.oid = "2",
199		.want_error = ASN1_R_MISSING_SECOND_NUMBER,
200	},
201	{
202		.oid = "2.5 4.10",
203		.want_error = ASN1_R_INVALID_SEPARATOR,
204	},
205	{
206		.oid = "2,5,4,10",
207		.want_error = ASN1_R_INVALID_SEPARATOR,
208	},
209	{
210		.oid = "2.5,4.10",
211		.want_error = ASN1_R_INVALID_DIGIT,
212	},
213	{
214		.oid = "2a.5.4.10",
215		.want_error = ASN1_R_INVALID_SEPARATOR,
216	},
217	{
218		.oid = "2.5a.4.10",
219		.want_error = ASN1_R_INVALID_DIGIT,
220	},
221};
222
223#define N_ASN1_OBJECT_TESTS \
224    (sizeof(asn1_object_tests) / sizeof(*asn1_object_tests))
225
226static int
227do_asn1_object_test(struct asn1_object_test *aot)
228{
229	ASN1_OBJECT *aobj = NULL;
230	uint8_t buf[1024];
231	const uint8_t *p;
232	uint8_t *der = NULL;
233	uint8_t *q;
234	int err, ret;
235	int failed = 1;
236
237	ERR_clear_error();
238
239	ret = a2d_ASN1_OBJECT(NULL, 0, aot->oid, -1);
240	if (ret < 0 || (size_t)ret != aot->content_len) {
241		fprintf(stderr, "FAIL: a2d_ASN1_OBJECT('%s') = %d, want %zu\n",
242		    aot->oid, ret, aot->content_len);
243		goto failed;
244	}
245	ret = a2d_ASN1_OBJECT(buf, sizeof(buf), aot->oid, -1);
246	if (ret < 0 || (size_t)ret != aot->content_len) {
247		fprintf(stderr, "FAIL: a2d_ASN1_OBJECT('%s') = %d, want %zu\n",
248		    aot->oid, ret, aot->content_len);
249		goto failed;
250	}
251	if (aot->content_len == 0) {
252		err = ERR_peek_error();
253		if (ERR_GET_REASON(err) != aot->want_error) {
254			fprintf(stderr, "FAIL: a2d_ASN1_OBJECT('%s') - got "
255			    "error reason %d, want %d\n", aot->oid,
256			    ERR_GET_REASON(err), aot->want_error);
257			goto failed;
258		}
259		goto done;
260	}
261
262	if (!asn1_compare_bytes("ASN1_OBJECT content", buf, ret, aot->content,
263	    aot->content_len))
264		goto failed;
265
266	p = aot->content;
267	if ((aobj = c2i_ASN1_OBJECT(NULL, &p, aot->content_len)) == NULL) {
268		fprintf(stderr, "FAIL: c2i_ASN1_OBJECT() failed\n");
269		goto failed;
270	}
271
272	q = buf;
273	ret = i2d_ASN1_OBJECT(aobj, &q);
274	if (!asn1_compare_bytes("ASN1_OBJECT DER", buf, ret, aot->der,
275	    aot->der_len))
276		goto failed;
277
278	der = NULL;
279	ret = i2d_ASN1_OBJECT(aobj, &der);
280	if (!asn1_compare_bytes("ASN1_OBJECT DER", der, ret, aot->der,
281	    aot->der_len))
282		goto failed;
283
284	free(der);
285	der = NULL;
286
287	ASN1_OBJECT_free(aobj);
288	aobj = NULL;
289
290	p = aot->der;
291	if ((aobj = d2i_ASN1_OBJECT(NULL, &p, aot->der_len)) == NULL) {
292		fprintf(stderr, "FAIL: d2i_ASN1_OBJECT() failed\n");
293		goto failed;
294	}
295	if (p != aot->der + aot->der_len) {
296		fprintf(stderr, "FAIL: d2i_ASN1_OBJECT() p = %p, want %p\n",
297		    p, aot->der + aot->der_len);
298		goto failed;
299	}
300
301	if (aot->txt != NULL) {
302		ret = i2t_ASN1_OBJECT(buf, sizeof(buf), aobj);
303		if (ret <= 0 || (size_t)ret >= sizeof(buf)) {
304			fprintf(stderr, "FAIL: i2t_ASN1_OBJECT() failed\n");
305			goto failed;
306		}
307		if (strcmp(aot->txt, buf) != 0) {
308			fprintf(stderr, "FAIL: i2t_ASN1_OBJECT() = '%s', "
309			    "want '%s'\n", buf, aot->txt);
310			goto failed;
311		}
312	}
313
314 done:
315	failed = 0;
316
317 failed:
318	ASN1_OBJECT_free(aobj);
319	free(der);
320
321	return failed;
322}
323
324static int
325asn1_object_test(void)
326{
327	int failed = 0;
328	size_t i;
329
330	for (i = 0; i < N_ASN1_OBJECT_TESTS; i++)
331		failed |= do_asn1_object_test(&asn1_object_tests[i]);
332
333	return failed;
334}
335
336const uint8_t asn1_object_bad_content1[] = {
337	0x55, 0x80, 0x04, 0x0a,
338};
339const uint8_t asn1_object_bad_content2[] = {
340	0x55, 0x04, 0x8a,
341};
342
343static int
344asn1_object_bad_content_test(void)
345{
346	ASN1_OBJECT *aobj = NULL;
347	const uint8_t *p;
348	size_t len;
349	int failed = 1;
350
351	p = asn1_object_bad_content1;
352	len = sizeof(asn1_object_bad_content1);
353	if ((aobj = c2i_ASN1_OBJECT(NULL, &p, len)) != NULL) {
354		fprintf(stderr, "FAIL: c2i_ASN1_OBJECT() succeeded with bad "
355		    "content 1\n");
356		goto failed;
357	}
358
359	p = asn1_object_bad_content2;
360	len = sizeof(asn1_object_bad_content2);
361	if ((aobj = c2i_ASN1_OBJECT(NULL, &p, len)) != NULL) {
362		fprintf(stderr, "FAIL: c2i_ASN1_OBJECT() succeeded with bad "
363		    "content 2\n");
364		goto failed;
365	}
366
367	failed = 0;
368
369 failed:
370	ASN1_OBJECT_free(aobj);
371
372	return failed;
373}
374
375static int
376asn1_object_txt_test(void)
377{
378	const char *obj_txt = "organizationName";
379	ASN1_OBJECT *aobj = NULL;
380	uint8_t small_buf[2];
381	const uint8_t *p;
382	int err, len, ret;
383	BIO *bio = NULL;
384	char *data;
385	long data_len;
386	int failed = 1;
387
388	ERR_clear_error();
389
390	ret = a2d_ASN1_OBJECT(small_buf, sizeof(small_buf), "1.2.3.4", -1);
391	if (ret != 0) {
392		fprintf(stderr, "FAIL: a2d_ASN1_OBJECT() with small buffer "
393		    "returned %d, want %d\n", ret, 0);
394		goto failed;
395	}
396	err = ERR_peek_error();
397	if (ERR_GET_REASON(err) != ASN1_R_BUFFER_TOO_SMALL) {
398		fprintf(stderr, "FAIL: Got error reason %d, want %d\n",
399		    ERR_GET_REASON(err), ASN1_R_BUFFER_TOO_SMALL);
400		goto failed;
401	}
402
403	p = &asn1_object_tests[2].der[0];
404	len = asn1_object_tests[2].der_len;
405	aobj = d2i_ASN1_OBJECT(NULL, &p, len);
406	if (aobj == NULL) {
407		fprintf(stderr, "FAIL: d2i_ASN1_OBJECT() failed\n");
408		goto failed;
409	}
410	ret = i2t_ASN1_OBJECT(small_buf, sizeof(small_buf), aobj);
411	if (ret < 0 || (unsigned long)ret != strlen(obj_txt)) {
412		fprintf(stderr, "FAIL: i2t_ASN1_OBJECT() with small buffer "
413		    "returned %d, want %zu\n", ret, strlen(obj_txt));
414		goto failed;
415	}
416
417	if ((bio = BIO_new(BIO_s_mem())) == NULL) {
418		fprintf(stderr, "FAIL: BIO_new() returned NULL\n");
419		goto failed;
420	}
421	ret = i2a_ASN1_OBJECT(bio, NULL);
422	if (ret != 4) {
423		fprintf(stderr, "FAIL: i2a_ASN1_OBJECT(_, NULL) returned %d, "
424		    "want 4\n", ret);
425		goto failed;
426	}
427	data_len = BIO_get_mem_data(bio, &data);
428	if (ret != data_len || memcmp("NULL", data, data_len) != 0) {
429		fprintf(stderr, "FAIL: i2a_ASN1_OBJECT(_, NULL) did not return "
430		    "'NULL'\n");
431		goto failed;
432	}
433
434	if ((ret = BIO_reset(bio)) <= 0) {
435		fprintf(stderr, "FAIL: BIO_reset failed: ret = %d\n", ret);
436		goto failed;
437	}
438	ret = i2a_ASN1_OBJECT(bio, aobj);
439	if (ret < 0 || (unsigned long)ret != strlen(obj_txt)) {
440		fprintf(stderr, "FAIL: i2a_ASN1_OBJECT() returned %d, "
441		    "want %zu\n", ret, strlen(obj_txt));
442		goto failed;
443	}
444	data_len = BIO_get_mem_data(bio, &data);
445	if (ret != data_len || memcmp(obj_txt, data, data_len) != 0) {
446		fprintf(stderr, "FAIL: i2a_ASN1_OBJECT() did not return "
447		    "'%s'\n", obj_txt);
448		goto failed;
449	}
450
451	failed = 0;
452
453 failed:
454	ASN1_OBJECT_free(aobj);
455	BIO_free(bio);
456
457	return failed;
458}
459
460const uint8_t asn1_large_oid_der[] = {
461	0x06, 0x26,
462	0x2b, 0x8f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
463	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
464	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
465	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
466	0xff, 0xff, 0xff, 0xff, 0xff, 0x7f,
467};
468
469static int
470asn1_object_large_oid_test(void)
471{
472	ASN1_OBJECT *aobj = NULL;
473	uint8_t buf[1024];
474	const uint8_t *p;
475	uint8_t *der = NULL;
476	uint8_t *q;
477	int ret;
478	int failed = 1;
479
480	p = asn1_large_oid_der;
481	aobj = d2i_ASN1_OBJECT(NULL, &p, sizeof(asn1_large_oid_der));
482	if (aobj == NULL) {
483		fprintf(stderr, "FAIL: d2i_ASN1_OBJECT() failed with "
484		    "large oid\n");
485		goto failed;
486	}
487
488	q = buf;
489	ret = i2d_ASN1_OBJECT(aobj, &q);
490	if (!asn1_compare_bytes("ASN1_OBJECT DER", buf, ret, asn1_large_oid_der,
491	    sizeof(asn1_large_oid_der)))
492		goto failed;
493
494	der = NULL;
495	ret = i2d_ASN1_OBJECT(aobj, &der);
496	if (!asn1_compare_bytes("ASN1_OBJECT DER", der, ret, asn1_large_oid_der,
497	    sizeof(asn1_large_oid_der)))
498		goto failed;
499
500	failed = 0;
501
502 failed:
503	ASN1_OBJECT_free(aobj);
504	free(der);
505
506	return failed;
507}
508
509static int
510asn1_object_i2d_errors(void)
511{
512	ASN1_OBJECT *aobj = NULL;
513	int ret;
514	int failed = 1;
515
516	if ((ret = i2d_ASN1_OBJECT(NULL, NULL)) > 0) {
517		fprintf(stderr, "FAIL: i2d_ASN1_OBJECT(NULL, NULL) returned %d, "
518		    "want <= 0\n", ret);
519		goto failed;
520	}
521
522	if ((aobj = OBJ_nid2obj(NID_undef)) == NULL) {
523		fprintf(stderr, "FAIL: OBJ_nid2obj() failed\n");
524		goto failed;
525	}
526
527	if (OBJ_get0_data(aobj) != NULL) {
528		fprintf(stderr, "FAIL: undefined obj didn't have NULL data\n");
529		goto failed;
530	}
531
532	if ((ret = i2d_ASN1_OBJECT(aobj, NULL)) > 0) {
533		fprintf(stderr, "FAIL: i2d_ASN1_OBJECT() succeeded on undefined "
534		    "object\n");
535		goto failed;
536	}
537
538	failed = 0;
539
540 failed:
541	ASN1_OBJECT_free(aobj);
542
543	return failed;
544}
545
546int
547main(int argc, char **argv)
548{
549	int failed = 0;
550
551	failed |= asn1_object_test();
552	failed |= asn1_object_bad_content_test();
553	failed |= asn1_object_txt_test();
554	failed |= asn1_object_large_oid_test();
555	failed |= asn1_object_i2d_errors();
556
557	return (failed);
558}
559