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.
This commit is contained in:
Roman Arutyunyan 2020-05-22 17:25:27 +03:00
parent 60438ae395
commit 5727f9a1e0
4 changed files with 401 additions and 5 deletions

View File

@ -187,12 +187,13 @@ ngx_int_t ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl,
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);

View File

@ -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_header_line(ngx_ssl_ocsp_ctx_t *ctx);
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 *ssl, ngx_str_t *responder,
}
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 *c)
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_connection_t *c)
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_connection_t *c)
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 *ctx)
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_ssl_t *ssl,
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

View File

@ -50,6 +50,8 @@ static char *ngx_http_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd,
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_commands[] = {
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 *cf)
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 *cf, void *parent, void *child)
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 *cf, void *parent, void *child)
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)
{

View File

@ -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;