[nginx] SSL: client certificate validation with OCSP (ticket #1534).

Roman Arutyunyan arut at nginx.com
Sat May 23 10:35:50 UTC 2020


details:   https://hg.nginx.org/nginx/rev/8409f9df6219
branches:  
changeset: 7653:8409f9df6219
user:      Roman Arutyunyan <arut at nginx.com>
date:      Fri May 22 17:30:12 2020 +0300
description:
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.

diffstat:

 src/event/ngx_event_openssl.c          |   60 +++-
 src/event/ngx_event_openssl.h          |   14 +
 src/event/ngx_event_openssl_stapling.c |  550 ++++++++++++++++++++++++++++++++-
 src/http/modules/ngx_http_ssl_module.c |   64 +++-
 src/http/modules/ngx_http_ssl_module.h |    3 +
 src/http/ngx_http_request.c            |   12 +
 6 files changed, 682 insertions(+), 21 deletions(-)

diffs (970 lines):

diff -r 7cffd81015e7 -r 8409f9df6219 src/event/ngx_event_openssl.c
--- a/src/event/ngx_event_openssl.c	Fri May 22 20:35:05 2020 +0300
+++ b/src/event/ngx_event_openssl.c	Fri May 22 17:30:12 2020 +0300
@@ -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 -r 7cffd81015e7 -r 8409f9df6219 src/event/ngx_event_openssl.h
--- a/src/event/ngx_event_openssl.h	Fri May 22 20:35:05 2020 +0300
+++ b/src/event/ngx_event_openssl.h	Fri May 22 17:30:12 2020 +0300
@@ -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 -r 7cffd81015e7 -r 8409f9df6219 src/event/ngx_event_openssl_stapling.c
--- a/src/event/ngx_event_openssl_stapling.c	Fri May 22 20:35:05 2020 +0300
+++ b/src/event/ngx_event_openssl_stapling.c	Fri May 22 17:30:12 2020 +0300
@@ -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 -r 7cffd81015e7 -r 8409f9df6219 src/http/modules/ngx_http_ssl_module.c
--- a/src/http/modules/ngx_http_ssl_module.c	Fri May 22 20:35:05 2020 +0300
+++ b/src/http/modules/ngx_http_ssl_module.c	Fri May 22 17:30:12 2020 +0300
@@ -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 -r 7cffd81015e7 -r 8409f9df6219 src/http/modules/ngx_http_ssl_module.h
--- a/src/http/modules/ngx_http_ssl_module.h	Fri May 22 20:35:05 2020 +0300
+++ b/src/http/modules/ngx_http_ssl_module.h	Fri May 22 17:30:12 2020 +0300
@@ -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 -r 7cffd81015e7 -r 8409f9df6219 src/http/ngx_http_request.c
--- a/src/http/ngx_http_request.c	Fri May 22 20:35:05 2020 +0300
+++ b/src/http/ngx_http_request.c	Fri May 22 17:30:12 2020 +0300
@@ -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;
+            }
         }
     }
 


More information about the nginx-devel mailing list