# HG changeset patch # User Maxim Dounin # Date 1346859570 -14400 # Node ID c131e9fd3c996d8e4032fe026d3f2676390dbf8c # Parent 2546a085a923633d40feb2f0b495f02e4083ac80 OCSP stapling: ssl_trusted_certificate directive. The directive allows to specify additional trusted Certificate Authority certificates to be used during certificate verification. In contrast to ssl_client_certificate DNs of these cerificates aren't sent to a client during handshake. Trusted certificates are loaded regardless of the fact whether client certificates verification is enabled as the same certificates will be used for OCSP stapling, during construction of an OCSP request and for verification of an OCSP response. The same applies to a CRL (which is now always loaded). 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 @@ -297,6 +297,33 @@ ngx_ssl_client_certificate(ngx_conf_t *c ngx_int_t +ngx_ssl_trusted_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, + ngx_int_t depth) +{ + SSL_CTX_set_verify_depth(ssl->ctx, depth); + + if (cert->len == 0) { + return NGX_OK; + } + + if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) { + return NGX_ERROR; + } + + if (SSL_CTX_load_verify_locations(ssl->ctx, (char *) cert->data, NULL) + == 0) + { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_load_verify_locations(\"%s\") failed", + cert->data); + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t ngx_ssl_crl(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *crl) { X509_STORE *store; 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 @@ -101,6 +101,8 @@ ngx_int_t ngx_ssl_certificate(ngx_conf_t ngx_str_t *cert, ngx_str_t *key); ngx_int_t ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert, ngx_int_t depth); +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); 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); 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 @@ -124,6 +124,13 @@ static ngx_command_t ngx_http_ssl_comma offsetof(ngx_http_ssl_srv_conf_t, client_certificate), NULL }, + { ngx_string("ssl_trusted_certificate"), + 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, trusted_certificate), + NULL }, + { ngx_string("ssl_prefer_server_ciphers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, @@ -325,6 +332,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t * sscf->dhparam = { 0, NULL }; * sscf->ecdh_curve = { 0, NULL }; * sscf->client_certificate = { 0, NULL }; + * sscf->trusted_certificate = { 0, NULL }; * sscf->crl = { 0, NULL }; * sscf->ciphers = { 0, NULL }; * sscf->shm_zone = NULL; @@ -380,6 +388,8 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t * ngx_conf_merge_str_value(conf->client_certificate, prev->client_certificate, ""); + ngx_conf_merge_str_value(conf->trusted_certificate, + prev->trusted_certificate, ""); ngx_conf_merge_str_value(conf->crl, prev->crl, ""); ngx_conf_merge_str_value(conf->ecdh_curve, prev->ecdh_curve, @@ -479,10 +489,18 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t * { return NGX_CONF_ERROR; } + } - if (ngx_ssl_crl(cf, &conf->ssl, &conf->crl) != NGX_OK) { - return NGX_CONF_ERROR; - } + if (ngx_ssl_trusted_certificate(cf, &conf->ssl, + &conf->trusted_certificate, + conf->verify_depth) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + if (ngx_ssl_crl(cf, &conf->ssl, &conf->crl) != NGX_OK) { + return NGX_CONF_ERROR; } if (conf->prefer_server_ciphers) { 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 @@ -35,6 +35,7 @@ typedef struct { ngx_str_t dhparam; ngx_str_t ecdh_curve; ngx_str_t client_certificate; + ngx_str_t trusted_certificate; ngx_str_t crl; ngx_str_t ciphers; # HG changeset patch # User Maxim Dounin # Date 1346859571 -14400 # Node ID 2098a470bb1167536254eff9e80549190bc87dbc # Parent c131e9fd3c996d8e4032fe026d3f2676390dbf8c OCSP stapling: ssl_stapling_file support. Very basic version without any OCSP responder query code, assuming valid DER-encoded OCSP response is present in a ssl_stapling_file configured. Such file might be produced with openssl like this: openssl ocsp -issuer root.crt -cert domain.crt -respout domain.staple \ -url http://ocsp.example.com diff --git a/auto/sources b/auto/sources --- a/auto/sources +++ b/auto/sources @@ -77,7 +77,8 @@ REGEX_SRCS=src/core/ngx_regex.c OPENSSL_MODULE=ngx_openssl_module OPENSSL_DEPS=src/event/ngx_event_openssl.h -OPENSSL_SRCS=src/event/ngx_event_openssl.c +OPENSSL_SRCS="src/event/ngx_event_openssl.c \ + src/event/ngx_event_openssl_stapling.c" EVENT_MODULES="ngx_events_module ngx_event_core_module" 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 @@ -17,6 +17,7 @@ #include #include #include +#include #define NGX_SSL_NAME "OpenSSL" @@ -104,6 +105,7 @@ 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); 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); diff --git a/src/event/ngx_event_openssl_stapling.c b/src/event/ngx_event_openssl_stapling.c new file mode 100644 --- /dev/null +++ b/src/event/ngx_event_openssl_stapling.c @@ -0,0 +1,121 @@ + +/* + * Copyright (C) Maxim Dounin + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +static int ngx_ssl_certificate_status_callback(ngx_ssl_conn_t *ssl_conn, + void *data); + + +ngx_int_t +ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file) +{ + BIO *bio; + int len; + u_char *p, *buf; + ngx_str_t *staple; + OCSP_RESPONSE *response; + + if (file->len == 0) { + return NGX_OK; + } + + if (ngx_conf_full_name(cf->cycle, file, 1) != NGX_OK) { + return NGX_ERROR; + } + + bio = BIO_new_file((char *) file->data, "r"); + if (bio == NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "BIO_new_file(\"%s\") failed", file->data); + return NGX_ERROR; + } + + response = d2i_OCSP_RESPONSE_bio(bio, NULL); + if (response == NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "d2i_OCSP_RESPONSE_bio(\"%s\") failed", file->data); + BIO_free(bio); + return NGX_ERROR; + } + + len = i2d_OCSP_RESPONSE(response, NULL); + if (len <= 0) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "i2d_OCSP_RESPONSE(\"%s\") failed", file->data); + goto failed; + } + + buf = ngx_pnalloc(cf->pool, len); + if (buf == NULL) { + goto failed; + } + + p = buf; + len = i2d_OCSP_RESPONSE(response, &p); + if (len <= 0) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "i2d_OCSP_RESPONSE(\"%s\") failed", file->data); + 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); + + return NGX_OK; + +failed: + + OCSP_RESPONSE_free(response); + BIO_free(bio); + + return NGX_ERROR; +} + + +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; + + c = ngx_ssl_get_connection(ssl_conn); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "SSL certificate status callback"); + + staple = data; + + /* we have to copy the staple 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; + } + + ngx_memcpy(p, staple->data, staple->len); + + SSL_set_tlsext_status_ocsp_resp(ssl_conn, p, staple->len); + + return SSL_TLSEXT_ERR_OK; +} 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 @@ -159,6 +159,20 @@ static ngx_command_t ngx_http_ssl_comma offsetof(ngx_http_ssl_srv_conf_t, crl), NULL }, + { ngx_string("ssl_stapling"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, stapling), + NULL }, + + { ngx_string("ssl_stapling_file"), + 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_file), + NULL }, + ngx_null_command }; @@ -336,6 +350,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t * sscf->crl = { 0, NULL }; * sscf->ciphers = { 0, NULL }; * sscf->shm_zone = NULL; + * sscf->stapling_file = { 0, NULL }; */ sscf->enable = NGX_CONF_UNSET; @@ -344,6 +359,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t sscf->verify_depth = NGX_CONF_UNSET_UINT; sscf->builtin_session_cache = NGX_CONF_UNSET; sscf->session_timeout = NGX_CONF_UNSET; + sscf->stapling = NGX_CONF_UNSET; return sscf; } @@ -397,6 +413,8 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t * ngx_conf_merge_str_value(conf->ciphers, prev->ciphers, NGX_DEFAULT_CIPHERS); + ngx_conf_merge_value(conf->stapling, prev->stapling, 0); + ngx_conf_merge_str_value(conf->stapling_file, prev->stapling_file, ""); conf->ssl.log = cf->log; @@ -533,6 +551,12 @@ 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; + } + return NGX_CONF_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 @@ -42,6 +42,9 @@ typedef struct { ngx_shm_zone_t *shm_zone; + ngx_flag_t stapling; + ngx_str_t stapling_file; + u_char *file; ngx_uint_t line; } ngx_http_ssl_srv_conf_t; # HG changeset patch # User Maxim Dounin # Date 1346859572 -14400 # Node ID 44e19d2f5ae453728a3fa8f43009626bb5f82eda # Parent 2098a470bb1167536254eff9e80549190bc87dbc 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 #include #include +#include #if (NGX_OPENSSL) #include #endif #include #include -#include #include #include #include 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 @@ -137,6 +139,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; } @@ -218,19 +236,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,192 @@ #include #include #include +#include +#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_issuer(ngx_conf_t *cf, ngx_ssl_t *ssl); +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 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_int_t rc; + 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; + + 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; + } + + goto done; + } + + rc = ngx_ssl_stapling_issuer(cf, ssl); + + if (rc == NGX_DECLINED) { return NGX_OK; } + if (rc != NGX_OK) { + return NGX_ERROR; + } + + rc = ngx_ssl_stapling_responder(cf, ssl, responder); + + if (rc == NGX_DECLINED) { + return NGX_OK; + } + + if (rc != NGX_OK) { + return NGX_ERROR; + } + +done: + + SSL_CTX_set_tlsext_status_cb(ssl->ctx, ngx_ssl_certificate_status_callback); + SSL_CTX_set_tlsext_status_arg(ssl->ctx, staple); + + 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 +220,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 +230,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 +251,204 @@ failed: } +static ngx_int_t +ngx_ssl_stapling_issuer(ngx_conf_t *cf, ngx_ssl_t *ssl) +{ + int i, n, rc; + X509 *cert, *issuer; + X509_STORE *store; + X509_STORE_CTX *store_ctx; + STACK_OF(X509) *chain; + ngx_ssl_stapling_t *staple; + + staple = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_stapling_index); + cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index); + +#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, ssl->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, ssl->log, 0, + "SSL get issuer: found %p in extra certs", issuer); + + staple->cert = cert; + staple->issuer = issuer; + + return NGX_OK; + } + } + + store = SSL_CTX_get_cert_store(ssl->ctx); + if (store == NULL) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->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_EMERG, ssl->log, 0, + "X509_STORE_CTX_new() failed"); + return NGX_ERROR; + } + + if (X509_STORE_CTX_init(store_ctx, store, NULL, NULL) == 0) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "X509_STORE_CTX_init() failed"); + return NGX_ERROR; + } + + rc = X509_STORE_CTX_get1_issuer(&issuer, store_ctx, cert); + + if (rc == -1) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "X509_STORE_CTX_get1_issuer() failed"); + X509_STORE_CTX_free(store_ctx); + return NGX_ERROR; + } + + if (rc == 0) { + ngx_log_error(NGX_LOG_WARN, ssl->log, 0, + "\"ssl_stapling\" ignored, issuer certificate not found"); + X509_STORE_CTX_free(store_ctx); + return NGX_DECLINED; + } + + X509_STORE_CTX_free(store_ctx); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ssl->log, 0, + "SSL get issuer: found %p in cert store", issuer); + + staple->cert = cert; + staple->issuer = issuer; + + return NGX_OK; +} + + +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_log_error(NGX_LOG_WARN, ssl->log, 0, + "\"ssl_stapling\" ignored, " + "no OCSP responder URL in the certificate"); + return NGX_DECLINED; + } + +#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_WARN, ssl->log, 0, + "\"ssl_stapling\" ignored, " + "no OCSP responder URL in the certificate"); + X509_email_free(aia); + return NGX_DECLINED; + } + + 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_WARN, ssl->log, 0, + "\"ssl_stapling\" ignored, " + "invalid URL prefix in OCSP responder \"%V\"", &u.url); + return NGX_DECLINED; + } + + if (ngx_parse_url(cf->pool, &u) != NGX_OK) { + if (u.err) { + ngx_log_error(NGX_LOG_WARN, ssl->log, 0, + "\"ssl_stapling\" ignored, " + "%s in OCSP responder \"%V\"", u.err, &u.url); + return NGX_DECLINED; + } + + 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 +456,1259 @@ 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 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_WARN, 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, + "\"ssl_stapling\" ignored, not supported"); + + 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; # HG changeset patch # User Maxim Dounin # Date 1346859574 -14400 # Node ID a3e41c8a18ef1474dd037b06050a57ef70da026c # Parent 44e19d2f5ae453728a3fa8f43009626bb5f82eda OCSP stapling: check Content-Type. This will result in better error message in case of incorrect response from OCSP responder: ... OCSP responder sent invalid "Content-Type" header: "text/plain" while requesting certificate status, responder: ... vs. ... d2i_OCSP_RESPONSE() failed (SSL: error:0D07209B:asn1 encoding routines:ASN1_get_object:too long error:0D068066:asn1 encoding routines:ASN1_CHECK_TLEN:bad object header error:0D07803A:asn1 encoding routines:ASN1_ITEM_EX_D2I:nested asn1 error) while requesting certificate status, responder: ... 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 @@ -1423,6 +1423,7 @@ done: static ngx_int_t ngx_ssl_ocsp_process_headers(ngx_ssl_ocsp_ctx_t *ctx) { + size_t len; ngx_int_t rc; ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, @@ -1440,6 +1441,33 @@ ngx_ssl_ocsp_process_headers(ngx_ssl_ocs ctx->header_end - ctx->header_start, ctx->header_start); + len = ctx->header_name_end - ctx->header_name_start; + + if (len == sizeof("Content-Type") - 1 + && ngx_strncasecmp(ctx->header_name_start, + (u_char *) "Content-Type", + sizeof("Content-Type") - 1) + == 0) + { + len = ctx->header_end - ctx->header_start; + + if (len != sizeof("application/ocsp-response") - 1 + || ngx_strncasecmp(ctx->header_start, + (u_char *) "application/ocsp-response", + sizeof("application/ocsp-response") - 1) + != 0) + { + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, + "OCSP responder sent invalid " + "\"Content-Type\" header: \"%*s\"", + ctx->header_end - ctx->header_start, + ctx->header_start); + return NGX_ERROR; + } + + continue; + } + /* TODO: honor Content-Length */ continue; # HG changeset patch # User Maxim Dounin # Date 1348247828 -14400 # Node ID c16f9000ea7099f0f0275385986b66aad2ed0e42 # Parent a3e41c8a18ef1474dd037b06050a57ef70da026c OCSP stapling: log error data in ngx_ssl_error(). It's hard to debug OCSP_basic_verify() failures without the actual error string it records in the error data field. 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 @@ -1590,10 +1590,12 @@ ngx_ssl_clear_error(ngx_log_t *log) void ngx_cdecl ngx_ssl_error(ngx_uint_t level, ngx_log_t *log, ngx_err_t err, char *fmt, ...) { - u_long n; - va_list args; - u_char *p, *last; - u_char errstr[NGX_MAX_CONF_ERRSTR]; + int flags; + u_long n; + va_list args; + u_char *p, *last; + u_char errstr[NGX_MAX_CONF_ERRSTR]; + const char *data; last = errstr + NGX_MAX_CONF_ERRSTR; @@ -1605,14 +1607,14 @@ ngx_ssl_error(ngx_uint_t level, ngx_log_ for ( ;; ) { - n = ERR_get_error(); + n = ERR_peek_error_line_data(NULL, NULL, &data, &flags); if (n == 0) { break; } if (p >= last) { - continue; + goto next; } *p++ = ' '; @@ -1622,6 +1624,15 @@ ngx_ssl_error(ngx_uint_t level, ngx_log_ while (p < last && *p) { p++; } + + if (p < last && *data && (flags & ERR_TXT_STRING)) { + *p++ = ':'; + p = ngx_cpystrn(p, (u_char *) data, last - p); + } + + next: + + (void) ERR_get_error(); } ngx_log_error(level, log, err, "%s)", errstr); # HG changeset patch # User Maxim Dounin # Date 1348247837 -14400 # Node ID 05053a9dac96127a8c9725a73ffc271efd5105fe # Parent c16f9000ea7099f0f0275385986b66aad2ed0e42 OCSP stapling: OCSP_basic_verify() OCSP_TRUSTOTHER flag now used. This is expected to simplify configuration in a common case when OCSP response is signed by a certificate already present in ssl_certificate chain. This case won't need any extra trusted certificates. 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 @@ -587,7 +587,7 @@ ngx_ssl_stapling_ocsp_handler(ngx_ssl_oc chain = staple->ssl_ctx->extra_certs; #endif - if (OCSP_basic_verify(basic, chain, store, 0) != 1) { + if (OCSP_basic_verify(basic, chain, store, OCSP_TRUSTOTHER) != 1) { ngx_ssl_error(NGX_LOG_ERR, ctx->log, 0, "OCSP_basic_verify() failed"); goto error; # HG changeset patch # User Maxim Dounin # Date 1348253249 -14400 # Node ID 797f6c9ec9f4a86476fea5c9a7eface5be991ad4 # Parent 05053a9dac96127a8c9725a73ffc271efd5105fe OCSP stapling: ssl_stapling_verify directive. This directive allows to switch off OCSP response verification, resulting in less configuration of trusted certificates needed in some cases. Note that for stapling OCSP response verification isn't something required as it will be done by a client anyway. But doing verification on a server allows to mitigate some attack vectors, most notably stop an attacker from presenting some specially crafted data to all site clients. 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 @@ -106,7 +106,7 @@ ngx_int_t ngx_ssl_trusted_certificate(ng 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 *responder, ngx_str_t *file); + 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); RSA *ngx_ssl_rsa512_key_callback(SSL *ssl, int is_export, int key_length); 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 @@ -32,7 +32,8 @@ typedef struct { time_t valid; - ngx_uint_t loading; /* unsigned:1 */ + unsigned verify:1; + unsigned loading:1; } ngx_ssl_stapling_t; @@ -113,8 +114,8 @@ static u_char *ngx_ssl_ocsp_log_error(ng ngx_int_t -ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *responder, - ngx_str_t *file) +ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file, + ngx_str_t *responder, ngx_uint_t verify) { ngx_int_t rc; ngx_pool_cleanup_t *cln; @@ -143,6 +144,7 @@ ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl staple->ssl_ctx = ssl->ctx; staple->timeout = 60000; + staple->verify = verify; if (file->len) { /* use OCSP response from the file */ @@ -587,7 +589,10 @@ ngx_ssl_stapling_ocsp_handler(ngx_ssl_oc chain = staple->ssl_ctx->extra_certs; #endif - if (OCSP_basic_verify(basic, chain, store, OCSP_TRUSTOTHER) != 1) { + if (OCSP_basic_verify(basic, chain, store, + staple->verify ? OCSP_TRUSTOTHER : OCSP_NOVERIFY) + != 1) + { ngx_ssl_error(NGX_LOG_ERR, ctx->log, 0, "OCSP_basic_verify() failed"); goto error; 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 @@ -182,6 +182,13 @@ static ngx_command_t ngx_http_ssl_comma offsetof(ngx_http_ssl_srv_conf_t, stapling_responder), NULL }, + { ngx_string("ssl_stapling_verify"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, stapling_verify), + NULL }, + ngx_null_command }; @@ -370,6 +377,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t sscf->builtin_session_cache = NGX_CONF_UNSET; sscf->session_timeout = NGX_CONF_UNSET; sscf->stapling = NGX_CONF_UNSET; + sscf->stapling_verify = NGX_CONF_UNSET; return sscf; } @@ -424,6 +432,7 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t * ngx_conf_merge_str_value(conf->ciphers, prev->ciphers, NGX_DEFAULT_CIPHERS); ngx_conf_merge_value(conf->stapling, prev->stapling, 0); + ngx_conf_merge_value(conf->stapling_verify, prev->stapling_verify, 1); ngx_conf_merge_str_value(conf->stapling_file, prev->stapling_file, ""); ngx_conf_merge_str_value(conf->stapling_responder, prev->stapling_responder, ""); @@ -565,8 +574,8 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t * if (conf->stapling) { - if (ngx_ssl_stapling(cf, &conf->ssl, &conf->stapling_responder, - &conf->stapling_file) + if (ngx_ssl_stapling(cf, &conf->ssl, &conf->stapling_file, + &conf->stapling_responder, conf->stapling_verify) != NGX_OK) { return NGX_CONF_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 @@ -43,6 +43,7 @@ typedef struct { ngx_shm_zone_t *shm_zone; ngx_flag_t stapling; + ngx_flag_t stapling_verify; ngx_str_t stapling_file; ngx_str_t stapling_responder;