source: mod_gnutls/src/gnutls_ocsp.c @ 6b4136c

debian/masterdebian/stretch-backportsupstream
Last change on this file since 6b4136c was 6b4136c, checked in by Thomas Klute <thomas2.klute@…>, 4 years ago

Store OCSP responses in DBM cache before use

This is not proper caching yet (the cached response is updated on
every use), but it shows the approach to be used. Memcached support to
be added once DBM works properly.

  • Property mode set to 100644
File size: 14.9 KB
Line 
1/**
2 *  Copyright 2016 Thomas Klute
3 *
4 *  Licensed under the Apache License, Version 2.0 (the "License");
5 *  you may not use this file except in compliance with the License.
6 *  You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 *  Unless required by applicable law or agreed to in writing, software
11 *  distributed under the License is distributed on an "AS IS" BASIS,
12 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 *  See the License for the specific language governing permissions and
14 *  limitations under the License.
15 */
16
17#include "gnutls_ocsp.h"
18#include "mod_gnutls.h"
19#include "gnutls_cache.h"
20
21#include <apr_lib.h>
22#include <apr_time.h>
23#include <gnutls/ocsp.h>
24#include <time.h>
25
26#ifdef APLOG_USE_MODULE
27APLOG_USE_MODULE(gnutls);
28#endif
29
30
31
32#define _log_one_ocsp_fail(s, c)                                      \
33    ap_log_cerror(APLOG_MARK, APLOG_INFO, APR_EGENERAL, (c),           \
34                  "Reason for failed OCSP response verification: %s", (s))
35/*
36 * Log all matching reasons for verification failure
37 */
38static void _log_verify_fail_reason(const unsigned int verify, conn_rec *c)
39{
40    if (verify & GNUTLS_OCSP_VERIFY_SIGNER_NOT_FOUND)
41        _log_one_ocsp_fail("Signer cert not found", c);
42
43    if (verify & GNUTLS_OCSP_VERIFY_SIGNER_KEYUSAGE_ERROR)
44        _log_one_ocsp_fail("Signer cert keyusage error", c);
45
46    if (verify & GNUTLS_OCSP_VERIFY_UNTRUSTED_SIGNER)
47        _log_one_ocsp_fail("Signer cert is not trusted", c);
48
49    if (verify & GNUTLS_OCSP_VERIFY_INSECURE_ALGORITHM)
50        _log_one_ocsp_fail("Insecure algorithm", c);
51
52    if (verify & GNUTLS_OCSP_VERIFY_SIGNATURE_FAILURE)
53        _log_one_ocsp_fail("Signature failure", c);
54
55    if (verify & GNUTLS_OCSP_VERIFY_CERT_NOT_ACTIVATED)
56        _log_one_ocsp_fail("Signer cert not yet activated", c);
57
58    if (verify & GNUTLS_OCSP_VERIFY_CERT_EXPIRED)
59        _log_one_ocsp_fail("Signer cert expired", c);
60}
61
62
63
64const char *mgs_store_ocsp_response_path(cmd_parms *parms,
65                                         void *dummy __attribute__((unused)),
66                                         const char *arg)
67{
68    mgs_srvconf_rec *sc = (mgs_srvconf_rec *)
69        ap_get_module_config(parms->server->module_config, &gnutls_module);
70
71    sc->ocsp_response_file = ap_server_root_relative(parms->pool, arg);
72    return NULL;
73}
74
75
76
77/**
78 * Check if the provided OCSP response is usable for stapling in this
79 * connection context. Returns GNUTLS_E_SUCCESS if yes.
80 */
81int check_ocsp_response(mgs_handle_t *ctxt, const gnutls_datum_t *ocsp_response)
82{
83    if (ctxt->sc->ocsp_trust == NULL)
84    {
85        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, ctxt->c,
86                      "No OCSP trust list available for server \"%s\"!",
87                      ctxt->c->base_server->server_hostname);
88        return GNUTLS_E_NO_CERTIFICATE_FOUND;
89    }
90
91    gnutls_ocsp_resp_t resp;
92    int ret = gnutls_ocsp_resp_init(&resp);
93    if (ret != GNUTLS_E_SUCCESS)
94    {
95        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, ctxt->c,
96                      "Could not initialize OCSP response structure: %s (%d)",
97                      gnutls_strerror(ret), ret);
98        goto resp_cleanup;
99    }
100    ret = gnutls_ocsp_resp_import(resp, ocsp_response);
101    if (ret != GNUTLS_E_SUCCESS)
102    {
103        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, ctxt->c,
104                      "Importing OCSP response failed: %s (%d)",
105                      gnutls_strerror(ret), ret);
106        goto resp_cleanup;
107    }
108
109    ret = gnutls_ocsp_resp_check_crt(resp, 0,
110                                     ctxt->sc->certs_x509_crt_chain[0]);
111    if (ret != GNUTLS_E_SUCCESS)
112    {
113        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, ctxt->c,
114                      "OCSP response is not for server certificate: %s (%d)",
115                      gnutls_strerror(ret), ret);
116        goto resp_cleanup;
117    }
118
119    unsigned int verify;
120    ret = gnutls_ocsp_resp_verify(resp, *(ctxt->sc->ocsp_trust), &verify, 0);
121    if (ret != GNUTLS_E_SUCCESS)
122    {
123        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, ctxt->c,
124                      "OCSP response verification failed: %s (%d)",
125                      gnutls_strerror(ret), ret);
126        goto resp_cleanup;
127    }
128    else
129    {
130        /* verification worked, check the result */
131        if (verify != 0)
132        {
133            _log_verify_fail_reason(verify, ctxt->c);
134            ret = GNUTLS_E_OCSP_RESPONSE_ERROR;
135            goto resp_cleanup;
136        }
137        else
138            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ctxt->c,
139                          "OCSP response is valid.");
140    }
141
142    /* OK, response is for our certificate and valid, let's get the
143     * actual response data. */
144    unsigned int cert_status;
145    time_t this_update;
146    time_t next_update;
147    ret = gnutls_ocsp_resp_get_single(resp, 0, NULL, NULL, NULL, NULL,
148                                      &cert_status, &this_update,
149                                      &next_update, NULL, NULL);
150    if (ret != GNUTLS_E_SUCCESS)
151    {
152        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, ctxt->c,
153                      "Could not get OCSP response data: %s (%d)",
154                      gnutls_strerror(ret), ret);
155        goto resp_cleanup;
156    }
157
158    apr_time_t now = apr_time_now();
159    apr_time_t valid_at;
160    apr_time_ansi_put(&valid_at, this_update);
161    /* Buffer for human-readable times produced by apr_rfc822_date,
162     * see apr_time.h */
163    char date_str[APR_RFC822_DATE_LEN];
164    apr_rfc822_date(date_str, valid_at);
165
166    if (now < valid_at)
167    {
168        /* We don't know if our clock or that of the OCSP responder is
169         * out of sync, so warn but continue. */
170        ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_EGENERAL, ctxt->c,
171                      "OSCP response claims to be from future (%s), check "
172                      "time synchronization!", date_str);
173    }
174
175    if (next_update == (time_t) -1)
176        ap_log_cerror(APLOG_MARK, APLOG_INFO, APR_SUCCESS, ctxt->c,
177                      "OSCP response does not contain nextUpdate info.");
178    else
179    {
180        apr_time_t valid_to;
181        apr_time_ansi_put(&valid_to, next_update);
182        if (now > valid_to)
183        {
184            apr_rfc822_date(date_str, valid_to);
185            ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, ctxt->c,
186                          "OCSP response has expired at %s!", date_str);
187            /* Do not send a stale response */
188            ret = GNUTLS_E_OCSP_RESPONSE_ERROR;
189            goto resp_cleanup;
190        }
191    }
192
193    /* What's the actual status? Will be one of
194     * gnutls_ocsp_cert_status_t as defined in gnutls/ocsp.h. */
195    if (cert_status == GNUTLS_OCSP_CERT_GOOD)
196    {
197        /* Yay, everything's good! */
198        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ctxt->c,
199                      "CA flagged certificate as valid at %s.", date_str);
200    }
201    else
202    {
203        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, ctxt->c,
204                      "CA flagged certificate as %s at %s.",
205                      cert_status == GNUTLS_OCSP_CERT_REVOKED ?
206                      "revoked" : "unknown", date_str);
207        ret = GNUTLS_E_OCSP_RESPONSE_ERROR;
208    }
209
210 resp_cleanup:
211    gnutls_ocsp_resp_deinit(resp);
212    return ret;
213}
214
215
216
217/*
218 * Returns the certificate fingerprint, memory is allocated from p.
219 */
220static gnutls_datum_t mgs_get_cert_fingerprint(apr_pool_t *p,
221                                               gnutls_x509_crt_t cert)
222{
223    gnutls_datum_t fingerprint = {NULL, 0};
224    size_t fplen;
225    gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_SHA1, NULL, &fplen);
226    unsigned char * fp = apr_palloc(p, fplen);
227    gnutls_x509_crt_get_fingerprint(cert, GNUTLS_DIG_SHA1, fp, &fplen);
228    // TODO: Prevent overflow
229    fingerprint.size = fplen;
230    fingerprint.data = fp;
231    return fingerprint;
232}
233
234
235
236/* TODO: response should be fetched from sc->ocsp_uri */
237apr_status_t mgs_cache_ocsp_response(server_rec *s)
238{
239    mgs_srvconf_rec *sc = (mgs_srvconf_rec *)
240        ap_get_module_config(s->module_config, &gnutls_module);
241
242    if (sc->cache_type != mgs_cache_dbm && sc->cache_type != mgs_cache_gdbm)
243    {
244        /* experimental OCSP cache requires DBM cache */
245        return APR_ENOTIMPL;
246    }
247
248    apr_pool_t *tmp;
249    apr_status_t rv = apr_pool_create(&tmp, NULL);
250
251    /* the fingerprint will be used as cache key */
252    gnutls_datum_t fingerprint =
253        mgs_get_cert_fingerprint(tmp, sc->certs_x509_crt_chain[0]);
254    if (fingerprint.data == NULL)
255        return APR_EINVAL;
256
257    ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, s,
258                 "Loading OCSP response from %s",
259                 sc->ocsp_response_file);
260    apr_file_t *file;
261    apr_finfo_t finfo;
262    apr_size_t br = 0;
263    rv = apr_file_open(&file, sc->ocsp_response_file,
264                       APR_READ | APR_BINARY, APR_OS_DEFAULT, tmp);
265    if (rv != APR_SUCCESS)
266    {
267        apr_pool_destroy(tmp);
268        return rv;
269    }
270    rv = apr_file_info_get(&finfo, APR_FINFO_SIZE, file);
271    if (rv != APR_SUCCESS)
272    {
273        apr_pool_destroy(tmp);
274        return rv;
275    }
276
277    gnutls_datum_t resp;
278    resp.data = apr_palloc(tmp, finfo.size);
279    rv = apr_file_read_full(file, resp.data, finfo.size, &br);
280    if (rv != APR_SUCCESS)
281    {
282        apr_pool_destroy(tmp);
283        return rv;
284    }
285    apr_file_close(file);
286    // TODO: Prevent overflow
287    resp.size = br;
288
289
290    /* TODO: make cache lifetime configurable */
291    int r = dbm_cache_store(s, fingerprint,
292                            resp, apr_time_now() + apr_time_from_sec(120));
293    /* destroy pool, and original copy of the OCSP response with it */
294    apr_pool_destroy(tmp);
295    if (r != 0)
296    {
297        ap_log_error(APLOG_MARK, APLOG_ERR, APR_EGENERAL, s,
298                      "Storing OCSP response in cache failed.");
299        return APR_EGENERAL;
300    }
301    return APR_SUCCESS;
302}
303
304
305
306int mgs_get_ocsp_response(gnutls_session_t session __attribute__((unused)),
307                          void *ptr,
308                          gnutls_datum_t *ocsp_response)
309{
310    mgs_handle_t *ctxt = (mgs_handle_t *) ptr;
311
312    apr_status_t rv = mgs_cache_ocsp_response(ctxt->c->base_server);
313    if (rv != APR_SUCCESS)
314    {
315        ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, ctxt->c,
316                      "Updating OCSP response cache failed");
317        return GNUTLS_E_NO_CERTIFICATE_STATUS;
318    }
319
320    gnutls_datum_t fingerprint =
321        mgs_get_cert_fingerprint(ctxt->c->pool,
322                                 ctxt->sc->certs_x509_crt_chain[0]);
323    if (fingerprint.data == NULL)
324        return GNUTLS_E_NO_CERTIFICATE_STATUS;
325
326    *ocsp_response = dbm_cache_fetch(ctxt, fingerprint);
327    if (ocsp_response->size == 0)
328    {
329        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, ctxt->c,
330                      "Fetching OCSP response from cache failed.");
331    }
332    else
333    {
334        /* Succeed if response is present and valid. */
335        if (check_ocsp_response(ctxt, ocsp_response) == GNUTLS_E_SUCCESS)
336            return GNUTLS_E_SUCCESS;
337    }
338
339    /* failure, clean up response data */
340    gnutls_free(ocsp_response->data);
341    ocsp_response->size = 0;
342    ocsp_response->data = NULL;
343    return GNUTLS_E_NO_CERTIFICATE_STATUS;
344}
345
346
347
348int mgs_create_ocsp_trust_list(gnutls_x509_trust_list_t *tl,
349                               const gnutls_x509_crt_t *chain,
350                               const int num)
351{
352    int added = 0;
353    int ret = gnutls_x509_trust_list_init(tl, num);
354
355    if (ret == GNUTLS_E_SUCCESS)
356        added = gnutls_x509_trust_list_add_cas(*tl, chain, num, 0);
357
358    if (added != num)
359        ret = GNUTLS_E_CERTIFICATE_ERROR;
360
361    /* Clean up trust list in case of error */
362    if (ret != GNUTLS_E_SUCCESS)
363        gnutls_x509_trust_list_deinit(*tl, 0);
364
365    return ret;
366}
367
368
369
370apr_status_t mgs_cleanup_trust_list(void *data)
371{
372    gnutls_x509_trust_list_t *tl = (gnutls_x509_trust_list_t *) data;
373    gnutls_x509_trust_list_deinit(*tl, 0);
374    return APR_SUCCESS;
375}
376
377
378
379apr_uri_t * mgs_cert_get_ocsp_uri(apr_pool_t *p, gnutls_x509_crt_t cert)
380{
381    apr_pool_t *tmp;
382    apr_status_t rv = apr_pool_create(&tmp, p);
383    if (rv != APR_SUCCESS)
384        return NULL;
385
386    apr_uri_t *ocsp_uri = NULL;
387
388    int ret = GNUTLS_E_SUCCESS;
389    /* search authority info access for OCSP URI */
390    for (int seq = 0; ret != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; seq++)
391    {
392        gnutls_datum_t ocsp_access_data;
393        ret = gnutls_x509_crt_get_authority_info_access(cert, seq,
394                                                        GNUTLS_IA_OCSP_URI,
395                                                        &ocsp_access_data,
396                                                        NULL);
397        if (ret == GNUTLS_E_SUCCESS)
398        {
399            /* create NULL terminated string */
400            char *ocsp_str =
401                apr_pstrndup(tmp, (const char*) ocsp_access_data.data,
402                             ocsp_access_data.size);
403            gnutls_free(ocsp_access_data.data);
404
405            ocsp_uri = apr_palloc(p, sizeof(apr_uri_t));
406            rv = apr_uri_parse(p, ocsp_str, ocsp_uri);
407            if (rv == APR_SUCCESS)
408                break;
409            else
410                ocsp_uri = NULL;
411        }
412    }
413
414    apr_pool_destroy(tmp);
415    return ocsp_uri;
416}
417
418
419
420/*
421 * Like in the general post_config hook the HTTP status codes for
422 * errors are just for fun. What matters is "neither OK nor DECLINED"
423 * to denote an error.
424 */
425int mgs_ocsp_post_config_server(apr_pool_t *pconf,
426                                apr_pool_t *ptemp __attribute__((unused)),
427                                server_rec *server)
428{
429    mgs_srvconf_rec *sc = (mgs_srvconf_rec *)
430        ap_get_module_config(server->module_config, &gnutls_module);
431
432    if (sc->certs_x509_chain_num < 2)
433    {
434        ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, server,
435                     "OCSP stapling is enabled but no CA certificate "
436                     "available for %s:%d, make sure it is included in "
437                     "GnuTLSCertificateFile!",
438                     server->server_hostname, server->addrs->host_port);
439        return HTTP_NOT_FOUND;
440    }
441
442    sc->ocsp_uri = mgs_cert_get_ocsp_uri(pconf, sc->certs_x509_crt_chain[0]);
443
444    sc->ocsp_trust = apr_palloc(pconf,
445                                sizeof(gnutls_x509_trust_list_t));
446     /* Only the direct issuer may sign the OCSP response or an OCSP
447      * signer. */
448    int ret = mgs_create_ocsp_trust_list(sc->ocsp_trust,
449                                         &(sc->certs_x509_crt_chain[1]),
450                                         1);
451    if (ret != GNUTLS_E_SUCCESS)
452    {
453        ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, server,
454                     "Could not create OCSP trust list: %s (%d)",
455                     gnutls_strerror(ret), ret);
456        return HTTP_INTERNAL_SERVER_ERROR;
457    }
458    /* deinit trust list when the config pool is destroyed */
459    apr_pool_cleanup_register(pconf, sc->ocsp_trust,
460                              mgs_cleanup_trust_list,
461                              apr_pool_cleanup_null);
462
463    return OK;
464}
Note: See TracBrowser for help on using the repository browser.