xref: /openbsd-src/regress/lib/libcrypto/x509/expirecallback.c (revision 3374c67d44f9b75b98444cbf63020f777792342e)
1 /* $OpenBSD: expirecallback.c,v 1.2 2022/10/17 18:44:36 jsing Exp $ */
2 /*
3  * Copyright (c) 2020 Joel Sing <jsing@openbsd.org>
4  * Copyright (c) 2020-2021 Bob Beck <beck@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <err.h>
20 #include <string.h>
21 
22 #include <openssl/bio.h>
23 #include <openssl/err.h>
24 #include <openssl/pem.h>
25 #include <openssl/x509.h>
26 #include <openssl/x509v3.h>
27 #include <openssl/x509_verify.h>
28 
29 #define MODE_MODERN_VFY		0
30 #define MODE_MODERN_VFY_DIR	1
31 #define MODE_LEGACY_VFY		2
32 #define MODE_VERIFY		3
33 
34 static int verbose = 1;
35 
36 static int
37 passwd_cb(char *buf, int size, int rwflag, void *u)
38 {
39 	memset(buf, 0, size);
40 	return (0);
41 }
42 
43 static int
44 certs_from_file(const char *filename, STACK_OF(X509) **certs)
45 {
46 	STACK_OF(X509_INFO) *xis = NULL;
47 	STACK_OF(X509) *xs = NULL;
48 	BIO *bio = NULL;
49 	X509 *x;
50 	int i;
51 
52 	if ((xs = sk_X509_new_null()) == NULL)
53 		errx(1, "failed to create X509 stack");
54 	if ((bio = BIO_new_file(filename, "r")) == NULL) {
55 		ERR_print_errors_fp(stderr);
56 		errx(1, "failed to create bio");
57 	}
58 	if ((xis = PEM_X509_INFO_read_bio(bio, NULL, passwd_cb, NULL)) == NULL)
59 		errx(1, "failed to read PEM");
60 
61 	for (i = 0; i < sk_X509_INFO_num(xis); i++) {
62 		if ((x = sk_X509_INFO_value(xis, i)->x509) == NULL)
63 			continue;
64 		if (!sk_X509_push(xs, x))
65 			errx(1, "failed to push X509");
66 		X509_up_ref(x);
67 	}
68 
69 	*certs = xs;
70 	xs = NULL;
71 
72 	sk_X509_INFO_pop_free(xis, X509_INFO_free);
73 	sk_X509_pop_free(xs, X509_free);
74 	BIO_free(bio);
75 
76 	return 1;
77 }
78 
79 static int
80 verify_cert_cb(int ok, X509_STORE_CTX *xsc)
81 {
82 	X509 *current_cert;
83 	int verify_err;
84 
85 	current_cert = X509_STORE_CTX_get_current_cert(xsc);
86 	if (current_cert != NULL) {
87 		X509_NAME_print_ex_fp(stderr,
88 		    X509_get_subject_name(current_cert), 0,
89 		    XN_FLAG_ONELINE);
90 		fprintf(stderr, "\n");
91 	}
92 
93 	verify_err = X509_STORE_CTX_get_error(xsc);
94 	if (verify_err != X509_V_OK) {
95 		if (verify_err == X509_V_ERR_CERT_HAS_EXPIRED)
96 			fprintf(stderr, "IGNORING ");
97 		fprintf(stderr, "verify error at depth %d: %s\n",
98 		    X509_STORE_CTX_get_error_depth(xsc),
99 		    X509_verify_cert_error_string(verify_err));
100 	}
101 
102 	/*
103 	 * Ignore expired certs, in the way people are told to do it
104 	 * by OpenSSL
105 	 */
106 
107 	if (verify_err == X509_V_ERR_CERT_HAS_EXPIRED)
108 		return 1;
109 
110 	return ok;
111 }
112 
113 static void
114 verify_cert(const char *roots_dir, const char *roots_file,
115     const char *bundle_file, int *chains, int *error, int *error_depth,
116     int mode)
117 {
118 	STACK_OF(X509) *roots = NULL, *bundle = NULL;
119 	X509_STORE_CTX *xsc = NULL;
120 	X509_STORE *store = NULL;
121 	X509 *leaf = NULL;
122 	int use_dir;
123 	int ret;
124 
125 	*chains = 0;
126 	*error = 0;
127 	*error_depth = 0;
128 
129 	use_dir = (mode == MODE_MODERN_VFY_DIR);
130 
131 	if (!use_dir && !certs_from_file(roots_file, &roots))
132 		errx(1, "failed to load roots from '%s'", roots_file);
133 	if (!certs_from_file(bundle_file, &bundle))
134 		errx(1, "failed to load bundle from '%s'", bundle_file);
135 	if (sk_X509_num(bundle) < 1)
136 		errx(1, "not enough certs in bundle");
137 	leaf = sk_X509_shift(bundle);
138 
139 	if ((xsc = X509_STORE_CTX_new()) == NULL)
140 		errx(1, "X509_STORE_CTX");
141 	if (use_dir && (store = X509_STORE_new()) == NULL)
142 		errx(1, "X509_STORE");
143 	if (!X509_STORE_CTX_init(xsc, store, leaf, bundle)) {
144 		ERR_print_errors_fp(stderr);
145 		errx(1, "failed to init store context");
146 	}
147 
148 	if (use_dir) {
149 		if (!X509_STORE_load_locations(store, NULL, roots_dir))
150 			errx(1, "failed to set by_dir directory of %s", roots_dir);
151 	}
152 	if (mode == MODE_LEGACY_VFY)
153 		X509_STORE_CTX_set_flags(xsc, X509_V_FLAG_LEGACY_VERIFY);
154 	else
155 		X509_VERIFY_PARAM_clear_flags(X509_STORE_CTX_get0_param(xsc),
156 		    X509_V_FLAG_LEGACY_VERIFY);
157 
158 	if (verbose)
159 		X509_STORE_CTX_set_verify_cb(xsc, verify_cert_cb);
160 	if (!use_dir)
161 		X509_STORE_CTX_set0_trusted_stack(xsc, roots);
162 
163 	ret = X509_verify_cert(xsc);
164 
165 	*error = X509_STORE_CTX_get_error(xsc);
166 	*error_depth = X509_STORE_CTX_get_error_depth(xsc);
167 
168 	if (ret == 1) {
169 		*chains = 1; /* XXX */
170 		goto done;
171 	}
172 
173 	if (*error == 0)
174 		errx(1, "Error unset on failure!\n");
175 
176 	fprintf(stderr, "failed to verify at %d: %s\n",
177 	    *error_depth, X509_verify_cert_error_string(*error));
178 
179  done:
180 	sk_X509_pop_free(roots, X509_free);
181 	sk_X509_pop_free(bundle, X509_free);
182 	X509_STORE_free(store);
183 	X509_STORE_CTX_free(xsc);
184 	X509_free(leaf);
185 }
186 
187 struct verify_cert_test {
188 	const char *id;
189 	int want_chains;
190 	int want_error;
191 	int want_error_depth;
192 	int want_legacy_error;
193 	int want_legacy_error_depth;
194 	int failing;
195 };
196 
197 struct verify_cert_test verify_cert_tests[] = {
198 	{
199 		.id = "2a",
200 		.want_chains = 1,
201 		.want_error = 0,
202 		.want_error_depth = 0,
203 		.want_legacy_error = 0,
204 		.want_legacy_error_depth = 0,
205 	},
206 	{
207 		.id = "8a",
208 		.want_chains = 1,
209 		.want_error = X509_V_ERR_CERT_HAS_EXPIRED,
210 		.want_error_depth = 0,
211 		.want_legacy_error = X509_V_ERR_CERT_HAS_EXPIRED,
212 		.want_legacy_error_depth = 0,
213 	},
214 	{
215 		.id = "9a",
216 		.want_chains = 1,
217 		.want_error = X509_V_ERR_CERT_HAS_EXPIRED,
218 		.want_error_depth = 0,
219 		.want_legacy_error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
220 		.want_legacy_error_depth = 0,
221 		.failing = 1,
222 	},
223 };
224 
225 #define N_VERIFY_CERT_TESTS \
226     (sizeof(verify_cert_tests) / sizeof(*verify_cert_tests))
227 
228 static int
229 verify_cert_test(const char *certs_path, int mode)
230 {
231 	char *roots_file, *bundle_file, *roots_dir;
232 	struct verify_cert_test *vct;
233 	int chains, error, error_depth;
234 	int failed = 0;
235 	size_t i;
236 
237 	for (i = 0; i < N_VERIFY_CERT_TESTS; i++) {
238 		vct = &verify_cert_tests[i];
239 
240 		if (asprintf(&roots_file, "%s/%s/roots.pem", certs_path,
241 		    vct->id) == -1)
242 			errx(1, "asprintf");
243 		if (asprintf(&bundle_file, "%s/%s/bundle.pem", certs_path,
244 		    vct->id) == -1)
245 			errx(1, "asprintf");
246 		if (asprintf(&roots_dir, "./%s/roots", vct->id) == -1)
247 			errx(1, "asprintf");
248 
249 		fprintf(stderr, "== Test %zu (%s)\n", i, vct->id);
250 		verify_cert(roots_dir, roots_file, bundle_file, &chains, &error,
251 		    &error_depth, mode);
252 
253 		if ((mode == MODE_VERIFY && chains == vct->want_chains) ||
254 		    (chains == 0 && vct->want_chains == 0) ||
255 		    (chains == 1 && vct->want_chains > 0)) {
256 			fprintf(stderr, "INFO: Succeeded with %d chains%s\n",
257 			    chains, vct->failing ? " (legacy failure)" : "");
258 			if (mode == MODE_LEGACY_VFY && vct->failing)
259 				failed |= 1;
260 		} else {
261 			fprintf(stderr, "FAIL: Failed with %d chains%s\n",
262 			    chains, vct->failing ? " (legacy failure)" : "");
263 			if (!vct->failing)
264 				failed |= 1;
265 		}
266 
267 		if (mode == MODE_LEGACY_VFY) {
268 			if (error != vct->want_legacy_error) {
269 				fprintf(stderr, "FAIL: Got legacy error %d, "
270 				    "want %d\n", error, vct->want_legacy_error);
271 				failed |= 1;
272 			}
273 			if (error_depth != vct->want_legacy_error_depth) {
274 				fprintf(stderr, "FAIL: Got legacy error depth "
275 				    "%d, want %d\n", error_depth,
276 				    vct->want_legacy_error_depth);
277 				failed |= 1;
278 			}
279 		} else if (mode == MODE_MODERN_VFY || mode == MODE_MODERN_VFY_DIR) {
280 			if (error != vct->want_error) {
281 				fprintf(stderr, "FAIL: Got error %d, want %d\n",
282 				    error, vct->want_error);
283 				failed |= 1;
284 			}
285 			if (error_depth != vct->want_error_depth) {
286 				fprintf(stderr, "FAIL: Got error depth %d, want"
287 				    " %d\n", error_depth, vct->want_error_depth);
288 				failed |= 1;
289 			}
290 		}
291 
292 		fprintf(stderr, "\n");
293 
294 		free(roots_file);
295 		free(bundle_file);
296 		free(roots_dir);
297 	}
298 
299 	return failed;
300 }
301 
302 int
303 main(int argc, char **argv)
304 {
305 	int failed = 0;
306 
307 	if (argc != 2) {
308 		fprintf(stderr, "usage: %s <certs_path>\n", argv[0]);
309 		exit(1);
310 	}
311 
312 	fprintf(stderr, "\n\nTesting legacy x509_vfy\n");
313 	failed |= verify_cert_test(argv[1], MODE_LEGACY_VFY);
314 	fprintf(stderr, "\n\nTesting modern x509_vfy\n");
315 	failed |= verify_cert_test(argv[1], MODE_MODERN_VFY);
316 	fprintf(stderr, "\n\nTesting modern x509_vfy by_dir\n");
317 	failed |= verify_cert_test(argv[1], MODE_MODERN_VFY_DIR);
318 
319 	return (failed);
320 }
321