summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorbeck <>2022-06-28 07:56:34 +0000
committerbeck <>2022-06-28 07:56:34 +0000
commitb88f8532734e13a9a2fcc8470d64c546b9d15ab3 (patch)
tree435b35111450a62496cf4ac38a00aa704fe6980a /src
parent63d6cdd50ac3d7dec72bc659f9c851f2d7ef2f5e (diff)
downloadopenbsd-b88f8532734e13a9a2fcc8470d64c546b9d15ab3.tar.gz
openbsd-b88f8532734e13a9a2fcc8470d64c546b9d15ab3.tar.bz2
openbsd-b88f8532734e13a9a2fcc8470d64c546b9d15ab3.zip
Fix the legacy verifier callback behaviour for untrusted certs.
The verifier callback is used by mutt to do a form of certificate pinning where the callback gets fired and depending on a cert saved to a file will decide to accept an untrusted cert. This corrects two problems that affected this. The callback was not getting the correct depth and chain for the error where mutt would save the certificate in the first place, and then the callback was not getting fired to allow it to override the failing certificate validation. thanks to Avon Robertson <avon.r@xtra.co.nz> for the report and sthen@ for analysis. "The callback is not an API, it's a gordian knot - tb@" ok jsing@
Diffstat (limited to 'src')
-rw-r--r--src/lib/libcrypto/x509/x509_verify.c61
-rw-r--r--src/regress/lib/libcrypto/x509/Makefile8
-rw-r--r--src/regress/lib/libcrypto/x509/callbackfailures.c297
3 files changed, 347 insertions, 19 deletions
diff --git a/src/lib/libcrypto/x509/x509_verify.c b/src/lib/libcrypto/x509/x509_verify.c
index 83030672ef..aa14bc1933 100644
--- a/src/lib/libcrypto/x509/x509_verify.c
+++ b/src/lib/libcrypto/x509/x509_verify.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: x509_verify.c,v 1.57 2022/06/27 14:10:22 tb Exp $ */ 1/* $OpenBSD: x509_verify.c,v 1.58 2022/06/28 07:56:34 beck Exp $ */
2/* 2/*
3 * Copyright (c) 2020-2021 Bob Beck <beck@openbsd.org> 3 * Copyright (c) 2020-2021 Bob Beck <beck@openbsd.org>
4 * 4 *
@@ -258,6 +258,25 @@ x509_verify_cert_self_signed(X509 *cert)
258 return (cert->ex_flags & EXFLAG_SS) ? 1 : 0; 258 return (cert->ex_flags & EXFLAG_SS) ? 1 : 0;
259} 259}
260 260
261/* XXX beck - clean up this mess of is_root */
262static int
263x509_verify_check_chain_end(X509 *cert, int full_chain)
264{
265 if (full_chain)
266 return x509_verify_cert_self_signed(cert);
267 return 1;
268}
269
270static int
271x509_verify_check_legacy_chain_end(struct x509_verify_ctx *ctx, X509 *cert,
272 int full_chain)
273{
274 if (X509_check_trust(cert, ctx->xsc->param->trust, 0) !=
275 X509_TRUST_TRUSTED)
276 return 0;
277 return x509_verify_check_chain_end(cert, full_chain);
278}
279
261static int 280static int
262x509_verify_ctx_cert_is_root(struct x509_verify_ctx *ctx, X509 *cert, 281x509_verify_ctx_cert_is_root(struct x509_verify_ctx *ctx, X509 *cert,
263 int full_chain) 282 int full_chain)
@@ -273,15 +292,16 @@ x509_verify_ctx_cert_is_root(struct x509_verify_ctx *ctx, X509 *cert,
273 if ((match = x509_vfy_lookup_cert_match(ctx->xsc, 292 if ((match = x509_vfy_lookup_cert_match(ctx->xsc,
274 cert)) != NULL) { 293 cert)) != NULL) {
275 X509_free(match); 294 X509_free(match);
276 return !full_chain || 295 return x509_verify_check_legacy_chain_end(ctx, cert,
277 x509_verify_cert_self_signed(cert); 296 full_chain);
297
278 } 298 }
279 } else { 299 } else {
280 /* Check the provided roots */ 300 /* Check the provided roots */
281 for (i = 0; i < sk_X509_num(ctx->roots); i++) { 301 for (i = 0; i < sk_X509_num(ctx->roots); i++) {
282 if (X509_cmp(sk_X509_value(ctx->roots, i), cert) == 0) 302 if (X509_cmp(sk_X509_value(ctx->roots, i), cert) == 0)
283 return !full_chain || 303 return x509_verify_check_chain_end(cert,
284 x509_verify_cert_self_signed(cert); 304 full_chain);
285 } 305 }
286 } 306 }
287 307
@@ -393,14 +413,23 @@ x509_verify_ctx_validate_legacy_chain(struct x509_verify_ctx *ctx,
393 ctx->xsc->error = X509_V_OK; 413 ctx->xsc->error = X509_V_OK;
394 ctx->xsc->error_depth = 0; 414 ctx->xsc->error_depth = 0;
395 415
396 trust = x509_vfy_check_trust(ctx->xsc);
397 if (trust == X509_TRUST_REJECTED)
398 goto err;
399
400 if (!x509_verify_ctx_set_xsc_chain(ctx, chain, 0, 1)) 416 if (!x509_verify_ctx_set_xsc_chain(ctx, chain, 0, 1))
401 goto err; 417 goto err;
402 418
403 /* 419 /*
420 * Call the legacy code to walk the chain and check trust
421 * in the legacy way to handle partial chains and get the
422 * callback fired correctly.
423 */
424 trust = x509_vfy_check_trust(ctx->xsc);
425 if (trust == X509_TRUST_REJECTED)
426 goto err; /* callback was called in x509_vfy_check_trust */
427 if (trust != X509_TRUST_TRUSTED) {
428 /* NOTREACHED */
429 goto err; /* should not happen if we get in here - abort? */
430 }
431
432 /*
404 * XXX currently this duplicates some work done in chain 433 * XXX currently this duplicates some work done in chain
405 * build, but we keep it here until we have feature parity 434 * build, but we keep it here until we have feature parity
406 */ 435 */
@@ -432,10 +461,6 @@ x509_verify_ctx_validate_legacy_chain(struct x509_verify_ctx *ctx,
432 if (!x509_vfy_check_policy(ctx->xsc)) 461 if (!x509_vfy_check_policy(ctx->xsc))
433 goto err; 462 goto err;
434 463
435 if ((!(ctx->xsc->param->flags & X509_V_FLAG_PARTIAL_CHAIN)) &&
436 trust != X509_TRUST_TRUSTED)
437 goto err;
438
439 ret = 1; 464 ret = 1;
440 465
441 err: 466 err:
@@ -688,8 +713,8 @@ x509_verify_build_chains(struct x509_verify_ctx *ctx, X509 *cert,
688 } 713 }
689 if (ret > 0) { 714 if (ret > 0) {
690 if (x509_verify_potential_parent(ctx, candidate, cert)) { 715 if (x509_verify_potential_parent(ctx, candidate, cert)) {
691 is_root = !full_chain || 716 is_root = x509_verify_check_legacy_chain_end(
692 x509_verify_cert_self_signed(candidate); 717 ctx, candidate, full_chain);
693 x509_verify_consider_candidate(ctx, cert, 718 x509_verify_consider_candidate(ctx, cert,
694 is_root, candidate, current_chain, 719 is_root, candidate, current_chain,
695 full_chain, name); 720 full_chain, name);
@@ -701,8 +726,8 @@ x509_verify_build_chains(struct x509_verify_ctx *ctx, X509 *cert,
701 for (i = 0; i < sk_X509_num(ctx->roots); i++) { 726 for (i = 0; i < sk_X509_num(ctx->roots); i++) {
702 candidate = sk_X509_value(ctx->roots, i); 727 candidate = sk_X509_value(ctx->roots, i);
703 if (x509_verify_potential_parent(ctx, candidate, cert)) { 728 if (x509_verify_potential_parent(ctx, candidate, cert)) {
704 is_root = !full_chain || 729 is_root = x509_verify_check_chain_end(candidate,
705 x509_verify_cert_self_signed(candidate); 730 full_chain);
706 x509_verify_consider_candidate(ctx, cert, 731 x509_verify_consider_candidate(ctx, cert,
707 is_root, candidate, current_chain, 732 is_root, candidate, current_chain,
708 full_chain, name); 733 full_chain, name);
@@ -1168,6 +1193,8 @@ x509_verify(struct x509_verify_ctx *ctx, X509 *leaf, char *name)
1168 * on failure and will be needed for 1193 * on failure and will be needed for
1169 * that. 1194 * that.
1170 */ 1195 */
1196 ctx->xsc->error = ctx->error;
1197 ctx->xsc->error_depth = ctx->error_depth;
1171 if (!x509_verify_ctx_save_xsc_error(ctx)) { 1198 if (!x509_verify_ctx_save_xsc_error(ctx)) {
1172 x509_verify_chain_free(current_chain); 1199 x509_verify_chain_free(current_chain);
1173 goto err; 1200 goto err;
diff --git a/src/regress/lib/libcrypto/x509/Makefile b/src/regress/lib/libcrypto/x509/Makefile
index ca66df19cd..4635d63ed0 100644
--- a/src/regress/lib/libcrypto/x509/Makefile
+++ b/src/regress/lib/libcrypto/x509/Makefile
@@ -1,7 +1,7 @@
1# $OpenBSD: Makefile,v 1.13 2022/06/25 20:01:43 beck Exp $ 1# $OpenBSD: Makefile,v 1.14 2022/06/28 07:56:34 beck Exp $
2 2
3PROGS = constraints verify x509attribute x509name x509req_ext callback 3PROGS = constraints verify x509attribute x509name x509req_ext callback
4PROGS += expirecallback 4PROGS += expirecallback callbackfailures
5LDADD = -lcrypto 5LDADD = -lcrypto
6DPADD = ${LIBCRYPTO} 6DPADD = ${LIBCRYPTO}
7 7
@@ -20,6 +20,7 @@ REGRESS_TARGETS += regress-x509name
20REGRESS_TARGETS += regress-x509req_ext 20REGRESS_TARGETS += regress-x509req_ext
21REGRESS_TARGETS += regress-callback 21REGRESS_TARGETS += regress-callback
22REGRESS_TARGETS += regress-expirecallback 22REGRESS_TARGETS += regress-expirecallback
23REGRESS_TARGETS += regress-callbackfailures
23 24
24CLEANFILES += x509name.result callbackout 25CLEANFILES += x509name.result callbackout
25 26
@@ -54,4 +55,7 @@ regress-callback: callback
54regress-expirecallback: expirecallback 55regress-expirecallback: expirecallback
55 ./expirecallback ${.CURDIR}/../certs 56 ./expirecallback ${.CURDIR}/../certs
56 57
58regress-callbackfailures: callbackfailures
59 ./callbackfailures ${.CURDIR}/../certs
60
57.include <bsd.regress.mk> 61.include <bsd.regress.mk>
diff --git a/src/regress/lib/libcrypto/x509/callbackfailures.c b/src/regress/lib/libcrypto/x509/callbackfailures.c
new file mode 100644
index 0000000000..f714adffff
--- /dev/null
+++ b/src/regress/lib/libcrypto/x509/callbackfailures.c
@@ -0,0 +1,297 @@
1/* $OpenBSD: callbackfailures.c,v 1.1 2022/06/28 07:56:34 beck 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
34static int verbose = 1;
35
36static int expected_depth;
37static int expected_error;
38static int seen_depth;
39static int seen_error;
40
41static int
42passwd_cb(char *buf, int size, int rwflag, void *u)
43{
44 memset(buf, 0, size);
45 return (0);
46}
47
48static int
49certs_from_file(const char *filename, STACK_OF(X509) **certs, int clear)
50{
51 STACK_OF(X509_INFO) *xis = NULL;
52 STACK_OF(X509) *xs = NULL;
53 BIO *bio = NULL;
54 X509 *x;
55 int i;
56
57 if (clear) {
58 if ((xs = sk_X509_new_null()) == NULL)
59 errx(1, "failed to create X509 stack");
60 } else
61 xs = *certs;
62 if ((bio = BIO_new_file(filename, "r")) == NULL) {
63 ERR_print_errors_fp(stderr);
64 errx(1, "failed to create bio");
65 }
66 if ((xis = PEM_X509_INFO_read_bio(bio, NULL, passwd_cb, NULL)) == NULL)
67 errx(1, "failed to read PEM");
68
69 for (i = 0; i < sk_X509_INFO_num(xis); i++) {
70 if ((x = sk_X509_INFO_value(xis, i)->x509) == NULL)
71 continue;
72 if (!sk_X509_push(xs, x))
73 errx(1, "failed to push X509");
74 X509_up_ref(x);
75 }
76
77 *certs = xs;
78 xs = NULL;
79
80 sk_X509_INFO_pop_free(xis, X509_INFO_free);
81 sk_X509_pop_free(xs, X509_free);
82 BIO_free(bio);
83
84 return 1;
85}
86
87static int
88verify_cert_cb(int ok, X509_STORE_CTX *xsc)
89{
90 X509 *current_cert;
91 int verify_err;
92
93 current_cert = X509_STORE_CTX_get_current_cert(xsc);
94 if (current_cert != NULL) {
95 X509_NAME_print_ex_fp(stderr,
96 X509_get_subject_name(current_cert), 0,
97 XN_FLAG_ONELINE);
98 fprintf(stderr, "\n");
99 }
100
101 verify_err = X509_STORE_CTX_get_error(xsc);
102 if (verify_err != X509_V_OK) {
103 seen_depth = X509_STORE_CTX_get_error_depth(xsc);
104 seen_error = verify_err;
105 fprintf(stderr, "verify error at depth %d: %s\n",
106 X509_STORE_CTX_get_error_depth(xsc),
107 X509_verify_cert_error_string(verify_err));
108 }
109
110 fprintf(stderr, "chain of length %d\n", sk_X509_num (X509_STORE_CTX_get0_chain (xsc)));
111
112 return ok;
113}
114
115static void
116verify_cert(const char *roots_dir, const char *roots_file,
117 const char *bundle_file, const char*bundle_file2, int *chains, int mode)
118{
119 STACK_OF(X509) *roots = NULL, *bundle = NULL;
120 X509_STORE_CTX *xsc = NULL;
121 X509_STORE *store = NULL;
122 int verify_err, use_dir;
123 X509 *leaf = NULL;
124
125 *chains = 0;
126 use_dir = (mode == MODE_MODERN_VFY_DIR);
127
128 if (!use_dir && !certs_from_file(roots_file, &roots, 1))
129 errx(1, "failed to load roots from '%s'", roots_file);
130 if (!certs_from_file(bundle_file, &bundle, 1))
131 errx(1, "failed to load bundle from '%s'", bundle_file);
132 if (!certs_from_file(bundle_file, &bundle, 0))
133 errx(1, "failed to load bundle from '%s'", bundle_file2);
134 if (sk_X509_num(bundle) < 1)
135 errx(1, "not enough certs in bundle");
136 leaf = sk_X509_shift(bundle);
137
138 if ((xsc = X509_STORE_CTX_new()) == NULL)
139 errx(1, "X509_STORE_CTX");
140 if (use_dir && (store = X509_STORE_new()) == NULL)
141 errx(1, "X509_STORE");
142 if (!X509_STORE_CTX_init(xsc, store, leaf, bundle)) {
143 ERR_print_errors_fp(stderr);
144 errx(1, "failed to init store context");
145 }
146
147 if (use_dir) {
148 if (!X509_STORE_load_locations(store, NULL, roots_dir))
149 errx(1, "failed to set by_dir directory of %s", roots_dir);
150 }
151 if (mode == MODE_LEGACY_VFY)
152 X509_STORE_CTX_set_flags(xsc, X509_V_FLAG_LEGACY_VERIFY);
153 else
154 X509_VERIFY_PARAM_clear_flags(X509_STORE_CTX_get0_param(xsc),
155 X509_V_FLAG_LEGACY_VERIFY);
156
157 if (verbose)
158 X509_STORE_CTX_set_verify_cb(xsc, verify_cert_cb);
159 if (!use_dir)
160 X509_STORE_CTX_set0_trusted_stack(xsc, roots);
161
162 if (X509_verify_cert(xsc) == 1) {
163 *chains = 1; /* XXX */
164 goto done;
165 }
166
167 verify_err = X509_STORE_CTX_get_error(xsc);
168 if (verify_err == 0)
169 errx(1, "Error unset on failure!\n");
170
171 fprintf(stderr, "failed to verify at %d: %s\n",
172 X509_STORE_CTX_get_error_depth(xsc),
173 X509_verify_cert_error_string(verify_err));
174
175 done:
176 sk_X509_pop_free(roots, X509_free);
177 sk_X509_pop_free(bundle, X509_free);
178 X509_STORE_free(store);
179 X509_STORE_CTX_free(xsc);
180 X509_free(leaf);
181}
182
183struct verify_cert_test {
184 const char *id;
185 int want_chains;
186 int failing;
187 int depth;
188 int error;
189};
190
191struct verify_cert_test verify_cert_tests[] = {
192 {
193 .id = "1a",
194 .want_chains = 0,
195 .depth = 0,
196 .error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
197 },
198 {
199 .id = "2a",
200 .want_chains = 0,
201 .depth = 1,
202 .error = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
203 },
204 {
205 .id = "2c",
206 .want_chains = 0,
207 .depth = 2,
208 .error = X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN,
209 },
210};
211
212#define N_VERIFY_CERT_TESTS \
213 (sizeof(verify_cert_tests) / sizeof(*verify_cert_tests))
214
215static int
216verify_cert_test(const char *certs_path, int mode)
217{
218 char *roots_file, *bundle_file, *bundle_file2, *roots_dir;
219 struct verify_cert_test *vct;
220 int failed = 0;
221 int chains;
222 size_t i;
223
224 for (i = 0; i < N_VERIFY_CERT_TESTS; i++) {
225 vct = &verify_cert_tests[i];
226
227 if (asprintf(&roots_file, "/etc/ssl/cert.pem") == -1)
228 errx(1, "asprintf");
229 if (asprintf(&bundle_file, "%s/%s/bundle.pem", certs_path,
230 vct->id) == -1)
231 errx(1, "asprintf");
232 if (asprintf(&bundle_file2, "%s/%s/roots.pem", certs_path,
233 vct->id) == -1)
234 errx(1, "asprintf");
235 if (asprintf(&roots_dir, "./%s/roots", vct->id) == -1)
236 errx(1, "asprintf");
237
238 fprintf(stderr, "== Test %zu (%s)\n", i, vct->id);
239 fprintf(stderr, "== depth %d\n", vct->depth);
240 fprintf(stderr, "== error %d\n", vct->error);
241 expected_depth = vct->depth;
242 expected_error = vct->error;
243 verify_cert(roots_dir, roots_file, bundle_file, bundle_file2, &chains, mode);
244 if (chains == 0 && vct->want_chains == 0) {
245 if (seen_error != expected_error) {
246 fprintf(stderr, "FAIL: expected error %d, got %d\n",
247 seen_error, expected_error);
248 failed |= 1;
249 }
250 if (seen_depth != expected_depth) {
251 fprintf(stderr, "FAIL: expected depth %d, got %d\n",
252 seen_depth, expected_depth);
253 failed |= 1;
254 }
255 }
256
257 if ((mode == MODE_VERIFY && chains == vct->want_chains) ||
258 (chains == 0 && vct->want_chains == 0) ||
259 (chains == 1 && vct->want_chains > 0)) {
260 fprintf(stderr, "INFO: Succeeded with %d chains%s\n",
261 chains, vct->failing ? " (legacy failure)" : "");
262 if (mode == MODE_LEGACY_VFY && vct->failing)
263 failed |= 1;
264 } else {
265 fprintf(stderr, "FAIL: Failed with %d chains%s\n",
266 chains, vct->failing ? " (legacy failure)" : "");
267 if (!vct->failing)
268 failed |= 1;
269 }
270 fprintf(stderr, "\n");
271
272 free(roots_file);
273 free(bundle_file);
274 free(bundle_file2);
275 free(roots_dir);
276 }
277
278 return failed;
279}
280
281int
282main(int argc, char **argv)
283{
284 int failed = 0;
285
286 if (argc != 2) {
287 fprintf(stderr, "usage: %s <certs_path>\n", argv[0]);
288 exit(1);
289 }
290
291 fprintf(stderr, "\n\nTesting legacy x509_vfy\n");
292 failed |= verify_cert_test(argv[1], MODE_LEGACY_VFY);
293 fprintf(stderr, "\n\nTesting modern x509_vfy\n");
294 failed |= verify_cert_test(argv[1], MODE_MODERN_VFY);
295
296 return (failed);
297}