diff options
Diffstat (limited to 'src/lib/libtls/tls_ocsp.c')
-rw-r--r-- | src/lib/libtls/tls_ocsp.c | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/src/lib/libtls/tls_ocsp.c b/src/lib/libtls/tls_ocsp.c new file mode 100644 index 0000000000..113ab0dd3d --- /dev/null +++ b/src/lib/libtls/tls_ocsp.c | |||
@@ -0,0 +1,399 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2015 Marko Kreen <markokr@gmail.com> | ||
3 | * Copyright (c) 2016 Bob Beck <beck@openbsd.org> | ||
4 | * | ||
5 | * Permission to use, copy, modify, and distribute this software for any | ||
6 | * purpose with or without fee is hereby granted, provided that the above | ||
7 | * copyright notice and this permission notice appear in all copies. | ||
8 | * | ||
9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
16 | */ | ||
17 | |||
18 | #include <sys/types.h> | ||
19 | |||
20 | #include <arpa/inet.h> | ||
21 | #include <netinet/in.h> | ||
22 | |||
23 | #include <openssl/err.h> | ||
24 | #include <openssl/ocsp.h> | ||
25 | #include <openssl/x509.h> | ||
26 | |||
27 | #include <tls.h> | ||
28 | #include "tls_internal.h" | ||
29 | |||
30 | #define MAXAGE_SEC (14*24*60*60) | ||
31 | #define JITTER_SEC (60) | ||
32 | |||
33 | /* | ||
34 | * State for request. | ||
35 | */ | ||
36 | |||
37 | static struct tls_ocsp_ctx * | ||
38 | tls_ocsp_ctx_new(void) | ||
39 | { | ||
40 | return (calloc(1, sizeof(struct tls_ocsp_ctx))); | ||
41 | } | ||
42 | |||
43 | void | ||
44 | tls_ocsp_ctx_free(struct tls_ocsp_ctx *ocsp_ctx) | ||
45 | { | ||
46 | if (ocsp_ctx == NULL) | ||
47 | return; | ||
48 | |||
49 | free(ocsp_ctx->ocsp_result); | ||
50 | ocsp_ctx->ocsp_result = NULL; | ||
51 | free(ocsp_ctx->ocsp_url); | ||
52 | ocsp_ctx->ocsp_url = NULL; | ||
53 | free(ocsp_ctx->request_data); | ||
54 | ocsp_ctx->request_data = NULL; | ||
55 | free(ocsp_ctx); | ||
56 | } | ||
57 | |||
58 | static int | ||
59 | tls_ocsp_asn1_parse_time(struct tls *ctx, ASN1_GENERALIZEDTIME *gt, time_t *time) | ||
60 | { | ||
61 | struct tm tm; | ||
62 | |||
63 | if (gt == NULL) | ||
64 | return -1; | ||
65 | /* RFC 6960 specifies that all times in OCSP must be GENERALIZEDTIME */ | ||
66 | if (asn1_time_parse(gt->data, gt->length, &tm, | ||
67 | V_ASN1_GENERALIZEDTIME) == -1) | ||
68 | return -1; | ||
69 | if ((*time = timegm(&tm)) == -1) | ||
70 | return -1; | ||
71 | return 0; | ||
72 | } | ||
73 | |||
74 | static int | ||
75 | tls_ocsp_fill_info(struct tls *ctx, int response_status, int cert_status, | ||
76 | int crl_reason, ASN1_GENERALIZEDTIME *revtime, | ||
77 | ASN1_GENERALIZEDTIME *thisupd, ASN1_GENERALIZEDTIME *nextupd) | ||
78 | { | ||
79 | struct tls_ocsp_result *info = NULL; | ||
80 | |||
81 | free(ctx->ocsp_ctx->ocsp_result); | ||
82 | ctx->ocsp_ctx->ocsp_result = NULL; | ||
83 | |||
84 | if ((info = calloc(1, sizeof (struct tls_ocsp_result))) == NULL) { | ||
85 | tls_set_error(ctx, "calloc"); | ||
86 | return -1; | ||
87 | } | ||
88 | info->response_status = response_status; | ||
89 | info->cert_status = cert_status; | ||
90 | info->crl_reason = crl_reason; | ||
91 | if (info->response_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) { | ||
92 | info->result_msg = | ||
93 | OCSP_response_status_str(info->response_status); | ||
94 | } else if (info->cert_status != V_OCSP_CERTSTATUS_REVOKED) { | ||
95 | info->result_msg = OCSP_cert_status_str(info->cert_status); | ||
96 | } else { | ||
97 | info->result_msg = OCSP_crl_reason_str(info->crl_reason); | ||
98 | } | ||
99 | info->revocation_time = info->this_update = info->next_update = -1; | ||
100 | if (revtime != NULL && | ||
101 | tls_ocsp_asn1_parse_time(ctx, revtime, &info->revocation_time) != 0) { | ||
102 | tls_set_error(ctx, | ||
103 | "unable to parse revocation time in OCSP reply"); | ||
104 | goto error; | ||
105 | } | ||
106 | if (thisupd != NULL && | ||
107 | tls_ocsp_asn1_parse_time(ctx, thisupd, &info->this_update) != 0) { | ||
108 | tls_set_error(ctx, | ||
109 | "unable to parse this update time in OCSP reply"); | ||
110 | goto error; | ||
111 | } | ||
112 | if (nextupd != NULL && | ||
113 | tls_ocsp_asn1_parse_time(ctx, nextupd, &info->next_update) != 0) { | ||
114 | tls_set_error(ctx, | ||
115 | "unable to parse next update time in OCSP reply"); | ||
116 | goto error; | ||
117 | } | ||
118 | ctx->ocsp_ctx->ocsp_result = info; | ||
119 | return 0; | ||
120 | error: | ||
121 | free(info); | ||
122 | return -1; | ||
123 | } | ||
124 | |||
125 | static OCSP_CERTID * | ||
126 | tls_ocsp_get_certid(X509 *main_cert, STACK_OF(X509) *extra_certs, | ||
127 | SSL_CTX *ssl_ctx) | ||
128 | { | ||
129 | X509_NAME *issuer_name; | ||
130 | X509 *issuer; | ||
131 | X509_STORE_CTX storectx; | ||
132 | X509_OBJECT tmpobj; | ||
133 | OCSP_CERTID *cid = NULL; | ||
134 | X509_STORE *store; | ||
135 | |||
136 | if ((issuer_name = X509_get_issuer_name(main_cert)) == NULL) | ||
137 | return NULL; | ||
138 | |||
139 | if (extra_certs != NULL) { | ||
140 | issuer = X509_find_by_subject(extra_certs, issuer_name); | ||
141 | if (issuer != NULL) | ||
142 | return OCSP_cert_to_id(NULL, main_cert, issuer); | ||
143 | } | ||
144 | |||
145 | if ((store = SSL_CTX_get_cert_store(ssl_ctx)) == NULL) | ||
146 | return NULL; | ||
147 | if (X509_STORE_CTX_init(&storectx, store, main_cert, extra_certs) != 1) | ||
148 | return NULL; | ||
149 | if (X509_STORE_get_by_subject(&storectx, X509_LU_X509, issuer_name, | ||
150 | &tmpobj) == 1) { | ||
151 | cid = OCSP_cert_to_id(NULL, main_cert, tmpobj.data.x509); | ||
152 | X509_OBJECT_free_contents(&tmpobj); | ||
153 | } | ||
154 | X509_STORE_CTX_cleanup(&storectx); | ||
155 | return cid; | ||
156 | } | ||
157 | |||
158 | struct tls_ocsp_ctx * | ||
159 | tls_ocsp_setup_from_peer(struct tls *ctx) | ||
160 | { | ||
161 | struct tls_ocsp_ctx *ocsp_ctx = NULL; | ||
162 | STACK_OF(OPENSSL_STRING) *ocsp_urls = NULL; | ||
163 | |||
164 | if ((ocsp_ctx = tls_ocsp_ctx_new()) == NULL) | ||
165 | goto failed; | ||
166 | |||
167 | /* steal state from ctx struct */ | ||
168 | ocsp_ctx->main_cert = SSL_get_peer_certificate(ctx->ssl_conn); | ||
169 | ocsp_ctx->extra_certs = SSL_get_peer_cert_chain(ctx->ssl_conn); | ||
170 | if (ocsp_ctx->main_cert == NULL) { | ||
171 | tls_set_errorx(ctx, "no peer certificate for OCSP"); | ||
172 | goto failed; | ||
173 | } | ||
174 | |||
175 | ocsp_urls = X509_get1_ocsp(ocsp_ctx->main_cert); | ||
176 | if (ocsp_urls == NULL) | ||
177 | goto failed; | ||
178 | ocsp_ctx->ocsp_url = strdup(sk_OPENSSL_STRING_value(ocsp_urls, 0)); | ||
179 | if (ocsp_ctx->ocsp_url == NULL) { | ||
180 | tls_set_errorx(ctx, "out of memory"); | ||
181 | goto failed; | ||
182 | } | ||
183 | |||
184 | X509_email_free(ocsp_urls); | ||
185 | return ocsp_ctx; | ||
186 | |||
187 | failed: | ||
188 | tls_ocsp_ctx_free(ocsp_ctx); | ||
189 | X509_email_free(ocsp_urls); | ||
190 | return NULL; | ||
191 | } | ||
192 | |||
193 | static int | ||
194 | tls_ocsp_verify_response(struct tls *ctx, OCSP_RESPONSE *resp) | ||
195 | { | ||
196 | OCSP_BASICRESP *br = NULL; | ||
197 | ASN1_GENERALIZEDTIME *revtime = NULL, *thisupd = NULL, *nextupd = NULL; | ||
198 | OCSP_CERTID *cid = NULL; | ||
199 | STACK_OF(X509) *combined = NULL; | ||
200 | int response_status=0, cert_status=0, crl_reason=0; | ||
201 | int ret = -1; | ||
202 | unsigned long flags; | ||
203 | |||
204 | if ((br = OCSP_response_get1_basic(resp)) == NULL) { | ||
205 | tls_set_errorx(ctx, "cannot load ocsp reply"); | ||
206 | goto error; | ||
207 | } | ||
208 | |||
209 | /* | ||
210 | * Skip validation of 'extra_certs' as this should be done | ||
211 | * already as part of main handshake. | ||
212 | */ | ||
213 | flags = OCSP_TRUSTOTHER; | ||
214 | |||
215 | /* now verify */ | ||
216 | if (OCSP_basic_verify(br, ctx->ocsp_ctx->extra_certs, | ||
217 | SSL_CTX_get_cert_store(ctx->ssl_ctx), flags) != 1) { | ||
218 | tls_set_error(ctx, "ocsp verify failed"); | ||
219 | goto error; | ||
220 | } | ||
221 | |||
222 | /* signature OK, look inside */ | ||
223 | response_status = OCSP_response_status(resp); | ||
224 | if (response_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) { | ||
225 | tls_set_errorx(ctx, "ocsp verify failed: response - %s", | ||
226 | OCSP_response_status_str(response_status)); | ||
227 | goto error; | ||
228 | } | ||
229 | |||
230 | cid = tls_ocsp_get_certid(ctx->ocsp_ctx->main_cert, | ||
231 | ctx->ocsp_ctx->extra_certs, ctx->ssl_ctx); | ||
232 | if (cid == NULL) { | ||
233 | tls_set_errorx(ctx, "ocsp verify failed: no issuer cert"); | ||
234 | goto error; | ||
235 | } | ||
236 | |||
237 | if (OCSP_resp_find_status(br, cid, &cert_status, &crl_reason, | ||
238 | &revtime, &thisupd, &nextupd) != 1) { | ||
239 | tls_set_errorx(ctx, "ocsp verify failed: no result for cert"); | ||
240 | goto error; | ||
241 | } | ||
242 | |||
243 | if (OCSP_check_validity(thisupd, nextupd, JITTER_SEC, | ||
244 | MAXAGE_SEC) != 1) { | ||
245 | tls_set_errorx(ctx, | ||
246 | "ocsp verify failed: ocsp response not current"); | ||
247 | goto error; | ||
248 | } | ||
249 | |||
250 | if (tls_ocsp_fill_info(ctx, response_status, cert_status, | ||
251 | crl_reason, revtime, thisupd, nextupd) != 0) | ||
252 | goto error; | ||
253 | |||
254 | /* finally can look at status */ | ||
255 | if (cert_status != V_OCSP_CERTSTATUS_GOOD && cert_status != | ||
256 | V_OCSP_CERTSTATUS_UNKNOWN) { | ||
257 | tls_set_errorx(ctx, "ocsp verify failed: revoked cert - %s", | ||
258 | OCSP_crl_reason_str(crl_reason)); | ||
259 | goto error; | ||
260 | } | ||
261 | |||
262 | ret = 0; | ||
263 | |||
264 | error: | ||
265 | sk_X509_free(combined); | ||
266 | OCSP_CERTID_free(cid); | ||
267 | OCSP_BASICRESP_free(br); | ||
268 | return ret; | ||
269 | } | ||
270 | |||
271 | /* TLS handshake verification callback for stapled requests */ | ||
272 | int | ||
273 | tls_ocsp_verify_cb(SSL *ssl, void *arg) | ||
274 | { | ||
275 | const unsigned char *raw = NULL; | ||
276 | int size, res = -1; | ||
277 | struct tls *ctx; | ||
278 | |||
279 | if ((ctx = SSL_get_app_data(ssl)) == NULL) | ||
280 | return -1; | ||
281 | |||
282 | size = SSL_get_tlsext_status_ocsp_resp(ssl, &raw); | ||
283 | if (size <= 0) | ||
284 | return 1; | ||
285 | |||
286 | tls_ocsp_ctx_free(ctx->ocsp_ctx); | ||
287 | ctx->ocsp_ctx = tls_ocsp_setup_from_peer(ctx); | ||
288 | if (ctx->ocsp_ctx != NULL) | ||
289 | res = tls_ocsp_process_response(ctx, raw, size); | ||
290 | |||
291 | return (res == 0) ? 1 : 0; | ||
292 | } | ||
293 | |||
294 | /* | ||
295 | * Public API | ||
296 | */ | ||
297 | |||
298 | /* Retrieve OSCP URL from peer certificate, if present */ | ||
299 | const char * | ||
300 | tls_peer_ocsp_url(struct tls *ctx) | ||
301 | { | ||
302 | if (ctx->ocsp_ctx == NULL) | ||
303 | return NULL; | ||
304 | return ctx->ocsp_ctx->ocsp_url; | ||
305 | } | ||
306 | |||
307 | const char * | ||
308 | tls_peer_ocsp_result(struct tls *ctx) | ||
309 | { | ||
310 | if (ctx->ocsp_ctx == NULL) | ||
311 | return NULL; | ||
312 | if (ctx->ocsp_ctx->ocsp_result == NULL) | ||
313 | return NULL; | ||
314 | return ctx->ocsp_ctx->ocsp_result->result_msg; | ||
315 | } | ||
316 | |||
317 | int | ||
318 | tls_peer_ocsp_response_status(struct tls *ctx) | ||
319 | { | ||
320 | if (ctx->ocsp_ctx == NULL) | ||
321 | return -1; | ||
322 | if (ctx->ocsp_ctx->ocsp_result == NULL) | ||
323 | return -1; | ||
324 | return ctx->ocsp_ctx->ocsp_result->response_status; | ||
325 | } | ||
326 | |||
327 | int | ||
328 | tls_peer_ocsp_cert_status(struct tls *ctx) | ||
329 | { | ||
330 | if (ctx->ocsp_ctx == NULL) | ||
331 | return -1; | ||
332 | if (ctx->ocsp_ctx->ocsp_result == NULL) | ||
333 | return -1; | ||
334 | return ctx->ocsp_ctx->ocsp_result->cert_status; | ||
335 | } | ||
336 | |||
337 | int | ||
338 | tls_peer_ocsp_crl_reason(struct tls *ctx) | ||
339 | { | ||
340 | if (ctx->ocsp_ctx == NULL) | ||
341 | return -1; | ||
342 | if (ctx->ocsp_ctx->ocsp_result == NULL) | ||
343 | return -1; | ||
344 | return ctx->ocsp_ctx->ocsp_result->crl_reason; | ||
345 | } | ||
346 | |||
347 | time_t | ||
348 | tls_peer_ocsp_this_update(struct tls *ctx) | ||
349 | { | ||
350 | if (ctx->ocsp_ctx == NULL) | ||
351 | return -1; | ||
352 | if (ctx->ocsp_ctx->ocsp_result == NULL) | ||
353 | return -1; | ||
354 | return ctx->ocsp_ctx->ocsp_result->this_update; | ||
355 | } | ||
356 | |||
357 | time_t | ||
358 | tls_peer_ocsp_next_update(struct tls *ctx) | ||
359 | { | ||
360 | if (ctx->ocsp_ctx == NULL) | ||
361 | return -1; | ||
362 | if (ctx->ocsp_ctx->ocsp_result == NULL) | ||
363 | return -1; | ||
364 | return ctx->ocsp_ctx->ocsp_result->next_update; | ||
365 | } | ||
366 | |||
367 | time_t | ||
368 | tls_peer_ocsp_revocation_time(struct tls *ctx) | ||
369 | { | ||
370 | if (ctx->ocsp_ctx == NULL) | ||
371 | return -1; | ||
372 | if (ctx->ocsp_ctx->ocsp_result == NULL) | ||
373 | return -1; | ||
374 | return ctx->ocsp_ctx->ocsp_result->revocation_time; | ||
375 | } | ||
376 | |||
377 | /* | ||
378 | * Process a raw OCSP response from an OCSP server request. | ||
379 | * OCSP details can then be retrieved with tls_peer_ocsp_* functions. | ||
380 | * returns 0 if certificate ok, -1 otherwise. | ||
381 | */ | ||
382 | int | ||
383 | tls_ocsp_process_response(struct tls *ctx, const unsigned char *response, | ||
384 | size_t size) | ||
385 | { | ||
386 | int ret; | ||
387 | OCSP_RESPONSE *resp; | ||
388 | |||
389 | resp = d2i_OCSP_RESPONSE(NULL, &response, size); | ||
390 | if (resp == NULL) { | ||
391 | tls_ocsp_ctx_free(ctx->ocsp_ctx); | ||
392 | ctx->ocsp_ctx = NULL; | ||
393 | tls_set_error(ctx, "unable to parse OCSP response"); | ||
394 | return -1; | ||
395 | } | ||
396 | ret = tls_ocsp_verify_response(ctx, resp); | ||
397 | OCSP_RESPONSE_free(resp); | ||
398 | return ret; | ||
399 | } | ||