[PATCH 3 of 4] OCSP stapling: loading OCSP responses

Maxim Dounin mdounin at mdounin.ru
Wed Sep 5 11:14:43 UTC 2012


# HG changeset patch
# User Maxim Dounin <mdounin at mdounin.ru>
# Date 1346843259 -14400
# Node ID 73a3c526e072c5f5ed8f04a75e3498def5cd97e5
# Parent  9fa7f13e92b0287cbef687cc28a6a282a6d3f4c8
OCSP stapling: loading OCSP responses.

This includes the ssl_stapling_responder directive (defaults to OCSP
responder set in certificate's AIA extension).

OCSP response for a given certificate is requested once we get at least
one connection with certificate_status extension in ClientHello, and
certificate status won't be sent in the connection in question.  This due
to limitations in the OpenSSL API (certificate status callback is blocking).

Note: SSL_CTX_use_certificate_chain_file() was reimplemented as it doesn't
allow to access the certificate loaded via SSL_CTX.

diff --git a/src/core/ngx_core.h b/src/core/ngx_core.h
--- a/src/core/ngx_core.h
+++ b/src/core/ngx_core.h
@@ -69,12 +69,12 @@ typedef void (*ngx_connection_handler_pt
 #include <ngx_slab.h>
 #include <ngx_inet.h>
 #include <ngx_cycle.h>
+#include <ngx_resolver.h>
 #if (NGX_OPENSSL)
 #include <ngx_event_openssl.h>
 #endif
 #include <ngx_process_cycle.h>
 #include <ngx_conf_file.h>
-#include <ngx_resolver.h>
 #include <ngx_open_file_cache.h>
 #include <ngx_os.h>
 #include <ngx_connection.h>
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
@@ -82,6 +82,8 @@ ngx_module_t  ngx_openssl_module = {
 int  ngx_ssl_connection_index;
 int  ngx_ssl_server_conf_index;
 int  ngx_ssl_session_cache_index;
+int  ngx_ssl_certificate_index;
+int  ngx_ssl_stapling_index;
 
 
 ngx_int_t
@@ -135,6 +137,22 @@ ngx_ssl_init(ngx_log_t *log)
         return NGX_ERROR;
     }
 
+    ngx_ssl_certificate_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
+                                                         NULL);
+    if (ngx_ssl_certificate_index == -1) {
+        ngx_ssl_error(NGX_LOG_ALERT, log, 0,
+                      "SSL_CTX_get_ex_new_index() failed");
+        return NGX_ERROR;
+    }
+
+    ngx_ssl_stapling_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
+                                                      NULL);
+    if (ngx_ssl_stapling_index == -1) {
+        ngx_ssl_error(NGX_LOG_ALERT, log, 0,
+                      "SSL_CTX_get_ex_new_index() failed");
+        return NGX_ERROR;
+    }
+
     return NGX_OK;
 }
 
@@ -216,19 +234,89 @@ ngx_int_t
 ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert,
     ngx_str_t *key)
 {
+    BIO     *bio;
+    X509    *x509;
+    u_long   n;
+
     if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) {
         return NGX_ERROR;
     }
 
-    if (SSL_CTX_use_certificate_chain_file(ssl->ctx, (char *) cert->data)
+    /*
+     * we can't use SSL_CTX_use_certificate_chain_file() as it doesn't
+     * allow to access certificate later from SSL_CTX, so we reimplement
+     * it here
+     */
+
+    bio = BIO_new_file((char *) cert->data, "r");
+    if (bio == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "BIO_new_file(\"%s\") failed", cert->data);
+        return NGX_ERROR;
+    }
+
+    x509 = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL);
+    if (x509 == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "PEM_read_bio_X509_AUX(\"%s\") failed", cert->data);
+        BIO_free(bio);
+        return NGX_ERROR;
+    }
+
+    if (SSL_CTX_use_certificate(ssl->ctx, x509) == 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_use_certificate(\"%s\") failed", cert->data);
+        X509_free(x509);
+        BIO_free(bio);
+        return NGX_ERROR;
+    }
+
+    if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_certificate_index, x509)
         == 0)
     {
         ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
-                      "SSL_CTX_use_certificate_chain_file(\"%s\") failed",
-                      cert->data);
+                      "SSL_CTX_set_ex_data() failed");
         return NGX_ERROR;
     }
 
+    X509_free(x509);
+
+    /* read rest of the chain */
+
+    for ( ;; ) {
+
+        x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
+        if (x509 == NULL) {
+            n = ERR_peek_last_error();
+
+            if (ERR_GET_LIB(n) == ERR_LIB_PEM
+                && ERR_GET_REASON(n) == PEM_R_NO_START_LINE)
+            {
+                /* end of file */
+                ERR_clear_error();
+                break;
+            }
+
+            /* some real error */
+
+            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                          "PEM_read_bio_X509(\"%s\") failed", cert->data);
+            BIO_free(bio);
+            return NGX_ERROR;
+        }
+
+        if (SSL_CTX_add_extra_chain_cert(ssl->ctx, x509) == 0) {
+            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                          "SSL_CTX_add_extra_chain_cert(\"%s\") failed",
+                          cert->data);
+            X509_free(x509);
+            BIO_free(bio);
+            return NGX_ERROR;
+        }
+    }
+
+    BIO_free(bio);
+
     if (ngx_conf_full_name(cf->cycle, key, 1) != NGX_OK) {
         return NGX_ERROR;
     }
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
@@ -105,7 +105,10 @@ ngx_int_t ngx_ssl_client_certificate(ngx
 ngx_int_t ngx_ssl_trusted_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl,
     ngx_str_t *cert, ngx_int_t depth);
 ngx_int_t ngx_ssl_crl(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *crl);
-ngx_int_t ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file);
+ngx_int_t ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_str_t *responder, ngx_str_t *file);
+ngx_int_t ngx_ssl_stapling_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_resolver_t *resolver, ngx_msec_t resolver_timeout);
 RSA *ngx_ssl_rsa512_key_callback(SSL *ssl, int is_export, int key_length);
 ngx_int_t ngx_ssl_dhparam(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file);
 ngx_int_t ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *name);
@@ -161,6 +164,8 @@ void ngx_ssl_cleanup_ctx(void *data);
 extern int  ngx_ssl_connection_index;
 extern int  ngx_ssl_server_conf_index;
 extern int  ngx_ssl_session_cache_index;
+extern int  ngx_ssl_certificate_index;
+extern int  ngx_ssl_stapling_index;
 
 
 #endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */
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
@@ -8,25 +8,183 @@
 #include <ngx_config.h>
 #include <ngx_core.h>
 #include <ngx_event.h>
