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