[nginx] OCSP: certificate status cache.
Roman Arutyunyan
arut at nginx.com
Sat May 23 10:35:53 UTC 2020
details: https://hg.nginx.org/nginx/rev/b56f725dd4bb
branches:
changeset: 7654:b56f725dd4bb
user: Roman Arutyunyan <arut at nginx.com>
date: Fri May 22 17:25:27 2020 +0300
description:
OCSP: certificate status cache.
When enabled, certificate status is stored in cache and is used to validate
the certificate in future requests.
New directive ssl_ocsp_cache is added to configure the cache.
diffstat:
src/event/ngx_event_openssl.h | 3 +-
src/event/ngx_event_openssl_stapling.c | 308 ++++++++++++++++++++++++++++++++-
src/http/modules/ngx_http_ssl_module.c | 94 +++++++++-
src/http/modules/ngx_http_ssl_module.h | 1 +
4 files changed, 401 insertions(+), 5 deletions(-)
diffs (570 lines):
diff -r 8409f9df6219 -r b56f725dd4bb src/event/ngx_event_openssl.h
--- a/src/event/ngx_event_openssl.h Fri May 22 17:30:12 2020 +0300
+++ b/src/event/ngx_event_openssl.h Fri May 22 17:25:27 2020 +0300
@@ -187,12 +187,13 @@ ngx_int_t ngx_ssl_stapling(ngx_conf_t *c
ngx_int_t ngx_ssl_stapling_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl,
ngx_resolver_t *resolver, ngx_msec_t resolver_timeout);
ngx_int_t ngx_ssl_ocsp(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *responder,
- ngx_uint_t depth);
+ ngx_uint_t depth, ngx_shm_zone_t *shm_zone);
ngx_int_t ngx_ssl_ocsp_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl,
ngx_resolver_t *resolver, ngx_msec_t resolver_timeout);
ngx_int_t ngx_ssl_ocsp_validate(ngx_connection_t *c);
ngx_int_t ngx_ssl_ocsp_get_status(ngx_connection_t *c, const char **s);
void ngx_ssl_ocsp_cleanup(ngx_connection_t *c);
+ngx_int_t ngx_ssl_ocsp_cache_init(ngx_shm_zone_t *shm_zone, void *data);
RSA *ngx_ssl_rsa512_key_callback(ngx_ssl_conn_t *ssl_conn, int is_export,
int key_length);
ngx_array_t *ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file);
diff -r 8409f9df6219 -r b56f725dd4bb src/event/ngx_event_openssl_stapling.c
--- a/src/event/ngx_event_openssl_stapling.c Fri May 22 17:30:12 2020 +0300
+++ b/src/event/ngx_event_openssl_stapling.c Fri May 22 17:25:27 2020 +0300
@@ -52,11 +52,28 @@ typedef struct {
in_port_t port;
ngx_uint_t depth;
+ ngx_shm_zone_t *shm_zone;
+
ngx_resolver_t *resolver;
ngx_msec_t resolver_timeout;
} ngx_ssl_ocsp_conf_t;
+typedef struct {
+ ngx_rbtree_t rbtree;
+ ngx_rbtree_node_t sentinel;
+ ngx_queue_t expire_queue;
+} ngx_ssl_ocsp_cache_t;
+
+
+typedef struct {
+ ngx_str_node_t node;
+ ngx_queue_t queue;
+ int status;
+ time_t valid;
+} ngx_ssl_ocsp_cache_node_t;
+
+
typedef struct ngx_ssl_ocsp_ctx_s ngx_ssl_ocsp_ctx_t;
@@ -100,10 +117,13 @@ struct ngx_ssl_ocsp_ctx_s {
void (*handler)(ngx_ssl_ocsp_ctx_t *ctx);
void *data;
+ ngx_str_t key;
ngx_buf_t *request;
ngx_buf_t *response;
ngx_peer_connection_t peer;
+ ngx_shm_zone_t *shm_zone;
+
ngx_int_t (*process)(ngx_ssl_ocsp_ctx_t *ctx);
ngx_uint_t state;
@@ -164,6 +184,10 @@ static ngx_int_t ngx_ssl_ocsp_parse_head
static ngx_int_t ngx_ssl_ocsp_process_body(ngx_ssl_ocsp_ctx_t *ctx);
static ngx_int_t ngx_ssl_ocsp_verify(ngx_ssl_ocsp_ctx_t *ctx);
+static ngx_int_t ngx_ssl_ocsp_cache_lookup(ngx_ssl_ocsp_ctx_t *ctx);
+static ngx_int_t ngx_ssl_ocsp_cache_store(ngx_ssl_ocsp_ctx_t *ctx);
+static ngx_int_t ngx_ssl_ocsp_create_key(ngx_ssl_ocsp_ctx_t *ctx);
+
static u_char *ngx_ssl_ocsp_log_error(ngx_log_t *log, u_char *buf, size_t len);
@@ -743,7 +767,7 @@ ngx_ssl_stapling_cleanup(void *data)
ngx_int_t
ngx_ssl_ocsp(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *responder,
- ngx_uint_t depth)
+ ngx_uint_t depth, ngx_shm_zone_t *shm_zone)
{
ngx_url_t u;
ngx_ssl_ocsp_conf_t *ocf;
@@ -754,6 +778,7 @@ ngx_ssl_ocsp(ngx_conf_t *cf, ngx_ssl_t *
}
ocf->depth = depth;
+ ocf->shm_zone = shm_zone;
if (responder->len) {
ngx_memzero(&u, sizeof(ngx_url_t));
@@ -939,6 +964,7 @@ ngx_ssl_ocsp_validate(ngx_connection_t *
static void
ngx_ssl_ocsp_validate_next(ngx_connection_t *c)
{
+ ngx_int_t rc;
ngx_uint_t n;
ngx_ssl_ocsp_t *ocsp;
ngx_ssl_ocsp_ctx_t *ctx;
@@ -978,6 +1004,8 @@ ngx_ssl_ocsp_validate_next(ngx_connectio
ctx->handler = ngx_ssl_ocsp_handler;
ctx->data = c;
+ ctx->shm_zone = ocf->shm_zone;
+
ctx->addrs = ocf->addrs;
ctx->naddrs = ocf->naddrs;
ctx->host = ocf->host;
@@ -994,7 +1022,28 @@ ngx_ssl_ocsp_validate_next(ngx_connectio
ocsp->ncert++;
- break;
+ rc = ngx_ssl_ocsp_cache_lookup(ctx);
+
+ if (rc == NGX_ERROR) {
+ goto failed;
+ }
+
+ if (rc == NGX_DECLINED) {
+ break;
+ }
+
+ /* rc == NGX_OK */
+
+ if (ctx->status != V_OCSP_CERTSTATUS_GOOD) {
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+ "ssl ocsp cached status \"%s\"",
+ OCSP_cert_status_str(ctx->status));
+ ocsp->cert_status = ctx->status;
+ goto done;
+ }
+
+ ocsp->ctx = NULL;
+ ngx_ssl_ocsp_done(ctx);
}
ngx_ssl_ocsp_request(ctx);
@@ -1029,6 +1078,13 @@ ngx_ssl_ocsp_handler(ngx_ssl_ocsp_ctx_t
goto done;
}
+ rc = ngx_ssl_ocsp_cache_store(ctx);
+ if (rc != NGX_OK) {
+ ocsp->status = rc;
+ ngx_ssl_ocsp_done(ctx);
+ goto done;
+ }
+
if (ctx->status != V_OCSP_CERTSTATUS_GOOD) {
ocsp->cert_status = ctx->status;
ocsp->status = NGX_OK;
@@ -2374,6 +2430,245 @@ error:
}
+ngx_int_t
+ngx_ssl_ocsp_cache_init(ngx_shm_zone_t *shm_zone, void *data)
+{
+ size_t len;
+ ngx_slab_pool_t *shpool;
+ ngx_ssl_ocsp_cache_t *cache;
+
+ if (data) {
+ shm_zone->data = data;
+ return NGX_OK;
+ }
+
+ shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+
+ if (shm_zone->shm.exists) {
+ shm_zone->data = shpool->data;
+ return NGX_OK;
+ }
+
+ cache = ngx_slab_alloc(shpool, sizeof(ngx_ssl_ocsp_cache_t));
+ if (cache == NULL) {
+ return NGX_ERROR;
+ }
+
+ shpool->data = cache;
+ shm_zone->data = cache;
+
+ ngx_rbtree_init(&cache->rbtree, &cache->sentinel,
+ ngx_str_rbtree_insert_value);
+
+ ngx_queue_init(&cache->expire_queue);
+
+ len = sizeof(" in OCSP cache \"\"") + shm_zone->shm.name.len;
+
+ shpool->log_ctx = ngx_slab_alloc(shpool, len);
+ if (shpool->log_ctx == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_sprintf(shpool->log_ctx, " in OCSP cache \"%V\"%Z",
+ &shm_zone->shm.name);
+
+ shpool->log_nomem = 0;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_ssl_ocsp_cache_lookup(ngx_ssl_ocsp_ctx_t *ctx)
+{
+ uint32_t hash;
+ ngx_shm_zone_t *shm_zone;
+ ngx_slab_pool_t *shpool;
+ ngx_ssl_ocsp_cache_t *cache;
+ ngx_ssl_ocsp_cache_node_t *node;
+
+ shm_zone = ctx->shm_zone;
+
+ if (shm_zone == NULL) {
+ return NGX_DECLINED;
+ }
+
+ if (ngx_ssl_ocsp_create_key(ctx) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, "ssl ocsp cache lookup");
+
+ cache = shm_zone->data;
+ shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+ hash = ngx_hash_key(ctx->key.data, ctx->key.len);
+
+ ngx_shmtx_lock(&shpool->mutex);
+
+ node = (ngx_ssl_ocsp_cache_node_t *)
+ ngx_str_rbtree_lookup(&cache->rbtree, &ctx->key, hash);
+
+ if (node) {
+ if (node->valid > ngx_time()) {
+ ctx->status = node->status;
+ ngx_shmtx_unlock(&shpool->mutex);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+ "ssl ocsp cache hit, %s",
+ OCSP_cert_status_str(ctx->status));
+
+ return NGX_OK;
+ }
+
+ ngx_queue_remove(&node->queue);
+ ngx_rbtree_delete(&cache->rbtree, &node->node.node);
+ ngx_slab_free_locked(shpool, node);
+
+ ngx_shmtx_unlock(&shpool->mutex);
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+ "ssl ocsp cache expired");
+
+ return NGX_DECLINED;
+ }
+
+ ngx_shmtx_unlock(&shpool->mutex);
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, "ssl ocsp cache miss");
+
+ return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_ssl_ocsp_cache_store(ngx_ssl_ocsp_ctx_t *ctx)
+{
+ time_t now, valid;
+ uint32_t hash;
+ ngx_queue_t *q;
+ ngx_shm_zone_t *shm_zone;
+ ngx_slab_pool_t *shpool;
+ ngx_ssl_ocsp_cache_t *cache;
+ ngx_ssl_ocsp_cache_node_t *node;
+
+ shm_zone = ctx->shm_zone;
+
+ if (shm_zone == NULL) {
+ return NGX_OK;
+ }
+
+ valid = ctx->valid;
+
+ now = ngx_time();
+
+ if (valid < now) {
+ return NGX_OK;
+ }
+
+ if (valid == NGX_MAX_TIME_T_VALUE) {
+ valid = now + 3600;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+ "ssl ocsp cache store, valid:%T", valid - now);
+
+ cache = shm_zone->data;
+ shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
+ hash = ngx_hash_key(ctx->key.data, ctx->key.len);
+
+ ngx_shmtx_lock(&shpool->mutex);
+
+ node = ngx_slab_calloc_locked(shpool,
+ sizeof(ngx_ssl_ocsp_cache_node_t) + ctx->key.len);
+ if (node == NULL) {
+
+ if (!ngx_queue_empty(&cache->expire_queue)) {
+ q = ngx_queue_last(&cache->expire_queue);
+ node = ngx_queue_data(q, ngx_ssl_ocsp_cache_node_t, queue);
+
+ ngx_rbtree_delete(&cache->rbtree, &node->node.node);
+ ngx_queue_remove(q);
+ ngx_slab_free_locked(shpool, node);
+
+ node = ngx_slab_alloc_locked(shpool,
+ sizeof(ngx_ssl_ocsp_cache_node_t) + ctx->key.len);
+ }
+
+ if (node == NULL) {
+ ngx_shmtx_unlock(&shpool->mutex);
+ ngx_log_error(NGX_LOG_ALERT, ctx->log, 0,
+ "could not allocate new entry%s", shpool->log_ctx);
+ return NGX_ERROR;
+ }
+ }
+
+ node->node.str.len = ctx->key.len;
+ node->node.str.data = (u_char *) node + sizeof(ngx_ssl_ocsp_cache_node_t);
+ ngx_memcpy(node->node.str.data, ctx->key.data, ctx->key.len);
+ node->node.node.key = hash;
+ node->status = ctx->status;
+ node->valid = valid;
+
+ ngx_rbtree_insert(&cache->rbtree, &node->node.node);
+ ngx_queue_insert_head(&cache->expire_queue, &node->queue);
+
+ ngx_shmtx_unlock(&shpool->mutex);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_ssl_ocsp_create_key(ngx_ssl_ocsp_ctx_t *ctx)
+{
+ u_char *p;
+ X509_NAME *name;
+ ASN1_INTEGER *serial;
+
+ p = ngx_pnalloc(ctx->pool, 60);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ ctx->key.data = p;
+ ctx->key.len = 60;
+
+ name = X509_get_subject_name(ctx->issuer);
+ if (X509_NAME_digest(name, EVP_sha1(), p, NULL) == 0) {
+ return NGX_ERROR;
+ }
+
+ p += 20;
+
+ if (X509_pubkey_digest(ctx->issuer, EVP_sha1(), p, NULL) == 0) {
+ return NGX_ERROR;
+ }
+
+ p += 20;
+
+ serial = X509_get_serialNumber(ctx->cert);
+ if (serial->length > 20) {
+ return NGX_ERROR;
+ }
+
+ p = ngx_cpymem(p, serial->data, serial->length);
+ ngx_memzero(p, 20 - serial->length);
+
+#if (NGX_DEBUG)
+ {
+ u_char buf[120];
+
+ ngx_hex_dump(buf, ctx->key.data, ctx->key.len);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+ "ssl ocsp key %*s", 120, buf);
+ }
+#endif
+
+ return NGX_OK;
+}
+
+
static u_char *
ngx_ssl_ocsp_log_error(ngx_log_t *log, u_char *buf, size_t len)
{
@@ -2436,7 +2731,7 @@ ngx_ssl_stapling_resolver(ngx_conf_t *cf
ngx_int_t
ngx_ssl_ocsp(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *responder,
- ngx_uint_t depth)
+ ngx_uint_t depth, ngx_shm_zone_t *shm_zone)
{
ngx_log_error(NGX_LOG_EMERG, ssl->log, 0,
"\"ssl_ocsp\" is not supported on this platform");
@@ -2473,4 +2768,11 @@ ngx_ssl_ocsp_cleanup(ngx_connection_t *c
}
+ngx_int_t
+ngx_ssl_ocsp_cache_init(ngx_shm_zone_t *shm_zone, void *data)
+{
+ return NGX_OK;
+}
+
+
#endif
diff -r 8409f9df6219 -r b56f725dd4bb src/http/modules/ngx_http_ssl_module.c
--- a/src/http/modules/ngx_http_ssl_module.c Fri May 22 17:30:12 2020 +0300
+++ b/src/http/modules/ngx_http_ssl_module.c Fri May 22 17:25:27 2020 +0300
@@ -50,6 +50,8 @@ static char *ngx_http_ssl_password_file(
void *conf);
static char *ngx_http_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd,
void *conf);
+static char *ngx_http_ssl_ocsp_cache(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf);
@@ -236,6 +238,13 @@ static ngx_command_t ngx_http_ssl_comma
offsetof(ngx_http_ssl_srv_conf_t, ocsp_responder),
NULL },
+ { ngx_string("ssl_ocsp_cache"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_http_ssl_ocsp_cache,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
{ ngx_string("ssl_stapling"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
@@ -602,6 +611,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t
sscf->session_tickets = NGX_CONF_UNSET;
sscf->session_ticket_keys = NGX_CONF_UNSET_PTR;
sscf->ocsp = NGX_CONF_UNSET_UINT;
+ sscf->ocsp_cache_zone = NGX_CONF_UNSET_PTR;
sscf->stapling = NGX_CONF_UNSET;
sscf->stapling_verify = NGX_CONF_UNSET;
@@ -667,6 +677,8 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *
ngx_conf_merge_uint_value(conf->ocsp, prev->ocsp, 0);
ngx_conf_merge_str_value(conf->ocsp_responder, prev->ocsp_responder, "");
+ ngx_conf_merge_ptr_value(conf->ocsp_cache_zone,
+ prev->ocsp_cache_zone, NULL);
ngx_conf_merge_value(conf->stapling, prev->stapling, 0);
ngx_conf_merge_value(conf->stapling_verify, prev->stapling_verify, 0);
@@ -838,7 +850,8 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *
return NGX_CONF_ERROR;
}
- if (ngx_ssl_ocsp(cf, &conf->ssl, &conf->ocsp_responder, conf->ocsp)
+ if (ngx_ssl_ocsp(cf, &conf->ssl, &conf->ocsp_responder, conf->ocsp,
+ conf->ocsp_cache_zone)
!= NGX_OK)
{
return NGX_CONF_ERROR;
@@ -1143,6 +1156,85 @@ invalid:
}
+static char *
+ngx_http_ssl_ocsp_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_ssl_srv_conf_t *sscf = conf;
+
+ size_t len;
+ ngx_int_t n;
+ ngx_str_t *value, name, size;
+ ngx_uint_t j;
+
+ if (sscf->ocsp_cache_zone != NGX_CONF_UNSET_PTR) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ sscf->ocsp_cache_zone = NULL;
+ return NGX_CONF_OK;
+ }
+
+ if (value[1].len <= sizeof("shared:") - 1
+ || ngx_strncmp(value[1].data, "shared:", sizeof("shared:") - 1) != 0)
+ {
+ goto invalid;
+ }
+
+ len = 0;
+
+ for (j = sizeof("shared:") - 1; j < value[1].len; j++) {
+ if (value[1].data[j] == ':') {
+ break;
+ }
+
+ len++;
+ }
+
+ if (len == 0) {
+ goto invalid;
+ }
+
+ name.len = len;
+ name.data = value[1].data + sizeof("shared:") - 1;
+
+ size.len = value[1].len - j - 1;
+ size.data = name.data + len + 1;
+
+ n = ngx_parse_size(&size);
+
+ if (n == NGX_ERROR) {
+ goto invalid;
+ }
+
+ if (n < (ngx_int_t) (8 * ngx_pagesize)) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "OCSP cache \"%V\" is too small", &value[1]);
+
+ return NGX_CONF_ERROR;
+ }
+
+ sscf->ocsp_cache_zone = ngx_shared_memory_add(cf, &name, n,
+ &ngx_http_ssl_module_ctx);
+ if (sscf->ocsp_cache_zone == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ sscf->ocsp_cache_zone->init = ngx_ssl_ocsp_cache_init;
+
+ return NGX_CONF_OK;
+
+invalid:
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid OCSP cache \"%V\"", &value[1]);
+
+ return NGX_CONF_ERROR;
+}
+
+
static ngx_int_t
ngx_http_ssl_init(ngx_conf_t *cf)
{
diff -r 8409f9df6219 -r b56f725dd4bb src/http/modules/ngx_http_ssl_module.h
--- a/src/http/modules/ngx_http_ssl_module.h Fri May 22 17:30:12 2020 +0300
+++ b/src/http/modules/ngx_http_ssl_module.h Fri May 22 17:25:27 2020 +0300
@@ -56,6 +56,7 @@ typedef struct {
ngx_uint_t ocsp;
ngx_str_t ocsp_responder;
+ ngx_shm_zone_t *ocsp_cache_zone;
ngx_flag_t stapling;
ngx_flag_t stapling_verify;
More information about the nginx-devel
mailing list