verifytest.c revision 1.8
1/*	$OpenBSD: verifytest.c,v 1.8 2023/05/28 09:02:01 beck Exp $	*/
2/*
3 * Copyright (c) 2014 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 <err.h>
19#include <stdio.h>
20#include <stdlib.h>
21
22#include <openssl/x509v3.h>
23#include <tls.h>
24
25extern int tls_check_name(struct tls *ctx, X509 *cert, const char *name,
26    int *match);
27
28struct alt_name {
29	const char name[128];
30	int name_len;
31	int name_type;
32};
33
34struct verify_test {
35	const char common_name[128];
36	int common_name_len;
37	struct alt_name alt_name1;
38	struct alt_name alt_name2;
39	struct alt_name alt_name3;
40	const char name[128];
41	int want_return;
42	int want_match;
43	int name_type;
44};
45
46struct verify_test verify_tests[] = {
47	{
48		/* CN without SANs - matching. */
49		.common_name = "www.openbsd.org",
50		.common_name_len = -1,
51		.name = "www.openbsd.org",
52		.want_return = 0,
53		.want_match = 1,
54	},
55	{
56		/* Zero length name - non-matching. */
57		.common_name = "www.openbsd.org",
58		.common_name_len = -1,
59		.name = "",
60		.want_return = 0,
61		.want_match = 0,
62	},
63	{
64		/* CN wildcard without SANs - matching. */
65		.common_name = "*.openbsd.org",
66		.common_name_len = -1,
67		.name = "www.openbsd.org",
68		.want_return = 0,
69		.want_match = 1,
70	},
71	{
72		/* CN without SANs - non-matching. */
73		.common_name = "www.openbsdfoundation.org",
74		.common_name_len = -1,
75		.name = "www.openbsd.org",
76		.want_return = 0,
77		.want_match = 0,
78	},
79	{
80		/* CN wildcard without SANs - invalid CN wildcard. */
81		.common_name = "w*.openbsd.org",
82		.common_name_len = -1,
83		.name = "www.openbsd.org",
84		.want_return = 0,
85		.want_match = 0,
86	},
87	{
88		/* CN wildcard without SANs - invalid CN wildcard. */
89		.common_name = "www.*.org",
90		.common_name_len = -1,
91		.name = "www.openbsd.org",
92		.want_return = 0,
93		.want_match = 0,
94	},
95	{
96		/* CN wildcard without SANs - invalid CN wildcard. */
97		.common_name = "www.openbsd.*",
98		.common_name_len = -1,
99		.name = "www.openbsd.org",
100		.want_return = 0,
101		.want_match = 0,
102	},
103	{
104		/* CN wildcard without SANs - invalid CN wildcard. */
105		.common_name = "*",
106		.common_name_len = -1,
107		.name = "www.openbsd.org",
108		.want_return = 0,
109		.want_match = 0,
110	},
111	{
112		/* CN wildcard without SANs - invalid CN wildcard. */
113		.common_name = "*.org",
114		.common_name_len = -1,
115		.name = "www.openbsd.org",
116		.want_return = 0,
117		.want_match = 0,
118	},
119	{
120		/* CN wildcard without SANs - invalid CN wildcard. */
121		.common_name = "*.org",
122		.common_name_len = -1,
123		.name = "openbsd.org",
124		.want_return = 0,
125		.want_match = 0,
126	},
127	{
128		/* CN IPv4 without SANs - matching. */
129		.common_name = "1.2.3.4",
130		.common_name_len = -1,
131		.name = "1.2.3.4",
132		.want_return = 0,
133		.want_match = 1,
134	},
135	{
136		/* CN IPv4 wildcard without SANS - invalid IP wildcard. */
137		.common_name = "*.2.3.4",
138		.common_name_len = -1,
139		.name = "1.2.3.4",
140		.want_return = 0,
141		.want_match = 0,
142	},
143	{
144		/* CN IPv6 without SANs - matching. */
145		.common_name = "cafe::beef",
146		.common_name_len = -1,
147		.name = "cafe::beef",
148		.want_return = 0,
149		.want_match = 1,
150	},
151	{
152		/* CN without SANs - error due to embedded NUL in CN. */
153		.common_name = {
154			0x77, 0x77, 0x77, 0x2e, 0x6f, 0x70, 0x65, 0x6e,
155			0x62, 0x73, 0x64, 0x2e, 0x6f, 0x72, 0x67, 0x00,
156			0x6e, 0x61, 0x73, 0x74, 0x79, 0x2e, 0x6f, 0x72,
157			0x67,
158		},
159		.common_name_len = 25,
160		.name = "www.openbsd.org",
161		.want_return = -1,
162		.want_match = 0,
163	},
164	{
165		/* CN wildcard without SANs - invalid non-matching name. */
166		.common_name = "*.openbsd.org",
167		.common_name_len = -1,
168		.name = ".openbsd.org",
169		.want_return = 0,
170		.want_match = 0,
171	},
172	{
173		/* CN with SANs - matching on first SAN. */
174		.common_name = "www.openbsd.org",
175		.common_name_len = -1,
176		.alt_name1 = {
177			.name = "www.openbsd.org",
178			.name_len = -1,
179			.name_type = GEN_DNS,
180		},
181		.alt_name2 = {
182			.name = "ftp.openbsd.org",
183			.name_len = -1,
184			.name_type = GEN_DNS,
185		},
186		.name = "www.openbsd.org",
187		.want_return = 0,
188		.want_match = 1,
189	},
190	{
191		/* SANs only - matching on first SAN. */
192		.common_name_len = 0,
193		.alt_name1 = {
194			.name = "www.openbsd.org",
195			.name_len = -1,
196			.name_type = GEN_DNS,
197		},
198		.alt_name2 = {
199			.name = "ftp.openbsd.org",
200			.name_len = -1,
201			.name_type = GEN_DNS,
202		},
203		.name = "www.openbsd.org",
204		.want_return = 0,
205		.want_match = 1,
206	},
207	{
208		/* SANs only - matching on second SAN. */
209		.common_name_len = 0,
210		.alt_name1 = {
211			.name = "www.openbsd.org",
212			.name_len = -1,
213			.name_type = GEN_DNS,
214		},
215		.alt_name2 = {
216			.name = "ftp.openbsd.org",
217			.name_len = -1,
218			.name_type = GEN_DNS,
219		},
220		.name = "ftp.openbsd.org",
221		.want_return = 0,
222		.want_match = 1,
223	},
224	{
225		/* SANs only - non-matching. */
226		.common_name_len = 0,
227		.alt_name1 = {
228			.name = "www.openbsd.org",
229			.name_len = -1,
230			.name_type = GEN_DNS,
231		},
232		.alt_name2 = {
233			.name = "ftp.openbsd.org",
234			.name_len = -1,
235			.name_type = GEN_DNS,
236		},
237		.name = "mail.openbsd.org",
238		.want_return = 0,
239		.want_match = 0,
240	},
241	{
242		/* CN with SANs - matching on second SAN. */
243		.common_name = "www.openbsd.org",
244		.common_name_len = -1,
245		.alt_name1 = {
246			.name = "www.openbsd.org",
247			.name_len = -1,
248			.name_type = GEN_DNS,
249		},
250		.alt_name2 = {
251			.name = "ftp.openbsd.org",
252			.name_len = -1,
253			.name_type = GEN_DNS,
254		},
255		.name = "ftp.openbsd.org",
256		.want_return = 0,
257		.want_match = 1,
258	},
259	{
260		/* CN with SANs - matching on wildcard second SAN. */
261		.common_name = "www.openbsdfoundation.org",
262		.common_name_len = -1,
263		.alt_name1 = {
264			.name = "www.openbsdfoundation.org",
265			.name_len = -1,
266			.name_type = GEN_DNS,
267		},
268		.alt_name2 = {
269			.name = "*.openbsd.org",
270			.name_len = -1,
271			.name_type = GEN_DNS,
272		},
273		.name = "www.openbsd.org",
274		.want_return = 0,
275		.want_match = 1,
276	},
277	{
278		/* CN with SANs - non-matching invalid wildcard. */
279		.common_name = "www.openbsdfoundation.org",
280		.common_name_len = -1,
281		.alt_name1 = {
282			.name = "www.openbsdfoundation.org",
283			.name_len = -1,
284			.name_type = GEN_DNS,
285		},
286		.alt_name2 = {
287			.name = "*.org",
288			.name_len = -1,
289			.name_type = GEN_DNS,
290		},
291		.name = "www.openbsd.org",
292		.want_return = 0,
293		.want_match = 0,
294	},
295	{
296		/* CN with SANs - non-matching IPv4 due to GEN_DNS SAN. */
297		.common_name = "www.openbsd.org",
298		.common_name_len = -1,
299		.alt_name1 = {
300			.name = "www.openbsd.org",
301			.name_len = -1,
302			.name_type = GEN_DNS,
303		},
304		.alt_name2 = {
305			.name = "1.2.3.4",
306			.name_len = -1,
307			.name_type = GEN_DNS,
308		},
309		.name = "1.2.3.4",
310		.want_return = 0,
311		.want_match = 0,
312	},
313	{
314		/* CN with SANs - matching IPv4 on GEN_IPADD SAN. */
315		.common_name = "www.openbsd.org",
316		.common_name_len = -1,
317		.alt_name1 = {
318			.name = "www.openbsd.org",
319			.name_len = -1,
320			.name_type = GEN_DNS,
321		},
322		.alt_name2 = {
323			.name = {0x01, 0x02, 0x03, 0x04},
324			.name_len = 4,
325			.name_type = GEN_IPADD,
326		},
327		.name = "1.2.3.4",
328		.want_return = 0,
329		.want_match = 1,
330	},
331	{
332		/* CN with SANs - matching IPv6 on GEN_IPADD SAN. */
333		.common_name = "www.openbsd.org",
334		.common_name_len = -1,
335		.alt_name1 = {
336			.name = "www.openbsd.org",
337			.name_len = -1,
338			.name_type = GEN_DNS,
339		},
340		.alt_name2 = {
341			.name = {
342				0xca, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
343				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbe, 0xef,
344			},
345			.name_len = 16,
346			.name_type = GEN_IPADD,
347		},
348		.name = "cafe::beef",
349		.want_return = 0,
350		.want_match = 1,
351	},
352	{
353		/* CN with SANs - error due to embedded NUL in GEN_DNS. */
354		.common_name = "www.openbsd.org.nasty.org",
355		.common_name_len = -1,
356		.alt_name1 = {
357			.name = "www.openbsd.org.nasty.org",
358			.name_len = -1,
359			.name_type = GEN_DNS,
360		},
361		.alt_name2 = {
362			.name = {
363				0x77, 0x77, 0x77, 0x2e, 0x6f, 0x70, 0x65, 0x6e,
364				0x62, 0x73, 0x64, 0x2e, 0x6f, 0x72, 0x67, 0x00,
365				0x6e, 0x61, 0x73, 0x74, 0x79, 0x2e, 0x6f, 0x72,
366				0x67,
367			},
368			.name_len = 25,
369			.name_type = GEN_DNS,
370		},
371		.name = "www.openbsd.org",
372		.want_return = -1,
373		.want_match = 0,
374	},
375	{
376		/* CN with SAN - non-matching due to non-matching SAN. */
377		.common_name = "www.openbsd.org",
378		.common_name_len = -1,
379		.alt_name1 = {
380			.name = "ftp.openbsd.org",
381			.name_len = -1,
382			.name_type = GEN_DNS,
383		},
384		.name = "www.openbsd.org",
385		.want_return = 0,
386		.want_match = 0,
387	},
388	{
389		/* CN with SAN - error due to illegal dNSName. */
390		.common_name = "www.openbsd.org",
391		.common_name_len = -1,
392		.alt_name1 = {
393			.name = " ",
394			.name_len = -1,
395			.name_type = GEN_DNS,
396		},
397		.name = "www.openbsd.org",
398		.want_return = -1,
399		.want_match = 0,
400	},
401};
402
403#define N_VERIFY_TESTS \
404    (sizeof(verify_tests) / sizeof(*verify_tests))
405
406static void
407alt_names_add(STACK_OF(GENERAL_NAME) *alt_name_stack, struct alt_name *alt)
408{
409	ASN1_STRING *alt_name_str;
410	GENERAL_NAME *alt_name;
411
412	if ((alt_name = GENERAL_NAME_new()) == NULL)
413		errx(1, "failed to malloc GENERAL_NAME");
414	alt_name->type = alt->name_type;
415
416	if ((alt_name_str = ASN1_STRING_new()) == NULL)
417		errx(1, "failed to malloc alt name");
418	if (ASN1_STRING_set(alt_name_str, alt->name, alt->name_len) == 0)
419		errx(1, "failed to set alt name");
420
421	switch (alt_name->type) {
422	case GEN_DNS:
423		alt_name->d.dNSName = alt_name_str;
424		break;
425	case GEN_IPADD:
426		alt_name->d.iPAddress = alt_name_str;
427		break;
428	default:
429		errx(1, "unknown alt name type (%i)", alt_name->type);
430	}
431
432	if (sk_GENERAL_NAME_push(alt_name_stack, alt_name) == 0)
433		errx(1, "failed to push alt_name");
434}
435
436static void
437cert_add_alt_names(X509 *cert, struct verify_test *vt)
438{
439	STACK_OF(GENERAL_NAME) *alt_name_stack = NULL;
440
441	if (vt->alt_name1.name_type == 0)
442		return;
443
444	if ((alt_name_stack = sk_GENERAL_NAME_new_null()) == NULL)
445		errx(1, "failed to malloc sk_GENERAL_NAME");
446
447	if (vt->alt_name1.name_type != 0)
448		alt_names_add(alt_name_stack, &vt->alt_name1);
449	if (vt->alt_name2.name_type != 0)
450		alt_names_add(alt_name_stack, &vt->alt_name2);
451	if (vt->alt_name3.name_type != 0)
452		alt_names_add(alt_name_stack, &vt->alt_name3);
453
454	if (X509_add1_ext_i2d(cert, NID_subject_alt_name,
455	    alt_name_stack, 0, 0) == 0)
456		errx(1, "failed to set subject alt name");
457
458	sk_GENERAL_NAME_pop_free(alt_name_stack, GENERAL_NAME_free);
459}
460
461static int
462do_verify_test(int test_no, struct verify_test *vt)
463{
464	struct tls *tls;
465	X509_NAME *name;
466	X509 *cert;
467	int failed = 1;
468	int match;
469
470	/* Build certificate structure. */
471	if ((cert = X509_new()) == NULL)
472		errx(1, "failed to malloc X509");
473
474	if (vt->common_name_len != 0) {
475		if ((name = X509_NAME_new()) == NULL)
476			errx(1, "failed to malloc X509_NAME");
477		if (X509_NAME_add_entry_by_NID(name, NID_commonName,
478		    vt->name_type ? vt->name_type : MBSTRING_ASC,
479		    (unsigned char *)vt->common_name,
480		    vt->common_name_len, -1, 0) == 0)
481			errx(1, "failed to add name entry");
482		if (X509_set_subject_name(cert, name) == 0)
483			errx(1, "failed to set subject name");
484		X509_NAME_free(name);
485	}
486
487	if ((tls = tls_client()) == NULL)
488		errx(1, "failed to malloc tls_client");
489
490	cert_add_alt_names(cert, vt);
491
492	match = 1;
493
494	if (tls_check_name(tls, cert, vt->name, &match) != vt->want_return) {
495		fprintf(stderr, "FAIL: test %i failed for check name '%s': "
496		    "%s\n", test_no, vt->name, tls_error(tls));
497		goto done;
498	}
499	if (match != vt->want_match) {
500		fprintf(stderr, "FAIL: test %i failed to match name '%s'\n",
501		    test_no, vt->name);
502		goto done;
503	}
504
505	failed = 0;
506
507 done:
508	X509_free(cert);
509	tls_free(tls);
510
511	return (failed);
512}
513
514int
515main(int argc, char **argv)
516{
517	int failed = 0;
518	size_t i;
519
520	tls_init();
521
522	for (i = 0; i < N_VERIFY_TESTS; i++)
523		failed += do_verify_test(i, &verify_tests[i]);
524
525	return (failed);
526}
527