+#include <ngx_event_connect.h>
 
 
+#ifdef SSL_CTRL_SET_TLSEXT_STATUS_REQ_CB
+
+typedef struct {
+    ngx_str_t                    staple;
+    ngx_msec_t                   timeout;
+
+    ngx_resolver_t              *resolver;
+    ngx_msec_t                   resolver_timeout;
+
+    ngx_addr_t                  *addrs;
+    ngx_str_t                    host;
+    ngx_str_t                    uri;
+    in_port_t                    port;
+
+    SSL_CTX                     *ssl_ctx;
+
+    X509                        *cert;
+    X509                        *issuer;
+
+    time_t                       valid;
+
+    ngx_uint_t                   loading;     /* unsigned:1 */
+} ngx_ssl_stapling_t;
+
+
+typedef struct ngx_ssl_ocsp_ctx_s  ngx_ssl_ocsp_ctx_t;
+
+struct ngx_ssl_ocsp_ctx_s {
+    X509                        *cert;
+    X509                        *issuer;
+
+    ngx_uint_t                   naddrs;
+
+    ngx_addr_t                  *addrs;
+    ngx_str_t                    host;
+    ngx_str_t                    uri;
+    in_port_t                    port;
+
+    ngx_resolver_t              *resolver;
+    ngx_msec_t                   resolver_timeout;
+
+    ngx_msec_t                   timeout;
+
+    void                       (*handler)(ngx_ssl_ocsp_ctx_t *r);
+    void                        *data;
+
+    ngx_buf_t                   *request;
+    ngx_buf_t                   *response;
+    ngx_peer_connection_t        peer;
+
+    ngx_int_t                  (*process)(ngx_ssl_ocsp_ctx_t *r);
+
+    ngx_uint_t                   state;
+
+    ngx_uint_t                   code;
+    ngx_uint_t                   count;
+
+    ngx_uint_t                   done;
+
+    u_char                      *header_name_start;
+    u_char                      *header_name_end;
+    u_char                      *header_start;
+    u_char                      *header_end;
+
+    ngx_pool_t                  *pool;
+    ngx_log_t                   *log;
+};
+
+
+static ngx_int_t ngx_ssl_stapling_file(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_str_t *file);
+static ngx_int_t ngx_ssl_stapling_responder(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_str_t *responder);
+
 static int ngx_ssl_certificate_status_callback(ngx_ssl_conn_t *ssl_conn,
     void *data);
+static void ngx_ssl_stapling_update(ngx_ssl_stapling_t *staple);
+static void ngx_ssl_stapling_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx);
+
+static X509 *ngx_ssl_stapling_get_issuer(SSL_CTX *ssl_ctx, X509 *cert,
+    ngx_log_t *log);
+
+static void ngx_ssl_stapling_cleanup(void *data);
+
+static ngx_ssl_ocsp_ctx_t *ngx_ssl_ocsp_start(void);
+static void ngx_ssl_ocsp_done(ngx_ssl_ocsp_ctx_t *ctx);
+static void ngx_ssl_ocsp_request(ngx_ssl_ocsp_ctx_t *ctx);
+static void ngx_ssl_ocsp_resolve_handler(ngx_resolver_ctx_t *resolve);
+static void ngx_ssl_ocsp_connect(ngx_ssl_ocsp_ctx_t *ctx);
+static void ngx_ssl_ocsp_write_handler(ngx_event_t *wev);
+static void ngx_ssl_ocsp_read_handler(ngx_event_t *rev);
+static void ngx_ssl_ocsp_dummy_handler(ngx_event_t *ev);
+
+static ngx_int_t ngx_ssl_ocsp_create_request(ngx_ssl_ocsp_ctx_t *ctx);
+static ngx_int_t ngx_ssl_ocsp_process_status_line(ngx_ssl_ocsp_ctx_t *ctx);
+static ngx_int_t ngx_ssl_ocsp_parse_status_line(ngx_ssl_ocsp_ctx_t *ctx);
+static ngx_int_t ngx_ssl_ocsp_process_headers(ngx_ssl_ocsp_ctx_t *ctx);
+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 u_char *ngx_ssl_ocsp_log_error(ngx_log_t *log, u_char *buf, size_t len);
 
 
 ngx_int_t
-ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file)
+ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *responder,
+    ngx_str_t *file)
 {
-    BIO            *bio;
-    int             len;
-    u_char         *p, *buf;
-    ngx_str_t      *staple;
-    OCSP_RESPONSE  *response;
+    ngx_pool_cleanup_t        *cln;
+    ngx_ssl_stapling_t        *staple;
 
-    if (file->len == 0) {
+    staple = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_stapling_t));
+    if (staple == NULL) {
+        return NGX_ERROR;
+    }
+
+    cln = ngx_pool_cleanup_add(cf->pool, 0);
+    if (cln == NULL) {
+        return NGX_ERROR;
+    }
+
+    cln->handler = ngx_ssl_stapling_cleanup;
+    cln->data = staple;
+
+    SSL_CTX_set_tlsext_status_cb(ssl->ctx, ngx_ssl_certificate_status_callback);
+    SSL_CTX_set_tlsext_status_arg(ssl->ctx, staple);
+
+    if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_stapling_index, staple)
+        == 0)
+    {
+        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                      "SSL_CTX_set_ex_data() failed");
+        return NGX_ERROR;
+    }
+
+    staple->ssl_ctx = ssl->ctx;
+    staple->timeout = 60000;
+
+    if (file->len) {
+        /* use OCSP response from the file */
+
+        if (ngx_ssl_stapling_file(cf, ssl, file) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
         return NGX_OK;
     }
 
+    staple->cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index);
+    staple->issuer = ngx_ssl_stapling_get_issuer(ssl->ctx, staple->cert,
+                                                 ssl->log);
+
+    if (staple->issuer == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_ssl_stapling_responder(cf, ssl, responder) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_ssl_stapling_file(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file)
+{
+    BIO                 *bio;
+    int                  len;
+    u_char              *p, *buf;
+    OCSP_RESPONSE       *response;
+    ngx_ssl_stapling_t  *staple;
+
+    staple = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_stapling_index);
+
     if (ngx_conf_full_name(cf->cycle, file, 1) != NGX_OK) {
         return NGX_ERROR;
     }
@@ -53,7 +211,7 @@ ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl
         goto failed;
     }
 
-    buf = ngx_pnalloc(cf->pool, len);
+    buf = ngx_alloc(len, ssl->log);
     if (buf == NULL) {
         goto failed;
     }
@@ -63,22 +221,15 @@ ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl
     if (len <= 0) {
         ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
                       "i2d_OCSP_RESPONSE(\"%s\") failed", file->data);
+        ngx_free(buf);
         goto failed;
     }
 
     OCSP_RESPONSE_free(response);
     BIO_free(bio);
 
