1 /* $OpenBSD: expirecallback.c,v 1.4 2024/08/23 12:56:26 anton 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
passwd_cb(char * buf,int size,int rwflag,void * u)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
certs_from_file(const char * filename,STACK_OF (X509)** certs)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
verify_cert_cb(int ok,X509_STORE_CTX * xsc)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
verify_cert(const char * roots_dir,const char * roots_file,const char * bundle_file,int * chains,int * error,int * error_depth,int mode)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!");
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
verify_cert_test(const char * certs_path,int mode)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
main(int argc,char ** argv)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