# HG changeset patch # User Roman Arutyunyan # Date 1590157812 -10800 # Node ID 8409f9df62194c01810ba8cb6a2030012f0960d0 # Parent 7cffd81015e701cc310f6f54e6a6a6185b4f7859 SSL: client certificate validation with OCSP (ticket #1534). OCSP validation for client certificates is enabled by the "ssl_ocsp" directive. OCSP responder can be optionally specified by "ssl_ocsp_responder". When session is reused, peer chain is not available for validation. If the verified chain contains certificates from the peer chain not available at the server, validation will fail. diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -130,6 +130,7 @@ int ngx_ssl_connection_index; int ngx_ssl_server_conf_index; int ngx_ssl_session_cache_index; int ngx_ssl_session_ticket_keys_index; +int ngx_ssl_ocsp_index; int ngx_ssl_certificate_index; int ngx_ssl_next_certificate_index; int ngx_ssl_certificate_name_index; @@ -213,6 +214,13 @@ ngx_ssl_init(ngx_log_t *log) return NGX_ERROR; } + ngx_ssl_ocsp_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL); + if (ngx_ssl_ocsp_index == -1) { + ngx_ssl_error(NGX_LOG_ALERT, log, 0, + "SSL_CTX_get_ex_new_index() failed"); + return NGX_ERROR; + } + ngx_ssl_certificate_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL); if (ngx_ssl_certificate_index == -1) { @@ -1594,6 +1602,7 @@ ngx_ssl_handshake(ngx_connection_t *c) { int n, sslerr; ngx_err_t err; + ngx_int_t rc; #ifdef SSL_READ_EARLY_DATA_SUCCESS if (c->ssl->try_early_data) { @@ -1601,6 +1610,10 @@ ngx_ssl_handshake(ngx_connection_t *c) } #endif + if (c->ssl->in_ocsp) { + return ngx_ssl_ocsp_validate(c); + } + ngx_ssl_clear_error(c->log); n = SSL_do_handshake(c->ssl->connection); @@ -1621,8 +1634,6 @@ ngx_ssl_handshake(ngx_connection_t *c) ngx_ssl_handshake_log(c); #endif - c->ssl->handshaked = 1; - c->recv = ngx_ssl_recv; c->send = ngx_ssl_write; c->recv_chain = ngx_ssl_recv_chain; @@ -1641,6 +1652,20 @@ ngx_ssl_handshake(ngx_connection_t *c) #endif #endif + rc = ngx_ssl_ocsp_validate(c); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_AGAIN) { + c->read->handler = ngx_ssl_handshake_handler; + c->write->handler = ngx_ssl_handshake_handler; + return NGX_AGAIN; + } + + c->ssl->handshaked = 1; + return NGX_OK; } @@ -1710,6 +1735,7 @@ ngx_ssl_try_early_data(ngx_connection_t u_char buf; size_t readbytes; ngx_err_t err; + ngx_int_t rc; ngx_ssl_clear_error(c->log); @@ -1744,7 +1770,6 @@ ngx_ssl_try_early_data(ngx_connection_t c->ssl->early_buf = buf; c->ssl->early_preread = 1; - c->ssl->handshaked = 1; c->ssl->in_early = 1; c->recv = ngx_ssl_recv; @@ -1752,6 +1777,20 @@ ngx_ssl_try_early_data(ngx_connection_t c->recv_chain = ngx_ssl_recv_chain; c->send_chain = ngx_ssl_send_chain; + rc = ngx_ssl_ocsp_validate(c); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_AGAIN) { + c->read->handler = ngx_ssl_handshake_handler; + c->write->handler = ngx_ssl_handshake_handler; + return NGX_AGAIN; + } + + c->ssl->handshaked = 1; + return NGX_OK; } @@ -2735,6 +2774,8 @@ ngx_ssl_shutdown(ngx_connection_t *c) int n, sslerr, mode; ngx_err_t err; + ngx_ssl_ocsp_cleanup(c); + if (SSL_in_init(c->ssl->connection)) { /* * OpenSSL 1.0.2f complains if SSL_shutdown() is called during @@ -4894,11 +4935,14 @@ ngx_ssl_get_client_verify(ngx_connection rc = SSL_get_verify_result(c->ssl->connection); if (rc == X509_V_OK) { - ngx_str_set(s, "SUCCESS"); - return NGX_OK; - } - - str = X509_verify_cert_error_string(rc); + if (ngx_ssl_ocsp_get_status(c, &str) == NGX_OK) { + ngx_str_set(s, "SUCCESS"); + return NGX_OK; + } + + } else { + str = X509_verify_cert_error_string(rc); + } s->data = ngx_pnalloc(pool, sizeof("FAILED:") - 1 + ngx_strlen(str)); if (s->data == NULL) { diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -64,6 +64,9 @@ #endif +typedef struct ngx_ssl_ocsp_s ngx_ssl_ocsp_t; + + struct ngx_ssl_s { SSL_CTX *ctx; ngx_log_t *log; @@ -87,6 +90,8 @@ struct ngx_ssl_connection_s { ngx_event_handler_pt saved_read_handler; ngx_event_handler_pt saved_write_handler; + ngx_ssl_ocsp_t *ocsp; + u_char early_buf; unsigned handshaked:1; @@ -97,6 +102,7 @@ struct ngx_ssl_connection_s { unsigned handshake_buffer_set:1; unsigned try_early_data:1; unsigned in_early:1; + unsigned in_ocsp:1; unsigned early_preread:1; unsigned write_blocked:1; }; @@ -180,6 +186,13 @@ ngx_int_t ngx_ssl_stapling(ngx_conf_t *c ngx_str_t *file, ngx_str_t *responder, ngx_uint_t verify); 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_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); 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); @@ -281,6 +294,7 @@ extern int ngx_ssl_connection_index; extern int ngx_ssl_server_conf_index; extern int ngx_ssl_session_cache_index; extern int ngx_ssl_session_ticket_keys_index; +extern int ngx_ssl_ocsp_index; extern int ngx_ssl_certificate_index; extern int ngx_ssl_next_certificate_index; extern int ngx_ssl_certificate_name_index; diff --git a/src/event/ngx_event_openssl_stapling.c b/src/event/ngx_event_openssl_stapling.c --- a/src/event/ngx_event_openssl_stapling.c +++ b/src/event/ngx_event_openssl_stapling.c @@ -43,8 +43,35 @@ typedef struct { } ngx_ssl_stapling_t; +typedef struct { + ngx_addr_t *addrs; + ngx_uint_t naddrs; + + ngx_str_t host; + ngx_str_t uri; + in_port_t port; + ngx_uint_t depth; + + ngx_resolver_t *resolver; + ngx_msec_t resolver_timeout; +} ngx_ssl_ocsp_conf_t; + + typedef struct ngx_ssl_ocsp_ctx_s ngx_ssl_ocsp_ctx_t; + +struct ngx_ssl_ocsp_s { + STACK_OF(X509) *certs; + ngx_uint_t ncert; + + int cert_status; + ngx_int_t status; + + ngx_ssl_ocsp_conf_t *conf; + ngx_ssl_ocsp_ctx_t *ctx; +}; + + struct ngx_ssl_ocsp_ctx_s { SSL_CTX *ssl_ctx; @@ -114,7 +141,12 @@ static time_t ngx_ssl_stapling_time(ASN1 static void ngx_ssl_stapling_cleanup(void *data); -static ngx_ssl_ocsp_ctx_t *ngx_ssl_ocsp_start(void); +static void ngx_ssl_ocsp_validate_next(ngx_connection_t *c); +static void ngx_ssl_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx); +static ngx_int_t ngx_ssl_ocsp_responder(ngx_connection_t *c, + ngx_ssl_ocsp_ctx_t *ctx); + +static ngx_ssl_ocsp_ctx_t *ngx_ssl_ocsp_start(ngx_log_t *log); static void ngx_ssl_ocsp_done(ngx_ssl_ocsp_ctx_t *ctx); static void ngx_ssl_ocsp_next(ngx_ssl_ocsp_ctx_t *ctx); static void ngx_ssl_ocsp_request(ngx_ssl_ocsp_ctx_t *ctx); @@ -570,7 +602,7 @@ ngx_ssl_stapling_update(ngx_ssl_stapling staple->loading = 1; - ctx = ngx_ssl_ocsp_start(); + ctx = ngx_ssl_ocsp_start(ngx_cycle->log); if (ctx == NULL) { return; } @@ -709,14 +741,467 @@ 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_url_t u; + ngx_ssl_ocsp_conf_t *ocf; + + ocf = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_ocsp_conf_t)); + if (ocf == NULL) { + return NGX_ERROR; + } + + ocf->depth = depth; + + if (responder->len) { + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.url = *responder; + u.default_port = 80; + u.uri_part = 1; + + if (u.url.len > 7 + && ngx_strncasecmp(u.url.data, (u_char *) "http://", 7) == 0) + { + u.url.len -= 7; + u.url.data += 7; + + } else { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "invalid URL prefix in OCSP responder \"%V\" " + "in \"ssl_ocsp_responder\"", &u.url); + return NGX_ERROR; + } + + if (ngx_parse_url(cf->pool, &u) != NGX_OK) { + if (u.err) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "%s in OCSP responder \"%V\" " + "in \"ssl_ocsp_responder\"", u.err, &u.url); + } + + return NGX_ERROR; + } + + ocf->addrs = u.addrs; + ocf->naddrs = u.naddrs; + ocf->host = u.host; + ocf->uri = u.uri; + ocf->port = u.port; + } + + if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_ocsp_index, ocf) == 0) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_set_ex_data() failed"); + return NGX_ERROR; + } + + return NGX_OK; +} + + +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_ssl_ocsp_conf_t *ocf; + + ocf = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_ocsp_index); + ocf->resolver = resolver; + ocf->resolver_timeout = resolver_timeout; + + return NGX_OK; +} + + +ngx_int_t +ngx_ssl_ocsp_validate(ngx_connection_t *c) +{ + X509 *cert; + SSL_CTX *ssl_ctx; + ngx_int_t rc; + X509_STORE *store; + X509_STORE_CTX *store_ctx; + STACK_OF(X509) *chain; + ngx_ssl_ocsp_t *ocsp; + ngx_ssl_ocsp_conf_t *ocf; + + if (c->ssl->in_ocsp) { + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_handle_write_event(c->write, 0) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_AGAIN; + } + + ssl_ctx = SSL_get_SSL_CTX(c->ssl->connection); + + ocf = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_ocsp_index); + if (ocf == NULL) { + return NGX_OK; + } + + if (SSL_get_verify_result(c->ssl->connection) != X509_V_OK) { + return NGX_OK; + } + + cert = SSL_get_peer_certificate(c->ssl->connection); + if (cert == NULL) { + return NGX_OK; + } + + ocsp = ngx_pcalloc(c->pool, sizeof(ngx_ssl_ocsp_t)); + if (ocsp == NULL) { + return NGX_ERROR; + } + + c->ssl->ocsp = ocsp; + + ocsp->status = NGX_AGAIN; + ocsp->cert_status = V_OCSP_CERTSTATUS_GOOD; + ocsp->conf = ocf; + +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER) + + ocsp->certs = SSL_get0_verified_chain(c->ssl->connection); + + if (ocsp->certs) { + ocsp->certs = X509_chain_up_ref(ocsp->certs); + if (ocsp->certs == NULL) { + return NGX_ERROR; + } + } + +#endif + + if (ocsp->certs == NULL) { + store = SSL_CTX_get_cert_store(ssl_ctx); + if (store == NULL) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "SSL_CTX_get_cert_store() failed"); + return NGX_ERROR; + } + + store_ctx = X509_STORE_CTX_new(); + if (store_ctx == NULL) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "X509_STORE_CTX_new() failed"); + return NGX_ERROR; + } + + chain = SSL_get_peer_cert_chain(c->ssl->connection); + + if (X509_STORE_CTX_init(store_ctx, store, cert, chain) == 0) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "X509_STORE_CTX_init() failed"); + X509_STORE_CTX_free(store_ctx); + return NGX_ERROR; + } + + rc = X509_verify_cert(store_ctx); + if (rc <= 0) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "X509_verify_cert() failed"); + X509_STORE_CTX_free(store_ctx); + return NGX_ERROR; + } + + ocsp->certs = X509_STORE_CTX_get1_chain(store_ctx); + if (ocsp->certs == NULL) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "X509_STORE_CTX_get1_chain() failed"); + X509_STORE_CTX_free(store_ctx); + return NGX_ERROR; + } + + X509_STORE_CTX_free(store_ctx); + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ssl ocsp validate, certs:%i", sk_X509_num(ocsp->certs)); + + ngx_ssl_ocsp_validate_next(c); + + if (ocsp->status == NGX_AGAIN) { + c->ssl->in_ocsp = 1; + return NGX_AGAIN; + } + + return NGX_OK; +} + + +static void +ngx_ssl_ocsp_validate_next(ngx_connection_t *c) +{ + ngx_uint_t n; + ngx_ssl_ocsp_t *ocsp; + ngx_ssl_ocsp_ctx_t *ctx; + ngx_ssl_ocsp_conf_t *ocf; + + ocsp = c->ssl->ocsp; + ocf = ocsp->conf; + + n = sk_X509_num(ocsp->certs); + + for ( ;; ) { + + if (ocsp->ncert == n - 1 || (ocf->depth == 2 && ocsp->ncert == 1)) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ssl ocsp validated, certs:%ui", ocsp->ncert); + goto done; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "ssl ocsp validate cert:%ui", ocsp->ncert); + + ctx = ngx_ssl_ocsp_start(c->log); + if (ctx == NULL) { + goto failed; + } + + ocsp->ctx = ctx; + + ctx->ssl_ctx = SSL_get_SSL_CTX(c->ssl->connection); + ctx->cert = sk_X509_value(ocsp->certs, ocsp->ncert); + ctx->issuer = sk_X509_value(ocsp->certs, ocsp->ncert + 1); + ctx->chain = ocsp->certs; + + ctx->resolver = ocf->resolver; + ctx->resolver_timeout = ocf->resolver_timeout; + + ctx->handler = ngx_ssl_ocsp_handler; + ctx->data = c; + + ctx->addrs = ocf->addrs; + ctx->naddrs = ocf->naddrs; + ctx->host = ocf->host; + ctx->uri = ocf->uri; + ctx->port = ocf->port; + + if (ngx_ssl_ocsp_responder(c, ctx) != NGX_OK) { + goto failed; + } + + if (ctx->uri.len == 0) { + ngx_str_set(&ctx->uri, "/"); + } + + ocsp->ncert++; + + break; + } + + ngx_ssl_ocsp_request(ctx); + return; + +done: + + ocsp->status = NGX_OK; + return; + +failed: + + ocsp->status = NGX_ERROR; +} + + +static void +ngx_ssl_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx) +{ + ngx_int_t rc; + ngx_ssl_ocsp_t *ocsp; + ngx_connection_t *c; + + c = ctx->data; + ocsp = c->ssl->ocsp; + ocsp->ctx = NULL; + + rc = ngx_ssl_ocsp_verify(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; + ngx_ssl_ocsp_done(ctx); + goto done; + } + + ngx_ssl_ocsp_done(ctx); + + ngx_ssl_ocsp_validate_next(c); + +done: + + if (ocsp->status == NGX_AGAIN || !c->ssl->in_ocsp) { + return; + } + + c->ssl->handshaked = 1; + + c->ssl->handler(c); +} + + +static ngx_int_t +ngx_ssl_ocsp_responder(ngx_connection_t *c, ngx_ssl_ocsp_ctx_t *ctx) +{ + char *s; + ngx_str_t responder; + ngx_url_t u; + STACK_OF(OPENSSL_STRING) *aia; + + if (ctx->host.len) { + return NGX_OK; + } + + /* extract OCSP responder URL from certificate */ + + aia = X509_get1_ocsp(ctx->cert); + if (aia == NULL) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "no OCSP responder URL in certificate"); + return NGX_ERROR; + } + +#if OPENSSL_VERSION_NUMBER >= 0x10000000L + s = sk_OPENSSL_STRING_value(aia, 0); +#else + s = sk_value(aia, 0); +#endif + if (s == NULL) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "no OCSP responder URL in certificate"); + X509_email_free(aia); + return NGX_ERROR; + } + + responder.len = ngx_strlen(s); + responder.data = ngx_palloc(ctx->pool, responder.len); + if (responder.data == NULL) { + X509_email_free(aia); + return NGX_ERROR; + } + + ngx_memcpy(responder.data, s, responder.len); + X509_email_free(aia); + + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.url = responder; + u.default_port = 80; + u.uri_part = 1; + u.no_resolve = 1; + + if (u.url.len > 7 + && ngx_strncasecmp(u.url.data, (u_char *) "http://", 7) == 0) + { + u.url.len -= 7; + u.url.data += 7; + + } else { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "invalid URL prefix in OCSP responder \"%V\" " + "in certificate", &u.url); + return NGX_ERROR; + } + + if (ngx_parse_url(ctx->pool, &u) != NGX_OK) { + if (u.err) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "%s in OCSP responder \"%V\" in certificate", + u.err, &u.url); + } + + return NGX_ERROR; + } + + if (u.host.len == 0) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "empty host in OCSP responder in certificate"); + return NGX_ERROR; + } + + ctx->addrs = u.addrs; + ctx->naddrs = u.naddrs; + ctx->host = u.host; + ctx->uri = u.uri; + ctx->port = u.port; + + return NGX_OK; +} + + +ngx_int_t +ngx_ssl_ocsp_get_status(ngx_connection_t *c, const char **s) +{ + ngx_ssl_ocsp_t *ocsp; + + ocsp = c->ssl->ocsp; + if (ocsp == NULL) { + return NGX_OK; + } + + if (ocsp->status == NGX_ERROR) { + *s = "certificate status request failed"; + return NGX_DECLINED; + } + + switch (ocsp->cert_status) { + + case V_OCSP_CERTSTATUS_GOOD: + return NGX_OK; + + case V_OCSP_CERTSTATUS_REVOKED: + *s = "certificate revoked"; + break; + + default: /* V_OCSP_CERTSTATUS_UNKNOWN */ + *s = "certificate status unknown"; + } + + return NGX_DECLINED; +} + + +void +ngx_ssl_ocsp_cleanup(ngx_connection_t *c) +{ + ngx_ssl_ocsp_t *ocsp; + + ocsp = c->ssl->ocsp; + if (ocsp == NULL) { + return; + } + + if (ocsp->ctx) { + ngx_ssl_ocsp_done(ocsp->ctx); + ocsp->ctx = NULL; + } + + if (ocsp->certs) { + sk_X509_pop_free(ocsp->certs, X509_free); + ocsp->certs = NULL; + } +} + + static ngx_ssl_ocsp_ctx_t * -ngx_ssl_ocsp_start(void) +ngx_ssl_ocsp_start(ngx_log_t *log) { - ngx_log_t *log; ngx_pool_t *pool; ngx_ssl_ocsp_ctx_t *ctx; - pool = ngx_create_pool(2048, ngx_cycle->log); + pool = ngx_create_pool(2048, log); if (pool == NULL) { return NULL; } @@ -828,6 +1313,14 @@ ngx_ssl_ocsp_request(ngx_ssl_ocsp_ctx_t } if (resolve == NGX_NO_RESOLVER) { + if (ctx->naddrs == 0) { + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, + "no resolver defined to resolve %V", &ctx->host); + + ngx_ssl_ocsp_error(ctx); + return; + } + ngx_log_error(NGX_LOG_WARN, ctx->log, 0, "no resolver defined to resolve %V", &ctx->host); goto connect; @@ -979,8 +1472,10 @@ ngx_ssl_ocsp_connect(ngx_ssl_ocsp_ctx_t ctx->process = ngx_ssl_ocsp_process_status_line; - ngx_add_timer(ctx->peer.connection->read, ctx->timeout); - ngx_add_timer(ctx->peer.connection->write, ctx->timeout); + if (ctx->timeout) { + ngx_add_timer(ctx->peer.connection->read, ctx->timeout); + ngx_add_timer(ctx->peer.connection->write, ctx->timeout); + } if (rc == NGX_OK) { ngx_ssl_ocsp_write_handler(ctx->peer.connection->write); @@ -1036,7 +1531,7 @@ ngx_ssl_ocsp_write_handler(ngx_event_t * } } - if (!wev->timer_set) { + if (!wev->timer_set && ctx->timeout) { ngx_add_timer(wev, ctx->timeout); } } @@ -1939,4 +2434,43 @@ 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_log_error(NGX_LOG_EMERG, ssl->log, 0, + "\"ssl_ocsp\" is not supported on this platform"); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_ssl_ocsp_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl, + ngx_resolver_t *resolver, ngx_msec_t resolver_timeout) +{ + return NGX_OK; +} + + +ngx_int_t +ngx_ssl_ocsp_validate(ngx_connection_t *c) +{ + return NGX_OK; +} + + +ngx_int_t +ngx_ssl_ocsp_get_status(ngx_connection_t *c, const char **s) +{ + return NGX_OK; +} + + +void +ngx_ssl_ocsp_cleanup(ngx_connection_t *c) +{ +} + + #endif diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -74,6 +74,14 @@ static ngx_conf_enum_t ngx_http_ssl_ver }; +static ngx_conf_enum_t ngx_http_ssl_ocsp[] = { + { ngx_string("off"), 0 }, + { ngx_string("on"), 1 }, + { ngx_string("leaf"), 2 }, + { ngx_null_string, 0 } +}; + + static ngx_conf_deprecated_t ngx_http_ssl_deprecated = { ngx_conf_deprecated, "ssl", "listen ... ssl" }; @@ -214,6 +222,20 @@ static ngx_command_t ngx_http_ssl_comma offsetof(ngx_http_ssl_srv_conf_t, crl), NULL }, + { ngx_string("ssl_ocsp"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_enum_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, ocsp), + &ngx_http_ssl_ocsp }, + + { ngx_string("ssl_ocsp_responder"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, ocsp_responder), + NULL }, + { ngx_string("ssl_stapling"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, @@ -561,6 +583,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t * sscf->crl = { 0, NULL }; * sscf->ciphers = { 0, NULL }; * sscf->shm_zone = NULL; + * sscf->ocsp_responder = { 0, NULL }; * sscf->stapling_file = { 0, NULL }; * sscf->stapling_responder = { 0, NULL }; */ @@ -578,6 +601,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t sscf->session_timeout = NGX_CONF_UNSET; sscf->session_tickets = NGX_CONF_UNSET; sscf->session_ticket_keys = NGX_CONF_UNSET_PTR; + sscf->ocsp = NGX_CONF_UNSET_UINT; sscf->stapling = NGX_CONF_UNSET; sscf->stapling_verify = NGX_CONF_UNSET; @@ -641,6 +665,9 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t * ngx_conf_merge_str_value(conf->ciphers, prev->ciphers, NGX_DEFAULT_CIPHERS); + ngx_conf_merge_uint_value(conf->ocsp, prev->ocsp, 0); + ngx_conf_merge_str_value(conf->ocsp_responder, prev->ocsp_responder, ""); + ngx_conf_merge_value(conf->stapling, prev->stapling, 0); ngx_conf_merge_value(conf->stapling_verify, prev->stapling_verify, 0); ngx_conf_merge_str_value(conf->stapling_file, prev->stapling_file, ""); @@ -802,6 +829,22 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t * return NGX_CONF_ERROR; } + if (conf->ocsp) { + + if (conf->verify == 3) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "\"ssl_ocsp\" is incompatible with " + "\"ssl_verify_client optional_no_ca\""); + return NGX_CONF_ERROR; + } + + if (ngx_ssl_ocsp(cf, &conf->ssl, &conf->ocsp_responder, conf->ocsp) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + } + if (ngx_ssl_dhparam(cf, &conf->ssl, &conf->dhparam) != NGX_OK) { return NGX_CONF_ERROR; } @@ -1118,17 +1161,28 @@ ngx_http_ssl_init(ngx_conf_t *cf) sscf = cscfp[s]->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; - if (sscf->ssl.ctx == NULL || !sscf->stapling) { + if (sscf->ssl.ctx == NULL) { continue; } clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index]; - if (ngx_ssl_stapling_resolver(cf, &sscf->ssl, clcf->resolver, + if (sscf->stapling) { + if (ngx_ssl_stapling_resolver(cf, &sscf->ssl, clcf->resolver, + clcf->resolver_timeout) + != NGX_OK) + { + return NGX_ERROR; + } + } + + if (sscf->ocsp) { + if (ngx_ssl_ocsp_resolver(cf, &sscf->ssl, clcf->resolver, clcf->resolver_timeout) - != NGX_OK) - { - return NGX_ERROR; + != NGX_OK) + { + return NGX_ERROR; + } } } diff --git a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h --- a/src/http/modules/ngx_http_ssl_module.h +++ b/src/http/modules/ngx_http_ssl_module.h @@ -54,6 +54,9 @@ typedef struct { ngx_flag_t session_tickets; ngx_array_t *session_ticket_keys; + ngx_uint_t ocsp; + ngx_str_t ocsp_responder; + ngx_flag_t stapling; ngx_flag_t stapling_verify; ngx_str_t stapling_file; diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -1993,6 +1993,7 @@ ngx_http_process_request(ngx_http_reques if (r->http_connection->ssl) { long rc; X509 *cert; + const char *s; ngx_http_ssl_srv_conf_t *sscf; if (c->ssl == NULL) { @@ -2037,6 +2038,17 @@ ngx_http_process_request(ngx_http_reques X509_free(cert); } + + if (ngx_ssl_ocsp_get_status(c, &s) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client SSL certificate verify error: %s", s); + + ngx_ssl_remove_cached_session(c->ssl->session_ctx, + (SSL_get0_session(c->ssl->connection))); + + ngx_http_finalize_request(r, NGX_HTTPS_CERT_ERROR); + return; + } } }