-    staple = ngx_palloc(cf->pool, sizeof(ngx_str_t));
-    if (staple == NULL) {
-        return NGX_ERROR;
-    }
-
-    staple->data = buf;
-    staple->len = len;
-
-    SSL_CTX_set_tlsext_status_cb(ssl->ctx, ngx_ssl_certificate_status_callback);
-    SSL_CTX_set_tlsext_status_arg(ssl->ctx, staple);
+    staple->staple.data = buf;
+    staple->staple.len = len;
 
     return NGX_OK;
 
@@ -91,12 +242,112 @@ failed:
 }
 
 
+static ngx_int_t
+ngx_ssl_stapling_responder(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *responder)
+{
+    ngx_url_t                  u;
+    char                      *s;
+    ngx_ssl_stapling_t        *staple;
+    STACK_OF(OPENSSL_STRING)  *aia;
+
+    staple = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_stapling_index);
+
+    if (responder->len == 0) {
+
+        /* extract OCSP responder URL from certificate */
+
+        aia = X509_get1_ocsp(staple->cert);
+        if (aia == NULL) {
+            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                          "no OCSP responder URL in the certificate, "
+                          "use the \"ssl_stapling_responder\" directive "
+                          "to set one");
+            return NGX_ERROR;
+        }
+
+        s = sk_OPENSSL_STRING_value(aia, 0);
+        if (s == NULL) {
+            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
+                          "no OCSP responder URL in the certificate, "
+                          "use the \"ssl_stapling_responder\" directive "
+                          "to set one");
+            X509_email_free(aia);
+            return NGX_ERROR;
+        }
+
+        responder->len = ngx_strlen(s);
+        responder->data = ngx_palloc(cf->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;
+
+    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, ssl->log, 0,
+                      "invalid URL prefix in \"%V\"", &u.url);
+        return NGX_ERROR;
+    }
+
+    if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
+        if (u.err) {
+            ngx_log_error(NGX_LOG_ERR, ssl->log, 0,
+                          "%s in OCSP responder \"%V\"", u.err, &u.url);
+        }
+
+        return NGX_ERROR;
+    }
+
+    staple->addrs = u.addrs;
+    staple->host = u.host;
+    staple->uri = u.uri;
+    staple->port = u.port;
+
+    if (staple->uri.len == 0) {
+        ngx_str_set(&staple->uri, "/");
+    }
+
+    return NGX_OK;
+}
+
+
+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_ssl_stapling_t  *staple;
+
+    staple = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_stapling_index);
+
+    staple->resolver = resolver;
+    staple->resolver_timeout = resolver_timeout;
+
+    return NGX_OK;
+}
+
+
 static int
 ngx_ssl_certificate_status_callback(ngx_ssl_conn_t *ssl_conn, void *data)
 {
-    u_char            *p;
-    ngx_str_t         *staple;
-    ngx_connection_t  *c;
+    int                  rc;
+    u_char              *p;
+    ngx_connection_t    *c;
+    ngx_ssl_stapling_t  *staple;
 
     c = ngx_ssl_get_connection(ssl_conn);
 
@@ -104,18 +355,1338 @@ ngx_ssl_certificate_status_callback(ngx_
                    "SSL certificate status callback");
 
     staple = data;
+    rc = SSL_TLSEXT_ERR_NOACK;
 
-    /* we have to copy the staple as OpenSSL will free it by itself */
+    if (staple->staple.len) {
+        /* we have to copy ocsp response as OpenSSL will free it by itself */
 
-    p = OPENSSL_malloc(staple->len);
-    if (p == NULL) {
-        ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "OPENSSL_malloc() failed");
-        return SSL_TLSEXT_ERR_ALERT_FATAL;
+        p = OPENSSL_malloc(staple->staple.len);
+        if (p == NULL) {
+            ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "OPENSSL_malloc() failed");
+            return SSL_TLSEXT_ERR_NOACK;
+        }
+
+        ngx_memcpy(p, staple->staple.data, staple->staple.len);
+
+        SSL_set_tlsext_status_ocsp_resp(ssl_conn, p, staple->staple.len);
+
+        rc = SSL_TLSEXT_ERR_OK;
     }
 
-    ngx_memcpy(p, staple->data, staple->len);
+    ngx_ssl_stapling_update(staple);
 
-    SSL_set_tlsext_status_ocsp_resp(ssl_conn, p, staple->len);
+    return rc;
+}
 
-    return SSL_TLSEXT_ERR_OK;
+
+static void
+ngx_ssl_stapling_update(ngx_ssl_stapling_t *staple)
+{
+    ngx_ssl_ocsp_ctx_t  *ctx;
+
+    if (staple->host.len == 0
+        || staple->loading || staple->valid >= ngx_time())
+    {
+        return;
+    }
+
+    staple->loading = 1;
+
+    ctx = ngx_ssl_ocsp_start();
+    if (ctx == NULL) {
+        return;
+    }
+
+    ctx->cert = staple->cert;
+    ctx->issuer = staple->issuer;
+
+    ctx->addrs = staple->addrs;
+    ctx->host = staple->host;
+    ctx->uri = staple->uri;
+    ctx->port = staple->port;
+    ctx->timeout = staple->timeout;
+
+    ctx->resolver = staple->resolver;
+    ctx->resolver_timeout = staple->resolver_timeout;
+
+    ctx->handler = ngx_ssl_stapling_ocsp_handler;
+    ctx->data = staple;
+
+    ngx_ssl_ocsp_request(ctx);
+
+    return;
 }
