2779 lines
66 KiB
C
2779 lines
66 KiB
C
|
|
/*
|
|
* Copyright (C) Maxim Dounin
|
|
* Copyright (C) Nginx, Inc.
|
|
*/
|
|
|
|
|
|
#include <ngx_config.h>
|
|
#include <ngx_core.h>
|
|
#include <ngx_event.h>
|
|
#include <ngx_event_connect.h>
|
|
|
|
|
|
#if (!defined OPENSSL_NO_OCSP && defined 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_uint_t naddrs;
|
|
ngx_str_t host;
|
|
ngx_str_t uri;
|
|
in_port_t port;
|
|
|
|
SSL_CTX *ssl_ctx;
|
|
|
|
X509 *cert;
|
|
X509 *issuer;
|
|
STACK_OF(X509) *chain;
|
|
|
|
u_char *name;
|
|
|
|
time_t valid;
|
|
time_t refresh;
|
|
|
|
unsigned verify:1;
|
|
unsigned loading:1;
|
|
} ngx_ssl_stapling_t;
|
|
|
|
|
|
typedef struct {
|
|
ngx_addr_t *addrs;
|
|
ngx_uint_t naddrs;
|
|
|
|
ngx_str_t host;
|
|
ngx_str_t uri;
|
|
in_port_t port;
|
|
ngx_uint_t depth;
|
|
|
|
ngx_shm_zone_t *shm_zone;
|
|
|
|
ngx_resolver_t *resolver;
|
|
ngx_msec_t resolver_timeout;
|
|
} ngx_ssl_ocsp_conf_t;
|
|
|
|
|
|
typedef struct {
|
|
ngx_rbtree_t rbtree;
|
|
ngx_rbtree_node_t sentinel;
|
|
ngx_queue_t expire_queue;
|
|
} ngx_ssl_ocsp_cache_t;
|
|
|
|
|
|
typedef struct {
|
|
ngx_str_node_t node;
|
|
ngx_queue_t queue;
|
|
int status;
|
|
time_t valid;
|
|
} ngx_ssl_ocsp_cache_node_t;
|
|
|
|
|
|
typedef struct ngx_ssl_ocsp_ctx_s ngx_ssl_ocsp_ctx_t;
|
|
|
|
|
|
struct ngx_ssl_ocsp_s {
|
|
STACK_OF(X509) *certs;
|
|
ngx_uint_t ncert;
|
|
|
|
int cert_status;
|
|
ngx_int_t status;
|
|
|
|
ngx_ssl_ocsp_conf_t *conf;
|
|
ngx_ssl_ocsp_ctx_t *ctx;
|
|
};
|
|
|
|
|
|
struct ngx_ssl_ocsp_ctx_s {
|
|
SSL_CTX *ssl_ctx;
|
|
|
|
X509 *cert;
|
|
X509 *issuer;
|
|
STACK_OF(X509) *chain;
|
|
|
|
int status;
|
|
time_t valid;
|
|
|
|
u_char *name;
|
|
|
|
ngx_uint_t naddrs;
|
|
ngx_uint_t naddr;
|
|
|
|
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 *ctx);
|
|
void *data;
|
|
|
|
ngx_str_t key;
|
|
ngx_buf_t *request;
|
|
ngx_buf_t *response;
|
|
ngx_peer_connection_t peer;
|
|
|
|
ngx_shm_zone_t *shm_zone;
|
|
|
|
ngx_int_t (*process)(ngx_ssl_ocsp_ctx_t *ctx);
|
|
|
|
ngx_uint_t state;
|
|
|
|
ngx_uint_t code;
|
|
ngx_uint_t count;
|
|
ngx_uint_t flags;
|
|
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_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
|
X509 *cert, ngx_str_t *file, ngx_str_t *responder, ngx_uint_t verify);
|
|
static ngx_int_t ngx_ssl_stapling_file(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
|
ngx_ssl_stapling_t *staple, ngx_str_t *file);
|
|
static ngx_int_t ngx_ssl_stapling_issuer(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
|
ngx_ssl_stapling_t *staple);
|
|
static ngx_int_t ngx_ssl_stapling_responder(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
|
ngx_ssl_stapling_t *staple, 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 time_t ngx_ssl_stapling_time(ASN1_GENERALIZEDTIME *asn1time);
|
|
|
|
static void ngx_ssl_stapling_cleanup(void *data);
|
|
|
|
static void ngx_ssl_ocsp_validate_next(ngx_connection_t *c);
|
|
static void ngx_ssl_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx);
|
|
static ngx_int_t ngx_ssl_ocsp_responder(ngx_connection_t *c,
|
|
ngx_ssl_ocsp_ctx_t *ctx);
|
|
|
|
static ngx_ssl_ocsp_ctx_t *ngx_ssl_ocsp_start(ngx_log_t *log);
|
|
static void ngx_ssl_ocsp_done(ngx_ssl_ocsp_ctx_t *ctx);
|
|
static void ngx_ssl_ocsp_next(ngx_ssl_ocsp_ctx_t *ctx);
|
|
static void ngx_ssl_ocsp_request(ngx_ssl_ocsp_ctx_t *ctx);
|
|
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 ngx_int_t ngx_ssl_ocsp_verify(ngx_ssl_ocsp_ctx_t *ctx);
|
|
|
|
static ngx_int_t ngx_ssl_ocsp_cache_lookup(ngx_ssl_ocsp_ctx_t *ctx);
|
|
static ngx_int_t ngx_ssl_ocsp_cache_store(ngx_ssl_ocsp_ctx_t *ctx);
|
|
static ngx_int_t ngx_ssl_ocsp_create_key(ngx_ssl_ocsp_ctx_t *ctx);
|
|
|
|
static u_char *ngx_ssl_ocsp_log_error(ngx_log_t *log, u_char *buf, size_t len);
|
|
|
|
|
|
ngx_int_t
|
|
ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file,
|
|
ngx_str_t *responder, ngx_uint_t verify)
|
|
{
|
|
X509 *cert;
|
|
|
|
for (cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index);
|
|
cert;
|
|
cert = X509_get_ex_data(cert, ngx_ssl_next_certificate_index))
|
|
{
|
|
if (ngx_ssl_stapling_certificate(cf, ssl, cert, file, responder, verify)
|
|
!= NGX_OK)
|
|
{
|
|
return NGX_ERROR;
|
|
}
|
|
}
|
|
|
|
SSL_CTX_set_tlsext_status_cb(ssl->ctx, ngx_ssl_certificate_status_callback);
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_ssl_stapling_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, X509 *cert,
|
|
ngx_str_t *file, ngx_str_t *responder, ngx_uint_t verify)
|
|
{
|
|
ngx_int_t rc;
|
|
ngx_pool_cleanup_t *cln;
|
|
ngx_ssl_stapling_t *staple;
|
|
|
|
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 (X509_set_ex_data(cert, ngx_ssl_stapling_index, staple) == 0) {
|
|
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "X509_set_ex_data() failed");
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
#ifdef SSL_CTRL_SELECT_CURRENT_CERT
|
|
/* OpenSSL 1.0.2+ */
|
|
SSL_CTX_select_current_cert(ssl->ctx, cert);
|
|
#endif
|
|
|
|
#ifdef SSL_CTRL_GET_EXTRA_CHAIN_CERTS
|
|
/* OpenSSL 1.0.1+ */
|
|
SSL_CTX_get_extra_chain_certs(ssl->ctx, &staple->chain);
|
|
#else
|
|
staple->chain = ssl->ctx->extra_certs;
|
|
#endif
|
|
|
|
staple->ssl_ctx = ssl->ctx;
|
|
staple->timeout = 60000;
|
|
staple->verify = verify;
|
|
staple->cert = cert;
|
|
staple->name = X509_get_ex_data(staple->cert,
|
|
ngx_ssl_certificate_name_index);
|
|
|
|
if (file->len) {
|
|
/* use OCSP response from the file */
|
|
|
|
if (ngx_ssl_stapling_file(cf, ssl, staple, file) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
rc = ngx_ssl_stapling_issuer(cf, ssl, staple);
|
|
|
|
if (rc == NGX_DECLINED) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
if (rc != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
rc = ngx_ssl_stapling_responder(cf, ssl, staple, responder);
|
|
|
|
if (rc == NGX_DECLINED) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
if (rc != 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_ssl_stapling_t *staple, ngx_str_t *file)
|
|
{
|
|
BIO *bio;
|
|
int len;
|
|
u_char *p, *buf;
|
|
OCSP_RESPONSE *response;
|
|
|
|
if (ngx_conf_full_name(cf->cycle, file, 1) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
bio = BIO_new_file((char *) file->data, "rb");
|
|
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_alloc(len, ssl->log);
|
|
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);
|
|
ngx_free(buf);
|
|
goto failed;
|
|
}
|
|
|
|
OCSP_RESPONSE_free(response);
|
|
BIO_free(bio);
|
|
|
|
staple->staple.data = buf;
|
|
staple->staple.len = len;
|
|
staple->valid = NGX_MAX_TIME_T_VALUE;
|
|
|
|
return NGX_OK;
|
|
|
|
failed:
|
|
|
|
OCSP_RESPONSE_free(response);
|
|
BIO_free(bio);
|
|
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_ssl_stapling_issuer(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
|
ngx_ssl_stapling_t *staple)
|
|
{
|
|
int i, n, rc;
|
|
X509 *cert, *issuer;
|
|
X509_STORE *store;
|
|
X509_STORE_CTX *store_ctx;
|
|
|
|
cert = staple->cert;
|
|
|
|
n = sk_X509_num(staple->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(staple->chain, i);
|
|
if (X509_check_issued(issuer, cert) == X509_V_OK) {
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10100001L
|
|
X509_up_ref(issuer);
|
|
#else
|
|
CRYPTO_add(&issuer->references, 1, CRYPTO_LOCK_X509);
|
|
#endif
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ssl->log, 0,
|
|
"SSL get issuer: found %p in extra certs", issuer);
|
|
|
|
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");
|
|
X509_STORE_CTX_free(store_ctx);
|
|
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 for certificate \"%s\"",
|
|
staple->name);
|
|
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->issuer = issuer;
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_ssl_stapling_responder(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
|
ngx_ssl_stapling_t *staple, ngx_str_t *responder)
|
|
{
|
|
char *s;
|
|
ngx_str_t rsp;
|
|
ngx_url_t u;
|
|
STACK_OF(OPENSSL_STRING) *aia;
|
|
|
|
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 \"%s\"",
|
|
staple->name);
|
|
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 \"%s\"",
|
|
staple->name);
|
|
X509_email_free(aia);
|
|
return NGX_DECLINED;
|
|
}
|
|
|
|
responder = &rsp;
|
|
|
|
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\" "
|
|
"in the certificate \"%s\"",
|
|
&u.url, staple->name);
|
|
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\" "
|
|
"in the certificate \"%s\"",
|
|
u.err, &u.url, staple->name);
|
|
return NGX_DECLINED;
|
|
}
|
|
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
staple->addrs = u.addrs;
|
|
staple->naddrs = u.naddrs;
|
|
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)
|
|
{
|
|
X509 *cert;
|
|
ngx_ssl_stapling_t *staple;
|
|
|
|
for (cert = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_certificate_index);
|
|
cert;
|
|
cert = X509_get_ex_data(cert, ngx_ssl_next_certificate_index))
|
|
{
|
|
staple = X509_get_ex_data(cert, 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)
|
|
{
|
|
int rc;
|
|
X509 *cert;
|
|
u_char *p;
|
|
ngx_connection_t *c;
|
|
ngx_ssl_stapling_t *staple;
|
|
|
|
c = ngx_ssl_get_connection(ssl_conn);
|
|
|
|
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
|
"SSL certificate status callback");
|
|
|
|
rc = SSL_TLSEXT_ERR_NOACK;
|
|
|
|
cert = SSL_get_certificate(ssl_conn);
|
|
|
|
if (cert == NULL) {
|
|
return rc;
|
|
}
|
|
|
|
staple = X509_get_ex_data(cert, ngx_ssl_stapling_index);
|
|
|
|
if (staple == NULL) {
|
|
return rc;
|
|
}
|
|
|
|
if (staple->staple.len
|
|
&& staple->valid >= ngx_time())
|
|
{
|
|
/* we have to copy ocsp response as OpenSSL will free it by itself */
|
|
|
|
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_ssl_stapling_update(staple);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
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->refresh >= ngx_time())
|
|
{
|
|
return;
|
|
}
|
|
|
|
staple->loading = 1;
|
|
|
|
ctx = ngx_ssl_ocsp_start(ngx_cycle->log);
|
|
if (ctx == NULL) {
|
|
return;
|
|
}
|
|
|
|
ctx->ssl_ctx = staple->ssl_ctx;
|
|
ctx->cert = staple->cert;
|
|
ctx->issuer = staple->issuer;
|
|
ctx->chain = staple->chain;
|
|
ctx->name = staple->name;
|
|
ctx->flags = (staple->verify ? OCSP_TRUSTOTHER : OCSP_NOVERIFY);
|
|
|
|
ctx->addrs = staple->addrs;
|
|
ctx->naddrs = staple->naddrs;
|
|
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)
|
|
{
|
|
time_t now;
|
|
ngx_str_t response;
|
|
ngx_ssl_stapling_t *staple;
|
|
|
|
staple = ctx->data;
|
|
now = ngx_time();
|
|
|
|
if (ngx_ssl_ocsp_verify(ctx) != NGX_OK) {
|
|
goto error;
|
|
}
|
|
|
|
if (ctx->status != V_OCSP_CERTSTATUS_GOOD) {
|
|
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
|
|
"certificate status \"%s\" in the OCSP response",
|
|
OCSP_cert_status_str(ctx->status));
|
|
goto error;
|
|
}
|
|
|
|
/* copy the response to memory not in ctx->pool */
|
|
|
|
response.len = ctx->response->last - ctx->response->pos;
|
|
response.data = ngx_alloc(response.len, ctx->log);
|
|
|
|
if (response.data == NULL) {
|
|
goto error;
|
|
}
|
|
|
|
ngx_memcpy(response.data, ctx->response->pos, response.len);
|
|
|
|
if (staple->staple.data) {
|
|
ngx_free(staple->staple.data);
|
|
}
|
|
|
|
staple->staple = response;
|
|
staple->valid = ctx->valid;
|
|
|
|
/*
|
|
* refresh before the response expires,
|
|
* but not earlier than in 5 minutes, and at least in an hour
|
|
*/
|
|
|
|
staple->loading = 0;
|
|
staple->refresh = ngx_max(ngx_min(ctx->valid - 300, now + 3600), now + 300);
|
|
|
|
ngx_ssl_ocsp_done(ctx);
|
|
return;
|
|
|
|
error:
|
|
|
|
staple->loading = 0;
|
|
staple->refresh = now + 300;
|
|
|
|
ngx_ssl_ocsp_done(ctx);
|
|
}
|
|
|
|
|
|
static time_t
|
|
ngx_ssl_stapling_time(ASN1_GENERALIZEDTIME *asn1time)
|
|
{
|
|
BIO *bio;
|
|
char *value;
|
|
size_t len;
|
|
time_t time;
|
|
|
|
/*
|
|
* OpenSSL doesn't provide a way to convert ASN1_GENERALIZEDTIME
|
|
* into time_t. To do this, we use ASN1_GENERALIZEDTIME_print(),
|
|
* which uses the "MMM DD HH:MM:SS YYYY [GMT]" format (e.g.,
|
|
* "Feb 3 00:55:52 2015 GMT"), and parse the result.
|
|
*/
|
|
|
|
bio = BIO_new(BIO_s_mem());
|
|
if (bio == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
/* fake weekday prepended to match C asctime() format */
|
|
|
|
BIO_write(bio, "Tue ", sizeof("Tue ") - 1);
|
|
ASN1_GENERALIZEDTIME_print(bio, asn1time);
|
|
len = BIO_get_mem_data(bio, &value);
|
|
|
|
time = ngx_parse_http_time((u_char *) value, len);
|
|
|
|
BIO_free(bio);
|
|
|
|
return time;
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
ngx_int_t
|
|
ngx_ssl_ocsp(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *responder,
|
|
ngx_uint_t depth, ngx_shm_zone_t *shm_zone)
|
|
{
|
|
ngx_url_t u;
|
|
ngx_ssl_ocsp_conf_t *ocf;
|
|
|
|
ocf = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_ocsp_conf_t));
|
|
if (ocf == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ocf->depth = depth;
|
|
ocf->shm_zone = shm_zone;
|
|
|
|
if (responder->len) {
|
|
ngx_memzero(&u, sizeof(ngx_url_t));
|
|
|
|
u.url = *responder;
|
|
u.default_port = 80;
|
|
u.uri_part = 1;
|
|
|
|
if (u.url.len > 7
|
|
&& ngx_strncasecmp(u.url.data, (u_char *) "http://", 7) == 0)
|
|
{
|
|
u.url.len -= 7;
|
|
u.url.data += 7;
|
|
|
|
} else {
|
|
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
|
|
"invalid URL prefix in OCSP responder \"%V\" "
|
|
"in \"ssl_ocsp_responder\"", &u.url);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
|
|
if (u.err) {
|
|
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
|
|
"%s in OCSP responder \"%V\" "
|
|
"in \"ssl_ocsp_responder\"", u.err, &u.url);
|
|
}
|
|
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ocf->addrs = u.addrs;
|
|
ocf->naddrs = u.naddrs;
|
|
ocf->host = u.host;
|
|
ocf->uri = u.uri;
|
|
ocf->port = u.port;
|
|
}
|
|
|
|
if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_ocsp_index, ocf) == 0) {
|
|
ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
|
|
"SSL_CTX_set_ex_data() failed");
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
ngx_int_t
|
|
ngx_ssl_ocsp_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
|
ngx_resolver_t *resolver, ngx_msec_t resolver_timeout)
|
|
{
|
|
ngx_ssl_ocsp_conf_t *ocf;
|
|
|
|
ocf = SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_ocsp_index);
|
|
ocf->resolver = resolver;
|
|
ocf->resolver_timeout = resolver_timeout;
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
ngx_int_t
|
|
ngx_ssl_ocsp_validate(ngx_connection_t *c)
|
|
{
|
|
X509 *cert;
|
|
SSL_CTX *ssl_ctx;
|
|
ngx_int_t rc;
|
|
X509_STORE *store;
|
|
X509_STORE_CTX *store_ctx;
|
|
STACK_OF(X509) *chain;
|
|
ngx_ssl_ocsp_t *ocsp;
|
|
ngx_ssl_ocsp_conf_t *ocf;
|
|
|
|
if (c->ssl->in_ocsp) {
|
|
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
return NGX_AGAIN;
|
|
}
|
|
|
|
ssl_ctx = SSL_get_SSL_CTX(c->ssl->connection);
|
|
|
|
ocf = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_ocsp_index);
|
|
if (ocf == NULL) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
if (SSL_get_verify_result(c->ssl->connection) != X509_V_OK) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
cert = SSL_get_peer_certificate(c->ssl->connection);
|
|
if (cert == NULL) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
ocsp = ngx_pcalloc(c->pool, sizeof(ngx_ssl_ocsp_t));
|
|
if (ocsp == NULL) {
|
|
X509_free(cert);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
c->ssl->ocsp = ocsp;
|
|
|
|
ocsp->status = NGX_AGAIN;
|
|
ocsp->cert_status = V_OCSP_CERTSTATUS_GOOD;
|
|
ocsp->conf = ocf;
|
|
|
|
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER)
|
|
|
|
ocsp->certs = SSL_get0_verified_chain(c->ssl->connection);
|
|
|
|
if (ocsp->certs) {
|
|
ocsp->certs = X509_chain_up_ref(ocsp->certs);
|
|
if (ocsp->certs == NULL) {
|
|
X509_free(cert);
|
|
return NGX_ERROR;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
if (ocsp->certs == NULL) {
|
|
store = SSL_CTX_get_cert_store(ssl_ctx);
|
|
if (store == NULL) {
|
|
ngx_ssl_error(NGX_LOG_ERR, c->log, 0,
|
|
"SSL_CTX_get_cert_store() failed");
|
|
X509_free(cert);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
store_ctx = X509_STORE_CTX_new();
|
|
if (store_ctx == NULL) {
|
|
ngx_ssl_error(NGX_LOG_ERR, c->log, 0,
|
|
"X509_STORE_CTX_new() failed");
|
|
X509_free(cert);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
chain = SSL_get_peer_cert_chain(c->ssl->connection);
|
|
|
|
if (X509_STORE_CTX_init(store_ctx, store, cert, chain) == 0) {
|
|
ngx_ssl_error(NGX_LOG_ERR, c->log, 0,
|
|
"X509_STORE_CTX_init() failed");
|
|
X509_STORE_CTX_free(store_ctx);
|
|
X509_free(cert);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
rc = X509_verify_cert(store_ctx);
|
|
if (rc <= 0) {
|
|
ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "X509_verify_cert() failed");
|
|
X509_STORE_CTX_free(store_ctx);
|
|
X509_free(cert);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ocsp->certs = X509_STORE_CTX_get1_chain(store_ctx);
|
|
if (ocsp->certs == NULL) {
|
|
ngx_ssl_error(NGX_LOG_ERR, c->log, 0,
|
|
"X509_STORE_CTX_get1_chain() failed");
|
|
X509_STORE_CTX_free(store_ctx);
|
|
X509_free(cert);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
X509_STORE_CTX_free(store_ctx);
|
|
}
|
|
|
|
X509_free(cert);
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
|
"ssl ocsp validate, certs:%d", sk_X509_num(ocsp->certs));
|
|
|
|
ngx_ssl_ocsp_validate_next(c);
|
|
|
|
if (ocsp->status == NGX_AGAIN) {
|
|
c->ssl->in_ocsp = 1;
|
|
return NGX_AGAIN;
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static void
|
|
ngx_ssl_ocsp_validate_next(ngx_connection_t *c)
|
|
{
|
|
ngx_int_t rc;
|
|
ngx_uint_t n;
|
|
ngx_ssl_ocsp_t *ocsp;
|
|
ngx_ssl_ocsp_ctx_t *ctx;
|
|
ngx_ssl_ocsp_conf_t *ocf;
|
|
|
|
ocsp = c->ssl->ocsp;
|
|
ocf = ocsp->conf;
|
|
|
|
n = sk_X509_num(ocsp->certs);
|
|
|
|
for ( ;; ) {
|
|
|
|
if (ocsp->ncert == n - 1 || (ocf->depth == 2 && ocsp->ncert == 1)) {
|
|
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
|
"ssl ocsp validated, certs:%ui", ocsp->ncert);
|
|
rc = NGX_OK;
|
|
goto done;
|
|
}
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
|
|
"ssl ocsp validate cert:%ui", ocsp->ncert);
|
|
|
|
ctx = ngx_ssl_ocsp_start(c->log);
|
|
if (ctx == NULL) {
|
|
rc = NGX_ERROR;
|
|
goto done;
|
|
}
|
|
|
|
ocsp->ctx = ctx;
|
|
|
|
ctx->ssl_ctx = SSL_get_SSL_CTX(c->ssl->connection);
|
|
ctx->cert = sk_X509_value(ocsp->certs, ocsp->ncert);
|
|
ctx->issuer = sk_X509_value(ocsp->certs, ocsp->ncert + 1);
|
|
ctx->chain = ocsp->certs;
|
|
|
|
ctx->resolver = ocf->resolver;
|
|
ctx->resolver_timeout = ocf->resolver_timeout;
|
|
|
|
ctx->handler = ngx_ssl_ocsp_handler;
|
|
ctx->data = c;
|
|
|
|
ctx->shm_zone = ocf->shm_zone;
|
|
|
|
ctx->addrs = ocf->addrs;
|
|
ctx->naddrs = ocf->naddrs;
|
|
ctx->host = ocf->host;
|
|
ctx->uri = ocf->uri;
|
|
ctx->port = ocf->port;
|
|
|
|
rc = ngx_ssl_ocsp_responder(c, ctx);
|
|
if (rc != NGX_OK) {
|
|
goto done;
|
|
}
|
|
|
|
if (ctx->uri.len == 0) {
|
|
ngx_str_set(&ctx->uri, "/");
|
|
}
|
|
|
|
ocsp->ncert++;
|
|
|
|
rc = ngx_ssl_ocsp_cache_lookup(ctx);
|
|
|
|
if (rc == NGX_ERROR) {
|
|
goto done;
|
|
}
|
|
|
|
if (rc == NGX_DECLINED) {
|
|
break;
|
|
}
|
|
|
|
/* rc == NGX_OK */
|
|
|
|
if (ctx->status != V_OCSP_CERTSTATUS_GOOD) {
|
|
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
|
"ssl ocsp cached status \"%s\"",
|
|
OCSP_cert_status_str(ctx->status));
|
|
ocsp->cert_status = ctx->status;
|
|
goto done;
|
|
}
|
|
|
|
ocsp->ctx = NULL;
|
|
ngx_ssl_ocsp_done(ctx);
|
|
}
|
|
|
|
ngx_ssl_ocsp_request(ctx);
|
|
return;
|
|
|
|
done:
|
|
|
|
ocsp->status = rc;
|
|
|
|
if (c->ssl->in_ocsp) {
|
|
c->ssl->handshaked = 1;
|
|
c->ssl->handler(c);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
ngx_ssl_ocsp_handler(ngx_ssl_ocsp_ctx_t *ctx)
|
|
{
|
|
ngx_int_t rc;
|
|
ngx_ssl_ocsp_t *ocsp;
|
|
ngx_connection_t *c;
|
|
|
|
c = ctx->data;
|
|
ocsp = c->ssl->ocsp;
|
|
ocsp->ctx = NULL;
|
|
|
|
rc = ngx_ssl_ocsp_verify(ctx);
|
|
if (rc != NGX_OK) {
|
|
goto done;
|
|
}
|
|
|
|
rc = ngx_ssl_ocsp_cache_store(ctx);
|
|
if (rc != NGX_OK) {
|
|
goto done;
|
|
}
|
|
|
|
if (ctx->status != V_OCSP_CERTSTATUS_GOOD) {
|
|
ocsp->cert_status = ctx->status;
|
|
goto done;
|
|
}
|
|
|
|
ngx_ssl_ocsp_done(ctx);
|
|
|
|
ngx_ssl_ocsp_validate_next(c);
|
|
|
|
return;
|
|
|
|
done:
|
|
|
|
ocsp->status = rc;
|
|
ngx_ssl_ocsp_done(ctx);
|
|
|
|
if (c->ssl->in_ocsp) {
|
|
c->ssl->handshaked = 1;
|
|
c->ssl->handler(c);
|
|
}
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_ssl_ocsp_responder(ngx_connection_t *c, ngx_ssl_ocsp_ctx_t *ctx)
|
|
{
|
|
char *s;
|
|
ngx_str_t responder;
|
|
ngx_url_t u;
|
|
STACK_OF(OPENSSL_STRING) *aia;
|
|
|
|
if (ctx->host.len) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
/* extract OCSP responder URL from certificate */
|
|
|
|
aia = X509_get1_ocsp(ctx->cert);
|
|
if (aia == NULL) {
|
|
ngx_log_error(NGX_LOG_ERR, c->log, 0,
|
|
"no OCSP responder URL in certificate");
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
|
|
s = sk_OPENSSL_STRING_value(aia, 0);
|
|
#else
|
|
s = sk_value(aia, 0);
|
|
#endif
|
|
if (s == NULL) {
|
|
ngx_log_error(NGX_LOG_ERR, c->log, 0,
|
|
"no OCSP responder URL in certificate");
|
|
X509_email_free(aia);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
responder.len = ngx_strlen(s);
|
|
responder.data = ngx_palloc(ctx->pool, responder.len);
|
|
if (responder.data == NULL) {
|
|
X509_email_free(aia);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ngx_memcpy(responder.data, s, responder.len);
|
|
X509_email_free(aia);
|
|
|
|
ngx_memzero(&u, sizeof(ngx_url_t));
|
|
|
|
u.url = responder;
|
|
u.default_port = 80;
|
|
u.uri_part = 1;
|
|
u.no_resolve = 1;
|
|
|
|
if (u.url.len > 7
|
|
&& ngx_strncasecmp(u.url.data, (u_char *) "http://", 7) == 0)
|
|
{
|
|
u.url.len -= 7;
|
|
u.url.data += 7;
|
|
|
|
} else {
|
|
ngx_log_error(NGX_LOG_ERR, c->log, 0,
|
|
"invalid URL prefix in OCSP responder \"%V\" "
|
|
"in certificate", &u.url);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (ngx_parse_url(ctx->pool, &u) != NGX_OK) {
|
|
if (u.err) {
|
|
ngx_log_error(NGX_LOG_ERR, c->log, 0,
|
|
"%s in OCSP responder \"%V\" in certificate",
|
|
u.err, &u.url);
|
|
}
|
|
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (u.host.len == 0) {
|
|
ngx_log_error(NGX_LOG_ERR, c->log, 0,
|
|
"empty host in OCSP responder in certificate");
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ctx->addrs = u.addrs;
|
|
ctx->naddrs = u.naddrs;
|
|
ctx->host = u.host;
|
|
ctx->uri = u.uri;
|
|
ctx->port = u.port;
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
ngx_int_t
|
|
ngx_ssl_ocsp_get_status(ngx_connection_t *c, const char **s)
|
|
{
|
|
ngx_ssl_ocsp_t *ocsp;
|
|
|
|
ocsp = c->ssl->ocsp;
|
|
if (ocsp == NULL) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
if (ocsp->status == NGX_ERROR) {
|
|
*s = "certificate status request failed";
|
|
return NGX_DECLINED;
|
|
}
|
|
|
|
switch (ocsp->cert_status) {
|
|
|
|
case V_OCSP_CERTSTATUS_GOOD:
|
|
return NGX_OK;
|
|
|
|
case V_OCSP_CERTSTATUS_REVOKED:
|
|
*s = "certificate revoked";
|
|
break;
|
|
|
|
default: /* V_OCSP_CERTSTATUS_UNKNOWN */
|
|
*s = "certificate status unknown";
|
|
}
|
|
|
|
return NGX_DECLINED;
|
|
}
|
|
|
|
|
|
void
|
|
ngx_ssl_ocsp_cleanup(ngx_connection_t *c)
|
|
{
|
|
ngx_ssl_ocsp_t *ocsp;
|
|
|
|
ocsp = c->ssl->ocsp;
|
|
if (ocsp == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (ocsp->ctx) {
|
|
ngx_ssl_ocsp_done(ocsp->ctx);
|
|
ocsp->ctx = NULL;
|
|
}
|
|
|
|
if (ocsp->certs) {
|
|
sk_X509_pop_free(ocsp->certs, X509_free);
|
|
ocsp->certs = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
static ngx_ssl_ocsp_ctx_t *
|
|
ngx_ssl_ocsp_start(ngx_log_t *log)
|
|
{
|
|
ngx_pool_t *pool;
|
|
ngx_ssl_ocsp_ctx_t *ctx;
|
|
|
|
pool = ngx_create_pool(2048, 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_next(ngx_ssl_ocsp_ctx_t *ctx)
|
|
{
|
|
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
|
"ssl ocsp next");
|
|
|
|
if (++ctx->naddr >= ctx->naddrs) {
|
|
ngx_ssl_ocsp_error(ctx);
|
|
return;
|
|
}
|
|
|
|
ctx->request->pos = ctx->request->start;
|
|
|
|
if (ctx->response) {
|
|
ctx->response->last = ctx->response->pos;
|
|
}
|
|
|
|
if (ctx->peer.connection) {
|
|
ngx_close_connection(ctx->peer.connection);
|
|
ctx->peer.connection = NULL;
|
|
}
|
|
|
|
ctx->state = 0;
|
|
ctx->count = 0;
|
|
ctx->done = 0;
|
|
|
|
ngx_ssl_ocsp_connect(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) {
|
|
if (ctx->naddrs == 0) {
|
|
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
|
|
"no resolver defined to resolve %V", &ctx->host);
|
|
|
|
ngx_ssl_ocsp_error(ctx);
|
|
return;
|
|
}
|
|
|
|
ngx_log_error(NGX_LOG_WARN, ctx->log, 0,
|
|
"no resolver defined to resolve %V", &ctx->host);
|
|
goto connect;
|
|
}
|
|
|
|
resolve->name = ctx->host;
|
|
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;
|
|
}
|
|
|
|
connect:
|
|
|
|
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;
|
|
socklen_t socklen;
|
|
ngx_uint_t i;
|
|
struct sockaddr *sockaddr;
|
|
|
|
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, 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)
|
|
{
|
|
u_char text[NGX_SOCKADDR_STRLEN];
|
|
ngx_str_t addr;
|
|
|
|
addr.data = text;
|
|
|
|
for (i = 0; i < resolve->naddrs; i++) {
|
|
addr.len = ngx_sock_ntop(resolve->addrs[i].sockaddr,
|
|
resolve->addrs[i].socklen,
|
|
text, NGX_SOCKADDR_STRLEN, 0);
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
|
"name was resolved to %V", &addr);
|
|
|
|
}
|
|
}
|
|
#endif
|
|
|
|
ctx->naddrs = resolve->naddrs;
|
|
ctx->addrs = ngx_pcalloc(ctx->pool, ctx->naddrs * sizeof(ngx_addr_t));
|
|
|
|
if (ctx->addrs == NULL) {
|
|
goto failed;
|
|
}
|
|
|
|
for (i = 0; i < resolve->naddrs; i++) {
|
|
|
|
socklen = resolve->addrs[i].socklen;
|
|
|
|
sockaddr = ngx_palloc(ctx->pool, socklen);
|
|
if (sockaddr == NULL) {
|
|
goto failed;
|
|
}
|
|
|
|
ngx_memcpy(sockaddr, resolve->addrs[i].sockaddr, socklen);
|
|
ngx_inet_set_port(sockaddr, ctx->port);
|
|
|
|
ctx->addrs[i].sockaddr = sockaddr;
|
|
ctx->addrs[i].socklen = socklen;
|
|
|
|
p = ngx_pnalloc(ctx->pool, NGX_SOCKADDR_STRLEN);
|
|
if (p == NULL) {
|
|
goto failed;
|
|
}
|
|
|
|
len = ngx_sock_ntop(sockaddr, socklen, p, NGX_SOCKADDR_STRLEN, 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_addr_t *addr;
|
|
|
|
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
|
"ssl ocsp connect %ui/%ui", ctx->naddr, ctx->naddrs);
|
|
|
|
addr = &ctx->addrs[ctx->naddr];
|
|
|
|
ctx->peer.sockaddr = addr->sockaddr;
|
|
ctx->peer.socklen = addr->socklen;
|
|
ctx->peer.name = &addr->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) {
|
|
ngx_ssl_ocsp_error(ctx);
|
|
return;
|
|
}
|
|
|
|
if (rc == NGX_BUSY || rc == NGX_DECLINED) {
|
|
ngx_ssl_ocsp_next(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;
|
|
|
|
if (ctx->timeout) {
|
|
ngx_add_timer(ctx->peer.connection->read, ctx->timeout);
|
|
ngx_add_timer(ctx->peer.connection->write, ctx->timeout);
|
|
}
|
|
|
|
if (rc == NGX_OK) {
|
|
ngx_ssl_ocsp_write_handler(ctx->peer.connection->write);
|
|
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_next(ctx);
|
|
return;
|
|
}
|
|
|
|
size = ctx->request->last - ctx->request->pos;
|
|
|
|
n = ngx_send(c, ctx->request->pos, size);
|
|
|
|
if (n == NGX_ERROR) {
|
|
ngx_ssl_ocsp_next(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 && ctx->timeout) {
|
|
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_connection_t *c;
|
|
ngx_ssl_ocsp_ctx_t *ctx;
|
|
|
|
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_next(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_next(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_next(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");
|
|
OCSP_CERTID_free(id);
|
|
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_debug2(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
|
"ssl ocsp request length %z, escape %d",
|
|
base64.len, (int) 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;
|
|
|
|
OCSP_REQUEST_free(ocsp);
|
|
|
|
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) {
|
|
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
|
"ssl ocsp status %ui \"%*s\"",
|
|
ctx->code,
|
|
ctx->header_end - ctx->header_start,
|
|
ctx->header_start);
|
|
|
|
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;
|
|
ctx->header_start = p - 2;
|
|
}
|
|
|
|
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:
|
|
ctx->header_end = p;
|
|
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:
|
|
ctx->header_end = p;
|
|
goto done;
|
|
}
|
|
break;
|
|
|
|
/* end of status line */
|
|
case sw_almost_done:
|
|
switch (ch) {
|
|
case LF:
|
|
ctx->header_end = p - 1;
|
|
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)
|
|
{
|
|
size_t len;
|
|
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);
|
|
|
|
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;
|
|
}
|
|
|
|
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 ngx_int_t
|
|
ngx_ssl_ocsp_verify(ngx_ssl_ocsp_ctx_t *ctx)
|
|
{
|
|
int n;
|
|
size_t len;
|
|
X509_STORE *store;
|
|
const u_char *p;
|
|
OCSP_CERTID *id;
|
|
OCSP_RESPONSE *ocsp;
|
|
OCSP_BASICRESP *basic;
|
|
ASN1_GENERALIZEDTIME *thisupdate, *nextupdate;
|
|
|
|
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 successful (%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(ctx->ssl_ctx);
|
|
if (store == NULL) {
|
|
ngx_ssl_error(NGX_LOG_CRIT, ctx->log, 0,
|
|
"SSL_CTX_get_cert_store() failed");
|
|
goto error;
|
|
}
|
|
|
|
if (OCSP_basic_verify(basic, ctx->chain, store, ctx->flags) != 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, &ctx->status, NULL, NULL,
|
|
&thisupdate, &nextupdate)
|
|
!= 1)
|
|
{
|
|
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
|
|
"certificate status not found in the OCSP response");
|
|
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;
|
|
}
|
|
|
|
if (nextupdate) {
|
|
ctx->valid = ngx_ssl_stapling_time(nextupdate);
|
|
if (ctx->valid == (time_t) NGX_ERROR) {
|
|
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
|
|
"invalid nextUpdate time in certificate status");
|
|
goto error;
|
|
}
|
|
|
|
} else {
|
|
ctx->valid = NGX_MAX_TIME_T_VALUE;
|
|
}
|
|
|
|
OCSP_CERTID_free(id);
|
|
OCSP_BASICRESP_free(basic);
|
|
OCSP_RESPONSE_free(ocsp);
|
|
|
|
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
|
"ssl ocsp response, %s, %uz",
|
|
OCSP_cert_status_str(ctx->status), len);
|
|
|
|
return NGX_OK;
|
|
|
|
error:
|
|
|
|
if (id) {
|
|
OCSP_CERTID_free(id);
|
|
}
|
|
|
|
if (basic) {
|
|
OCSP_BASICRESP_free(basic);
|
|
}
|
|
|
|
if (ocsp) {
|
|
OCSP_RESPONSE_free(ocsp);
|
|
}
|
|
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
|
|
ngx_int_t
|
|
ngx_ssl_ocsp_cache_init(ngx_shm_zone_t *shm_zone, void *data)
|
|
{
|
|
size_t len;
|
|
ngx_slab_pool_t *shpool;
|
|
ngx_ssl_ocsp_cache_t *cache;
|
|
|
|
if (data) {
|
|
shm_zone->data = data;
|
|
return NGX_OK;
|
|
}
|
|
|
|
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
|
|
|
|
if (shm_zone->shm.exists) {
|
|
shm_zone->data = shpool->data;
|
|
return NGX_OK;
|
|
}
|
|
|
|
cache = ngx_slab_alloc(shpool, sizeof(ngx_ssl_ocsp_cache_t));
|
|
if (cache == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
shpool->data = cache;
|
|
shm_zone->data = cache;
|
|
|
|
ngx_rbtree_init(&cache->rbtree, &cache->sentinel,
|
|
ngx_str_rbtree_insert_value);
|
|
|
|
ngx_queue_init(&cache->expire_queue);
|
|
|
|
len = sizeof(" in OCSP cache \"\"") + shm_zone->shm.name.len;
|
|
|
|
shpool->log_ctx = ngx_slab_alloc(shpool, len);
|
|
if (shpool->log_ctx == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ngx_sprintf(shpool->log_ctx, " in OCSP cache \"%V\"%Z",
|
|
&shm_zone->shm.name);
|
|
|
|
shpool->log_nomem = 0;
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_ssl_ocsp_cache_lookup(ngx_ssl_ocsp_ctx_t *ctx)
|
|
{
|
|
uint32_t hash;
|
|
ngx_shm_zone_t *shm_zone;
|
|
ngx_slab_pool_t *shpool;
|
|
ngx_ssl_ocsp_cache_t *cache;
|
|
ngx_ssl_ocsp_cache_node_t *node;
|
|
|
|
shm_zone = ctx->shm_zone;
|
|
|
|
if (shm_zone == NULL) {
|
|
return NGX_DECLINED;
|
|
}
|
|
|
|
if (ngx_ssl_ocsp_create_key(ctx) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, "ssl ocsp cache lookup");
|
|
|
|
cache = shm_zone->data;
|
|
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
|
|
hash = ngx_hash_key(ctx->key.data, ctx->key.len);
|
|
|
|
ngx_shmtx_lock(&shpool->mutex);
|
|
|
|
node = (ngx_ssl_ocsp_cache_node_t *)
|
|
ngx_str_rbtree_lookup(&cache->rbtree, &ctx->key, hash);
|
|
|
|
if (node) {
|
|
if (node->valid > ngx_time()) {
|
|
ctx->status = node->status;
|
|
ngx_shmtx_unlock(&shpool->mutex);
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
|
"ssl ocsp cache hit, %s",
|
|
OCSP_cert_status_str(ctx->status));
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
ngx_queue_remove(&node->queue);
|
|
ngx_rbtree_delete(&cache->rbtree, &node->node.node);
|
|
ngx_slab_free_locked(shpool, node);
|
|
|
|
ngx_shmtx_unlock(&shpool->mutex);
|
|
|
|
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
|
"ssl ocsp cache expired");
|
|
|
|
return NGX_DECLINED;
|
|
}
|
|
|
|
ngx_shmtx_unlock(&shpool->mutex);
|
|
|
|
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ctx->log, 0, "ssl ocsp cache miss");
|
|
|
|
return NGX_DECLINED;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_ssl_ocsp_cache_store(ngx_ssl_ocsp_ctx_t *ctx)
|
|
{
|
|
time_t now, valid;
|
|
uint32_t hash;
|
|
ngx_queue_t *q;
|
|
ngx_shm_zone_t *shm_zone;
|
|
ngx_slab_pool_t *shpool;
|
|
ngx_ssl_ocsp_cache_t *cache;
|
|
ngx_ssl_ocsp_cache_node_t *node;
|
|
|
|
shm_zone = ctx->shm_zone;
|
|
|
|
if (shm_zone == NULL) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
valid = ctx->valid;
|
|
|
|
now = ngx_time();
|
|
|
|
if (valid < now) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
if (valid == NGX_MAX_TIME_T_VALUE) {
|
|
valid = now + 3600;
|
|
}
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
|
"ssl ocsp cache store, valid:%T", valid - now);
|
|
|
|
cache = shm_zone->data;
|
|
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
|
|
hash = ngx_hash_key(ctx->key.data, ctx->key.len);
|
|
|
|
ngx_shmtx_lock(&shpool->mutex);
|
|
|
|
node = ngx_slab_calloc_locked(shpool,
|
|
sizeof(ngx_ssl_ocsp_cache_node_t) + ctx->key.len);
|
|
if (node == NULL) {
|
|
|
|
if (!ngx_queue_empty(&cache->expire_queue)) {
|
|
q = ngx_queue_last(&cache->expire_queue);
|
|
node = ngx_queue_data(q, ngx_ssl_ocsp_cache_node_t, queue);
|
|
|
|
ngx_rbtree_delete(&cache->rbtree, &node->node.node);
|
|
ngx_queue_remove(q);
|
|
ngx_slab_free_locked(shpool, node);
|
|
|
|
node = ngx_slab_alloc_locked(shpool,
|
|
sizeof(ngx_ssl_ocsp_cache_node_t) + ctx->key.len);
|
|
}
|
|
|
|
if (node == NULL) {
|
|
ngx_shmtx_unlock(&shpool->mutex);
|
|
ngx_log_error(NGX_LOG_ALERT, ctx->log, 0,
|
|
"could not allocate new entry%s", shpool->log_ctx);
|
|
return NGX_ERROR;
|
|
}
|
|
}
|
|
|
|
node->node.str.len = ctx->key.len;
|
|
node->node.str.data = (u_char *) node + sizeof(ngx_ssl_ocsp_cache_node_t);
|
|
ngx_memcpy(node->node.str.data, ctx->key.data, ctx->key.len);
|
|
node->node.node.key = hash;
|
|
node->status = ctx->status;
|
|
node->valid = valid;
|
|
|
|
ngx_rbtree_insert(&cache->rbtree, &node->node.node);
|
|
ngx_queue_insert_head(&cache->expire_queue, &node->queue);
|
|
|
|
ngx_shmtx_unlock(&shpool->mutex);
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_ssl_ocsp_create_key(ngx_ssl_ocsp_ctx_t *ctx)
|
|
{
|
|
u_char *p;
|
|
X509_NAME *name;
|
|
ASN1_INTEGER *serial;
|
|
|
|
p = ngx_pnalloc(ctx->pool, 60);
|
|
if (p == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ctx->key.data = p;
|
|
ctx->key.len = 60;
|
|
|
|
name = X509_get_subject_name(ctx->issuer);
|
|
if (X509_NAME_digest(name, EVP_sha1(), p, NULL) == 0) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
p += 20;
|
|
|
|
if (X509_pubkey_digest(ctx->issuer, EVP_sha1(), p, NULL) == 0) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
p += 20;
|
|
|
|
serial = X509_get_serialNumber(ctx->cert);
|
|
if (serial->length > 20) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
p = ngx_cpymem(p, serial->data, serial->length);
|
|
ngx_memzero(p, 20 - serial->length);
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, ctx->log, 0,
|
|
"ssl ocsp key %xV", &ctx->key);
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
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;
|
|
buf = p;
|
|
}
|
|
|
|
ctx = log->data;
|
|
|
|
if (ctx) {
|
|
p = ngx_snprintf(buf, len, ", responder: %V", &ctx->host);
|
|
len -= p - buf;
|
|
buf = p;
|
|
}
|
|
|
|
if (ctx && ctx->peer.name) {
|
|
p = ngx_snprintf(buf, len, ", peer: %V", ctx->peer.name);
|
|
len -= p - buf;
|
|
buf = p;
|
|
}
|
|
|
|
if (ctx && ctx->name) {
|
|
p = ngx_snprintf(buf, len, ", certificate: \"%s\"", ctx->name);
|
|
len -= p - buf;
|
|
buf = p;
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
#else
|
|
|
|
|
|
ngx_int_t
|
|
ngx_ssl_stapling(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file,
|
|
ngx_str_t *responder, ngx_uint_t verify)
|
|
{
|
|
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;
|
|
}
|
|
|
|
|
|
ngx_int_t
|
|
ngx_ssl_ocsp(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *responder,
|
|
ngx_uint_t depth, ngx_shm_zone_t *shm_zone)
|
|
{
|
|
ngx_log_error(NGX_LOG_EMERG, ssl->log, 0,
|
|
"\"ssl_ocsp\" is not supported on this platform");
|
|
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
|
|
ngx_int_t
|
|
ngx_ssl_ocsp_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl,
|
|
ngx_resolver_t *resolver, ngx_msec_t resolver_timeout)
|
|
{
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
ngx_int_t
|
|
ngx_ssl_ocsp_validate(ngx_connection_t *c)
|
|
{
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
ngx_int_t
|
|
ngx_ssl_ocsp_get_status(ngx_connection_t *c, const char **s)
|
|
{
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
void
|
|
ngx_ssl_ocsp_cleanup(ngx_connection_t *c)
|
|
{
|
|
}
|
|
|
|
|
|
ngx_int_t
|
|
ngx_ssl_ocsp_cache_init(ngx_shm_zone_t *shm_zone, void *data)
|
|
{
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
#endif
|