summaryrefslogtreecommitdiff
path: root/src/regress/lib/libcrypto
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/regress/lib/libcrypto
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/regress/lib/libcrypto')
-rw-r--r--src/regress/lib/libcrypto/x509/Makefile8
-rw-r--r--src/regress/lib/libcrypto/x509/callbackfailures.c297
2 files changed, 303 insertions, 2 deletions
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}