+
+
+static X509 *
+ngx_ssl_stapling_get_issuer(SSL_CTX *ssl_ctx, X509 *cert, ngx_log_t *log)
+{
+    int              i, n, rc;
+    X509            *issuer;
+    X509_STORE      *store;
+    X509_STORE_CTX  *store_ctx;
+    STACK_OF(X509)  *chain;
+
+#if OPENSSL_VERSION_NUMBER >= 0x10001000L
+    SSL_CTX_get_extra_chain_certs(ssl_ctx, &chain);
+#else
+    chain = ssl_ctx->extra_certs;
+#endif
+
+    n = sk_X509_num(chain);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
+                   "SSL get issuer: %d extra certs", n);
+
+    for (i = 0; i < n; i++) {
+        issuer = sk_X509_value(chain, i);
+        if (X509_check_issued(issuer, cert) == X509_V_OK) {
+            CRYPTO_add(&issuer->references, 1, CRYPTO_LOCK_X509);
+
+            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
+                           "SSL get issuer: found %p in extra certs", issuer);
+
+            return issuer;
+        }
+    }
+
+    store = SSL_CTX_get_cert_store(ssl_ctx);
+    if (store == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, log, 0,
+                      "SSL_CTX_get_cert_store() failed");
+        return NULL;
+    }
+
+    store_ctx = X509_STORE_CTX_new();
+    if (store_ctx == NULL) {
+        ngx_ssl_error(NGX_LOG_EMERG, log, 0,
+                      "X509_STORE_CTX_new() failed");
+        return NULL;
+    }
+
+    if (X509_STORE_CTX_init(store_ctx, store, NULL, NULL) == 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, log, 0,
+                      "X509_STORE_CTX_init() failed");
+        return NULL;
+    }
+
+    rc = X509_STORE_CTX_get1_issuer(&issuer, store_ctx, cert);
+
+    if (rc == -1) {
+        ngx_ssl_error(NGX_LOG_EMERG, log, 0,
+                      "X509_STORE_CTX_get1_issuer() failed");
+        X509_STORE_CTX_free(store_ctx);
+        return NULL;
+    }
+
+    if (rc == 0) {
+        /* TODO: provide better error message */
+        ngx_log_error(NGX_LOG_EMERG, log, 0,
+                      "X509_STORE_CTX_get1_issuer() failed, "
+                      "issuer certificate not found");
+        X509_STORE_CTX_free(store_ctx);
+        return NULL;
+    }
+
+    X509_STORE_CTX_free(store_ctx);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0,
+                   "SSL get issuer: found %p in cert store", issuer);
+
+    return issuer;
+}
+
+
+static void
+ngx_ssl_stapling_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x0090707fL
+    const
+#endif
+    u_char                *p;
+    int                    n;
+    size_t                 len;
+    ngx_str_t              response;
+    X509_STORE            *store;
+    STACK_OF(X509)        *chain;
+    OCSP_CERTID           *id;
+    OCSP_RESPONSE         *ocsp;
+    OCSP_BASICRESP        *basic;
+    ngx_ssl_stapling_t    *staple;
+    ASN1_GENERALIZEDTIME  *thisupdate, *nextupdate;
+
+    staple = ctx->data;
+    ocsp = NULL;
+    basic = NULL;
+    id = NULL;
+
+    if (ctx->code != 200) {
+        goto error;
+    }
+
+    /* check the response */
+
+    len = ctx->response->last - ctx->response->pos;
+    p = ctx->response->pos;
+
+    ocsp = d2i_OCSP_RESPONSE(NULL, &p, len);
+    if (ocsp == NULL) {
+        ngx_ssl_error(NGX_LOG_ERR, ctx->log, 0,
+                      "d2i_OCSP_RESPONSE() failed");
+        goto error;
+    }
+
+    n = OCSP_response_status(ocsp);
+
+    if (n != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
+        ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+                      "OCSP response not successfull (%d: %s)",
+                      n, OCSP_response_status_str(n));
+        goto error;
+    }
+
+    basic = OCSP_response_get1_basic(ocsp);
+    if (basic == NULL) {
+        ngx_ssl_error(NGX_LOG_ERR, ctx->log, 0,
+                      "OCSP_response_get1_basic() failed");
+        goto error;
+    }
+
+    store = SSL_CTX_get_cert_store(staple->ssl_ctx);
+    if (store == NULL) {
+        ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0,
+                      "SSL_CTX_get_cert_store() failed");
+        goto error;
+    }
+
+#if OPENSSL_VERSION_NUMBER >= 0x10001000L
+    SSL_CTX_get_extra_chain_certs(staple->ssl_ctx, &chain);
+#else
+    chain = staple->ssl_ctx->extra_certs;
+#endif
+
+    if (OCSP_basic_verify(basic, chain, store, 0) != 1) {
+        ngx_ssl_error(NGX_LOG_ERR, ctx->log, 0,
+                      "OCSP_basic_verify() failed");
+        goto error;
+    }
+
+    id = OCSP_cert_to_id(NULL, ctx->cert, ctx->issuer);
+    if (id == NULL) {
+        ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0,
+                      "OCSP_cert_to_id() failed");
+        goto error;
+    }
+
+    if (OCSP_resp_find_status(basic, id, &n, NULL, NULL,
+                              &thisupdate, &nextupdate)
+        != 1)
+    {
+        ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+                      "certificate status not found in the OCSP response",
+                      n, OCSP_response_status_str(n));
+        goto error;
+    }
+
+    if (n != V_OCSP_CERTSTATUS_GOOD) {
+        ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+                      "certificate status \"%s\" in the OCSP response",
+                      n, OCSP_cert_status_str(n));
+        goto error;
+    }
+
+    if (OCSP_check_validity(thisupdate, nextupdate, 300, -1) != 1) {
+        ngx_ssl_error(NGX_LOG_ERR, ctx->log, 0,
+                      "OCSP_check_validity() failed");
+        goto error;
+    }
+
+    OCSP_CERTID_free(id);
+    OCSP_BASICRESP_free(basic);
+    OCSP_RESPONSE_free(ocsp);
+
+    /* copy the response to memory not in ctx->pool */
+
+    response.len = len;
+    response.data = ngx_alloc(response.len, ctx->log);
+
+    if (response.data == NULL) {
+        goto done;
+    }
+
+    ngx_memcpy(response.data, ctx->response->pos, response.len);
+
+    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp response, %s, %uz",
+                   OCSP_cert_status_str(n), response.len);
+
+    if (staple->staple.data) {
+        ngx_free(staple->staple.data);
+    }
+
+    staple->staple = response;
+
+done:
+
+    staple->loading = 0;
+    staple->valid = ngx_time() + 3600; /* ssl_stapling_valid */
+
+    ngx_ssl_ocsp_done(ctx);
+    return;
+
+error:
+
+    staple->loading = 0;
+    staple->valid = ngx_time() + 300; /* ssl_stapling_err_valid */
+
+    if (id) {
+        OCSP_CERTID_free(id);
+    }
+
+    if (basic) {
+        OCSP_BASICRESP_free(basic);
+    }
+
+    if (ocsp) {
+        OCSP_RESPONSE_free(ocsp);
+    }
+
+    ngx_ssl_ocsp_done(ctx);
+}
+
+
+static void
+ngx_ssl_stapling_cleanup(void *data)
+{
+    ngx_ssl_stapling_t  *staple = data;
+
+    if (staple->issuer) {
+        X509_free(staple->issuer);
+    }
+
+    if (staple->staple.data) {
+        ngx_free(staple->staple.data);
+    }
+}
+
+
+static ngx_ssl_ocsp_ctx_t *
+ngx_ssl_ocsp_start(void)
+{
+    ngx_log_t           *log;
+    ngx_pool_t          *pool;
+    ngx_ssl_ocsp_ctx_t  *ctx;
+
+    pool = ngx_create_pool(2048, ngx_cycle->log);
+    if (pool == NULL) {
+        return NULL;
+    }
+
+    ctx = ngx_pcalloc(pool, sizeof(ngx_ssl_ocsp_ctx_t));
+    if (ctx == NULL) {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+    log = ngx_palloc(pool, sizeof(ngx_log_t));
+    if (log == NULL) {
+        ngx_destroy_pool(pool);
+        return NULL;
+    }
+
+    ctx->pool = pool;
+
+    *log = *ctx->pool->log;
+
+    ctx->pool->log = log;
+    ctx->log = log;
+
+    log->handler = ngx_ssl_ocsp_log_error;
+    log->data = ctx;
+    log->action = "requesting certificate status";
+
+    return ctx;
+}
+
+
+static void
+ngx_ssl_ocsp_done(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp done");
+
+    if (ctx->peer.connection) {
+        ngx_close_connection(ctx->peer.connection);
+    }
+
+    ngx_destroy_pool(ctx->pool);
+}
+
+
+static void
+ngx_ssl_ocsp_error(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp error");
+
+    ctx->code = 0;
+    ctx->handler(ctx);
+}
+
+
+static void
+ngx_ssl_ocsp_request(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    ngx_resolver_ctx_t  *resolve, temp;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp request");
+
+    if (ngx_ssl_ocsp_create_request(ctx) != NGX_OK) {
+        ngx_ssl_ocsp_error(ctx);
+        return;
+    }
+
+    if (ctx->resolver) {
+        /* resolve OCSP responder hostname */
+
+        temp.name = ctx->host;
+
+        resolve = ngx_resolve_start(ctx->resolver, &temp);
+        if (resolve == NULL) {
+            ngx_ssl_ocsp_error(ctx);
+            return;
+        }
+
+        if (resolve == NGX_NO_RESOLVER) {
+            ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+                          "no resolver defined to resolve %V", &ctx->host);
+            ngx_ssl_ocsp_error(ctx);
+            return;
+        }
+
+        resolve->name = ctx->host;
+        resolve->type = NGX_RESOLVE_A;
+        resolve->handler = ngx_ssl_ocsp_resolve_handler;
+        resolve->data = ctx;
+        resolve->timeout = ctx->resolver_timeout;
+
+        if (ngx_resolve_name(resolve) != NGX_OK) {
+            ngx_ssl_ocsp_error(ctx);
+            return;
+        }
+
+        return;
+    }
+
+    ngx_ssl_ocsp_connect(ctx);
+}
+
+
+static void
+ngx_ssl_ocsp_resolve_handler(ngx_resolver_ctx_t *resolve)
+{
+    ngx_ssl_ocsp_ctx_t *ctx = resolve->data;
+
+    u_char              *p;
+    size_t               len;
+    in_port_t            port;
+    ngx_uint_t           i;
+    struct sockaddr_in  *sin;
+
+    ngx_log_debug0(NGX_LOG_ALERT, ctx->log, 0,
+                   "ssl ocsp resolve handler");
+
+    if (resolve->state) {
+        ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+                      "%V could not be resolved (%i: %s)",
+                      &resolve->name, resolve->state,
+                      ngx_resolver_strerror(resolve->state));
+        goto failed;
+    }
+
+#if (NGX_DEBUG)
+    {
+    in_addr_t   addr;
+
+    for (i = 0; i < resolve->naddrs; i++) {
+        addr = ntohl(resolve->addrs[i]);
+
+        ngx_log_debug4(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                       "name was resolved to %ud.%ud.%ud.%ud",
+                       (addr >> 24) & 0xff, (addr >> 16) & 0xff,
+                       (addr >> 8) & 0xff, addr & 0xff);
+    }
+    }
+#endif
+
+    ctx->naddrs = resolve->naddrs;
+    ctx->addrs = ngx_pcalloc(ctx->pool, ctx->naddrs * sizeof(ngx_addr_t));
+
+    if (ctx->addrs == NULL) {
+        goto failed;
+    }
+
+    port = htons(ctx->port);
+
+    for (i = 0; i < resolve->naddrs; i++) {
+
+        sin = ngx_pcalloc(ctx->pool, sizeof(struct sockaddr_in));
+        if (sin == NULL) {
+            goto failed;
+        }
+
+        sin->sin_family = AF_INET;
+        sin->sin_port = port;
+        sin->sin_addr.s_addr = resolve->addrs[i];
+
+        ctx->addrs[i].sockaddr = (struct sockaddr *) sin;
+        ctx->addrs[i].socklen = sizeof(struct sockaddr_in);
+
+        len = NGX_INET_ADDRSTRLEN + sizeof(":65535") - 1;
+
+        p = ngx_pnalloc(ctx->pool, len);
+        if (p == NULL) {
+            goto failed;
+        }
+
+        len = ngx_sock_ntop((struct sockaddr *) sin, p, len, 1);
+
+        ctx->addrs[i].name.len = len;
+        ctx->addrs[i].name.data = p;
+    }
+
+    ngx_resolve_name_done(resolve);
+
+    ngx_ssl_ocsp_connect(ctx);
+    return;
+
+failed:
+
+    ngx_resolve_name_done(resolve);
+    ngx_ssl_ocsp_error(ctx);
+}
+
+
+static void
+ngx_ssl_ocsp_connect(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    ngx_int_t    rc;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp connect");
+
+    /* TODO: use all ip addresses */
+
+    ctx->peer.sockaddr = ctx->addrs[0].sockaddr;
+    ctx->peer.socklen = ctx->addrs[0].socklen;
+    ctx->peer.name = &ctx->addrs[0].name;
+    ctx->peer.get = ngx_event_get_peer;
+    ctx->peer.log = ctx->log;
+    ctx->peer.log_error = NGX_ERROR_ERR;
+
+    rc = ngx_event_connect_peer(&ctx->peer);
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp connect peer done");
+
+    if (rc == NGX_ERROR || rc == NGX_BUSY || rc == NGX_DECLINED) {
+        ngx_ssl_ocsp_error(ctx);
+        return;
+    }
+
+    ctx->peer.connection->data = ctx;
+    ctx->peer.connection->pool = ctx->pool;
+
+    ctx->peer.connection->read->handler = ngx_ssl_ocsp_read_handler;
+    ctx->peer.connection->write->handler = ngx_ssl_ocsp_write_handler;
+
+    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 (rc == NGX_OK) {
+        ngx_ssl_ocsp_write_handler(ctx->peer.connection->write);
+        return;
+    }
+}
+
+
+static void
+ngx_ssl_ocsp_write_handler(ngx_event_t *wev)
+{
+    ssize_t              n, size;
+    ngx_connection_t    *c;
+    ngx_ssl_ocsp_ctx_t  *ctx;
+
+    c = wev->data;
+    ctx = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, wev->log, 0,
+                   "ssl ocsp write handler");
+
+    if (wev->timedout) {
+        ngx_log_error(NGX_LOG_ERR, wev->log, NGX_ETIMEDOUT,
+                      "OCSP responder timed out");
+        ngx_ssl_ocsp_error(ctx);
+        return;
+    }
+
+    size = ctx->request->last - ctx->request->pos;
+
+    n = ngx_send(c, ctx->request->pos, size);
+
+    if (n == NGX_ERROR) {
+        ngx_ssl_ocsp_error(ctx);
+        return;
+    }
+
+    if (n > 0) {
+        ctx->request->pos += n;
+
+        if (n == size) {
+            wev->handler = ngx_ssl_ocsp_dummy_handler;
+
+            if (wev->timer_set) {
+                ngx_del_timer(wev);
+            }
+
+            if (ngx_handle_write_event(wev, 0) != NGX_OK) {
+                ngx_ssl_ocsp_error(ctx);
+            }
+
+            return;
+        }
+    }
+
+    if (!wev->timer_set) {
+        ngx_add_timer(wev, ctx->timeout);
+    }
+}
+
+
+static void
+ngx_ssl_ocsp_read_handler(ngx_event_t *rev)
+{
+    ssize_t            n, size;
+    ngx_int_t          rc;
+    ngx_ssl_ocsp_ctx_t    *ctx;
+    ngx_connection_t  *c;
+
+    c = rev->data;
+    ctx = c->data;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0,
+                   "ssl ocsp read handler");
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_ERR, rev->log, NGX_ETIMEDOUT,
+                      "OCSP responder timed out");
+        ngx_ssl_ocsp_error(ctx);
+        return;
+    }
+
+    if (ctx->response == NULL) {
+        ctx->response = ngx_create_temp_buf(ctx->pool, 16384);
+        if (ctx->response == NULL) {
+            ngx_ssl_ocsp_error(ctx);
+            return;
+        }
+    }
+
+    for ( ;; ) {
+
+        size = ctx->response->end - ctx->response->last;
+
+        n = ngx_recv(c, ctx->response->last, size);
+
+        if (n > 0) {
+            ctx->response->last += n;
+
+            rc = ctx->process(ctx);
+
+            if (rc == NGX_ERROR) {
+                ngx_ssl_ocsp_error(ctx);
+                return;
+            }
+
+            continue;
+        }
+
+        if (n == NGX_AGAIN) {
+
+            if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+                ngx_ssl_ocsp_error(ctx);
+            }
+
+            return;
+        }
+
+        break;
+    }
+
+    ctx->done = 1;
+
+    rc = ctx->process(ctx);
+
+    if (rc == NGX_DONE) {
+        /* ctx->handler() was called */
+        return;
+    }
+
+    ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+                  "OCSP responder prematurely closed connection");
+
+    ngx_ssl_ocsp_error(ctx);
+}
+
+
+static void
+ngx_ssl_ocsp_dummy_handler(ngx_event_t *ev)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0,
+                   "ssl ocsp dummy handler");
+}
+
+
+static ngx_int_t
+ngx_ssl_ocsp_create_request(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    int            len;
+    u_char        *p;
+    uintptr_t      escape;
+    ngx_str_t      binary, base64;
+    ngx_buf_t     *b;
+    OCSP_CERTID   *id;
+    OCSP_REQUEST  *ocsp;
+
+    ocsp = OCSP_REQUEST_new();
+    if (ocsp == NULL) {
+        ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0,
+                      "OCSP_REQUEST_new() failed");
+        return NGX_ERROR;
+    }
+
+    id = OCSP_cert_to_id(NULL, ctx->cert, ctx->issuer);
+    if (id == NULL) {
+        ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0,
+                      "OCSP_cert_to_id() failed");
+        goto failed;
+    }
+
+    if (OCSP_request_add0_id(ocsp, id) == NULL) {
+        ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0,
+                      "OCSP_request_add0_id() failed");
+        goto failed;
+    }
+
+    len = i2d_OCSP_REQUEST(ocsp, NULL);
+    if (len <= 0) {
+        ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0,
+                      "i2d_OCSP_REQUEST() failed");
+        goto failed;
+    }
+
+    binary.len = len;
+    binary.data = ngx_palloc(ctx->pool, len);
+    if (binary.data == NULL) {
+        goto failed;
+    }
+
+    p = binary.data;
+    len = i2d_OCSP_REQUEST(ocsp, &p);
+    if (len <= 0) {
+        ngx_ssl_error(NGX_LOG_EMERG, ctx->log, 0,
+                      "i2d_OCSP_REQUEST() failed");
+        goto failed;
+    }
+
+    base64.len = ngx_base64_encoded_length(binary.len);
+    base64.data = ngx_palloc(ctx->pool, base64.len);
+    if (base64.data == NULL) {
+        goto failed;
+    }
+
+    ngx_encode_base64(&base64, &binary);
+
+    escape = ngx_escape_uri(NULL, base64.data, base64.len,
+                            NGX_ESCAPE_URI_COMPONENT);
+
+    ngx_log_debug(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                  "ssl ocsp request length %z, escape %d",
+                  base64.len, escape);
+
+    len = sizeof("GET ") - 1 + ctx->uri.len + sizeof("/") - 1
+          + base64.len + 2 * escape + sizeof(" HTTP/1.0" CRLF) - 1
+          + sizeof("Host: ") - 1 + ctx->host.len + sizeof(CRLF) - 1
+          + sizeof(CRLF) - 1;
+
+    b = ngx_create_temp_buf(ctx->pool, len);
+    if (b == NULL) {
+        goto failed;
+    }
+
+    p = b->last;
+
+    p = ngx_cpymem(p, "GET ", sizeof("GET ") - 1);
+    p = ngx_cpymem(p, ctx->uri.data, ctx->uri.len);
+
+    if (ctx->uri.data[ctx->uri.len - 1] != '/') {
+        *p++ = '/';
+    }
+
+    if (escape == 0) {
+        p = ngx_cpymem(p, base64.data, base64.len);
+
+    } else {
+        p = (u_char *) ngx_escape_uri(p, base64.data, base64.len,
+                                      NGX_ESCAPE_URI_COMPONENT);
+    }
+
+    p = ngx_cpymem(p, " HTTP/1.0" CRLF, sizeof(" HTTP/1.0" CRLF) - 1);
+    p = ngx_cpymem(p, "Host: ", sizeof("Host: ") - 1);
+    p = ngx_cpymem(p, ctx->host.data, ctx->host.len);
+    *p++ = CR; *p++ = LF;
+
+    /* add "\r\n" at the header end */
+    *p++ = CR; *p++ = LF;
+
+    b->last = p;
+    ctx->request = b;
+
+    return NGX_OK;
+
+failed:
+
+    OCSP_REQUEST_free(ocsp);
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_ssl_ocsp_process_status_line(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    ngx_int_t  rc;
+
+    rc = ngx_ssl_ocsp_parse_status_line(ctx);
+
+    if (rc == NGX_OK) {
+#if 0
+        ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                       "ssl ocsp status line \"%*s\"",
+                       ctx->response->pos - ctx->response->start,
+                       ctx->response->start);
+#endif
+
+        ctx->process = ngx_ssl_ocsp_process_headers;
+        return ctx->process(ctx);
+    }
+
+    if (rc == NGX_AGAIN) {
+        return NGX_AGAIN;
+    }
+
+    /* rc == NGX_ERROR */
+
+    ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+                  "OCSP responder sent invalid response");
+
+    return NGX_ERROR;
+}
+
+
+static ngx_int_t
+ngx_ssl_ocsp_parse_status_line(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    u_char      ch;
+    u_char     *p;
+    ngx_buf_t  *b;
+    enum {
+        sw_start = 0,
+        sw_H,
+        sw_HT,
+        sw_HTT,
+        sw_HTTP,
+        sw_first_major_digit,
+        sw_major_digit,
+        sw_first_minor_digit,
+        sw_minor_digit,
+        sw_status,
+        sw_space_after_status,
+        sw_status_text,
+        sw_almost_done
+    } state;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp process status line");
+
+    state = ctx->state;
+    b = ctx->response;
+
+    for (p = b->pos; p < b->last; p++) {
+        ch = *p;
+
+        switch (state) {
+
+        /* "HTTP/" */
+        case sw_start:
+            switch (ch) {
+            case 'H':
+                state = sw_H;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        case sw_H:
+            switch (ch) {
+            case 'T':
+                state = sw_HT;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        case sw_HT:
+            switch (ch) {
+            case 'T':
+                state = sw_HTT;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        case sw_HTT:
+            switch (ch) {
+            case 'P':
+                state = sw_HTTP;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        case sw_HTTP:
+            switch (ch) {
+            case '/':
+                state = sw_first_major_digit;
+                break;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        /* the first digit of major HTTP version */
+        case sw_first_major_digit:
+            if (ch < '1' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            state = sw_major_digit;
+            break;
+
+        /* the major HTTP version or dot */
+        case sw_major_digit:
+            if (ch == '.') {
+                state = sw_first_minor_digit;
+                break;
+            }
+
+            if (ch < '0' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            break;
+
+        /* the first digit of minor HTTP version */
+        case sw_first_minor_digit:
+            if (ch < '0' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            state = sw_minor_digit;
+            break;
+
+        /* the minor HTTP version or the end of the request line */
+        case sw_minor_digit:
+            if (ch == ' ') {
+                state = sw_status;
+                break;
+            }
+
+            if (ch < '0' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            break;
+
+        /* HTTP status code */
+        case sw_status:
+            if (ch == ' ') {
+                break;
+            }
+
+            if (ch < '0' || ch > '9') {
+                return NGX_ERROR;
+            }
+
+            ctx->code = ctx->code * 10 + ch - '0';
+
+            if (++ctx->count == 3) {
+                state = sw_space_after_status;
+            }
+
+            break;
+
+        /* space or end of line */
+        case sw_space_after_status:
+            switch (ch) {
+            case ' ':
+                state = sw_status_text;
+                break;
+            case '.':                    /* IIS may send 403.1, 403.2, etc */
+                state = sw_status_text;
+                break;
+            case CR:
+                state = sw_almost_done;
+                break;
+            case LF:
+                goto done;
+            default:
+                return NGX_ERROR;
+            }
+            break;
+
+        /* any text until end of line */
+        case sw_status_text:
+            switch (ch) {
+            case CR:
+                state = sw_almost_done;
+                break;
+            case LF:
+                goto done;
+            }
+            break;
+
+        /* end of status line */
+        case sw_almost_done:
+            switch (ch) {
+            case LF:
+                goto done;
+            default:
+                return NGX_ERROR;
+            }
+        }
+    }
+
+    b->pos = p;
+    ctx->state = state;
+
+    return NGX_AGAIN;
+
+done:
+
+    b->pos = p + 1;
+    ctx->state = sw_start;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_ssl_ocsp_process_headers(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    ngx_int_t  rc;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp process headers");
+
+    for ( ;; ) {
+        rc = ngx_ssl_ocsp_parse_header_line(ctx);
+
+        if (rc == NGX_OK) {
+
+            ngx_log_debug4(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                           "ssl ocsp header \"%*s: %*s\"",
+                           ctx->header_name_end - ctx->header_name_start,
+                           ctx->header_name_start,
+                           ctx->header_end - ctx->header_start,
+                           ctx->header_start);
+
+            /* TODO: honor Content-Length */
+
+            continue;
+        }
+
+        if (rc == NGX_DONE) {
+            break;
+        }
+
+        if (rc == NGX_AGAIN) {
+            return NGX_AGAIN;
+        }
+
+        /* rc == NGX_ERROR */
+
+        ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
+                      "OCSP responder sent invalid response");
+
+        return NGX_ERROR;
+    }
+
+    ctx->process = ngx_ssl_ocsp_process_body;
+    return ctx->process(ctx);
+}
+
+static ngx_int_t
+ngx_ssl_ocsp_parse_header_line(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    u_char      c, ch, *p;
+    enum {
+        sw_start = 0,
+        sw_name,
+        sw_space_before_value,
+        sw_value,
+        sw_space_after_value,
+        sw_almost_done,
+        sw_header_almost_done
+    } state;
+
+    state = ctx->state;
+
+    for (p = ctx->response->pos; p < ctx->response->last; p++) {
+        ch = *p;
+
+#if 0
+        ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                       "s:%d in:'%02Xd:%c'", state, ch, ch);
+#endif
+
+        switch (state) {
+
+        /* first char */
+        case sw_start:
+
+            switch (ch) {
+            case CR:
+                ctx->header_end = p;
+                state = sw_header_almost_done;
+                break;
+            case LF:
+                ctx->header_end = p;
+                goto header_done;
+            default:
+                state = sw_name;
+                ctx->header_name_start = p;
+
+                c = (u_char) (ch | 0x20);
+                if (c >= 'a' && c <= 'z') {
+                    break;
+                }
+
+                if (ch >= '0' && ch <= '9') {
+                    break;
+                }
+
+                return NGX_ERROR;
+            }
+            break;
+
+        /* header name */
+        case sw_name:
+            c = (u_char) (ch | 0x20);
+            if (c >= 'a' && c <= 'z') {
+                break;
+            }
+
+            if (ch == ':') {
+                ctx->header_name_end = p;
+                state = sw_space_before_value;
+                break;
+            }
+
+            if (ch == '-') {
+                break;
+            }
+
+            if (ch >= '0' && ch <= '9') {
+                break;
+            }
+
+            if (ch == CR) {
+                ctx->header_name_end = p;
+                ctx->header_start = p;
+                ctx->header_end = p;
+                state = sw_almost_done;
+                break;
+            }
+
+            if (ch == LF) {
+                ctx->header_name_end = p;
+                ctx->header_start = p;
+                ctx->header_end = p;
+                goto done;
+            }
+
+            return NGX_ERROR;
+
+        /* space* before header value */
+        case sw_space_before_value:
+            switch (ch) {
+            case ' ':
+                break;
+            case CR:
+                ctx->header_start = p;
+                ctx->header_end = p;
+                state = sw_almost_done;
+                break;
+            case LF:
+                ctx->header_start = p;
+                ctx->header_end = p;
+                goto done;
+            default:
+                ctx->header_start = p;
+                state = sw_value;
+                break;
+            }
+            break;
+
+        /* header value */
+        case sw_value:
+            switch (ch) {
+            case ' ':
+                ctx->header_end = p;
+                state = sw_space_after_value;
+                break;
+            case CR:
+                ctx->header_end = p;
+                state = sw_almost_done;
+                break;
+            case LF:
+                ctx->header_end = p;
+                goto done;
+            }
+            break;
+
+        /* space* before end of header line */
+        case sw_space_after_value:
+            switch (ch) {
+            case ' ':
+                break;
+            case CR:
+                state = sw_almost_done;
+                break;
+            case LF:
+                goto done;
+            default:
+                state = sw_value;
+                break;
+            }
+            break;
+
+        /* end of header line */
+        case sw_almost_done:
+            switch (ch) {
+            case LF:
+                goto done;
+            default:
+                return NGX_ERROR;
+            }
+
+        /* end of header */
+        case sw_header_almost_done:
+            switch (ch) {
+            case LF:
+                goto header_done;
+            default:
+                return NGX_ERROR;
+            }
+        }
+    }
+
+    ctx->response->pos = p;
+    ctx->state = state;
+
+    return NGX_AGAIN;
+
+done:
+
+    ctx->response->pos = p + 1;
+    ctx->state = sw_start;
+
+    return NGX_OK;
+
+header_done:
+
+    ctx->response->pos = p + 1;
+    ctx->state = sw_start;
+
+    return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_ssl_ocsp_process_body(ngx_ssl_ocsp_ctx_t *ctx)
+{
+    ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
+                   "ssl ocsp process body");
+
+    if (ctx->done) {
+        ctx->handler(ctx);
+        return NGX_DONE;
+    }
+
+    return NGX_AGAIN;
+}
+
+
+static u_char *
+ngx_ssl_ocsp_log_error(ngx_log_t *log, u_char *buf, size_t len)
+{
+    u_char              *p;
+    ngx_ssl_ocsp_ctx_t  *ctx;
+
+    p = buf;
+
+    if (log->action) {
+        p = ngx_snprintf(buf, len, " while %s", log->action);
+        len -= p - buf;
+    }
+
+    ctx = log->data;
+
+    if (ctx) {
+        p = ngx_snprintf(p, len, ", responder: %V", &ctx->host);
+    }
+
+    return p;
+}
+
+
+#else
+
+
+ngx_int_t
+ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *responder,
+    ngx_str_t *file)
+{
+    ngx_log_error(NGX_LOG_WARN, ssl->log, 0,
+                  "OCSP stapling not supported on this platform, ignored");
+
+    return NGX_OK;
+}
+
+ngx_int_t
+ngx_ssl_stapling_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl,
+    ngx_resolver_t *resolver, ngx_msec_t resolver_timeout)
+{
+    return NGX_OK;
+}
+
+
+#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
@@ -33,6 +33,8 @@ static char *ngx_http_ssl_enable(ngx_con
 static char *ngx_http_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
 
+static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf);
+
 
 static ngx_conf_bitmask_t  ngx_http_ssl_protocols[] = {
     { ngx_string("SSLv2"), NGX_SSL_SSLv2 },
@@ -173,13 +175,20 @@ static ngx_command_t  ngx_http_ssl_comma
       offsetof(ngx_http_ssl_srv_conf_t, stapling_file),
       NULL },
 
+    { ngx_string("ssl_stapling_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, stapling_responder),
+      NULL },
+
       ngx_null_command
 };
 
 
 static ngx_http_module_t  ngx_http_ssl_module_ctx = {
     ngx_http_ssl_add_variables,            /* preconfiguration */
-    NULL,                                  /* postconfiguration */
+    ngx_http_ssl_init,                     /* postconfiguration */
 
     NULL,                                  /* create main configuration */
     NULL,                                  /* init main configuration */
@@ -351,6 +360,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t 
      *     sscf->ciphers = { 0, NULL };
      *     sscf->shm_zone = NULL;
      *     sscf->stapling_file = { 0, NULL };
+     *     sscf->stapling_responder = { 0, NULL };
      */
 
     sscf->enable = NGX_CONF_UNSET;
@@ -415,6 +425,8 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *
 
     ngx_conf_merge_value(conf->stapling, prev->stapling, 0);
     ngx_conf_merge_str_value(conf->stapling_file, prev->stapling_file, "");
+    ngx_conf_merge_str_value(conf->stapling_responder,
+                         prev->stapling_responder, "");
 
     conf->ssl.log = cf->log;
 
@@ -551,10 +563,15 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *
         return NGX_CONF_ERROR;
     }
 
-    if (conf->stapling
-        && ngx_ssl_stapling(cf, &conf->ssl, &conf->stapling_file) != NGX_OK)
-    {
-        return NGX_CONF_ERROR;
+    if (conf->stapling) {
+
+        if (ngx_ssl_stapling(cf, &conf->ssl, &conf->stapling_responder,
+                            &conf->stapling_file)
+            != NGX_OK)
+        {
+            return NGX_CONF_ERROR;
+        }
+
     }
 
     return NGX_CONF_OK;
@@ -692,3 +709,37 @@ invalid:
 
     return NGX_CONF_ERROR;
 }
+
+
+static ngx_int_t
+ngx_http_ssl_init(ngx_conf_t *cf)
+{
+    ngx_uint_t                   s;
+    ngx_http_ssl_srv_conf_t     *sscf;
+    ngx_http_core_loc_conf_t    *clcf;
+    ngx_http_core_srv_conf_t   **cscfp;
+    ngx_http_core_main_conf_t   *cmcf;
+
+    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+    cscfp = cmcf->servers.elts;
+
+    for (s = 0; s < cmcf->servers.nelts; s++) {
+
+        sscf = cscfp[s]->ctx->srv_conf[ngx_http_ssl_module.ctx_index];
+
+        if (!sscf->stapling) {
+            continue;
+        }
+
+        clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];
+
+        if (ngx_ssl_stapling_resolver(cf, &sscf->ssl, clcf->resolver,
+                                      clcf->resolver_timeout)
+            != NGX_OK)
+        {
+            return NGX_ERROR;
+        }
+    }
+
+    return NGX_OK;
+}
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
@@ -44,6 +44,7 @@ typedef struct {
 
     ngx_flag_t                      stapling;
     ngx_str_t                       stapling_file;
+    ngx_str_t                       stapling_responder;
 
     u_char                         *file;
     ngx_uint_t                      line;



More information about the nginx-devel mailing list