mirror of
https://github.com/nginx/nginx.git
synced 2024-12-16 06:09:00 +08:00
573810ce36
Broken by 6893a1007a7c (1.9.2) during introduction of strict OCSP response validity checks. As stapling file is expected to be returned unconditionally, fix is to set its validity to the maximum supported time. Reported by Faidon Liambotis.
1822 lines
43 KiB
C
1822 lines
43 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_str_t host;
|
|
ngx_str_t uri;
|
|
in_port_t port;
|
|
|
|
SSL_CTX *ssl_ctx;
|
|
|
|
X509 *cert;
|
|
X509 *issuer;
|
|
|
|
time_t valid;
|
|
time_t refresh;
|
|
|
|
unsigned verify:1;
|
|
unsigned loading: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 time_t ngx_ssl_stapling_time(ASN1_GENERALIZEDTIME *asn1time);
|
|
|
|
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_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 (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;
|
|
staple->verify = verify;
|
|
|
|
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;
|
|
}
|
|
|
|
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_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)
|
|
{
|
|
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");
|
|
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");
|
|
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)
|
|
{
|
|
int rc;
|
|
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");
|
|
|
|
staple = data;
|
|
rc = SSL_TLSEXT_ERR_NOACK;
|
|
|
|
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();
|
|
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;
|
|
time_t now, valid;
|
|
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;
|
|
now = ngx_time();
|
|
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(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,
|
|
staple->verify ? OCSP_TRUSTOTHER : OCSP_NOVERIFY)
|
|
!= 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");
|
|
goto error;
|
|
}
|
|
|
|
if (n != V_OCSP_CERTSTATUS_GOOD) {
|
|
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
|
|
"certificate status \"%s\" in the OCSP response",
|
|
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;
|
|
}
|
|
|
|
valid = ngx_ssl_stapling_time(nextupdate);
|
|
if (valid == (time_t) NGX_ERROR) {
|
|
ngx_log_error(NGX_LOG_ERR, ctx->log, 0,
|
|
"invalid nextUpdate time in certificate status");
|
|
goto error;
|
|
}
|
|
|
|
OCSP_CERTID_free(id);
|
|
OCSP_BASICRESP_free(basic);
|
|
OCSP_RESPONSE_free(ocsp);
|
|
|
|
id = NULL;
|
|
basic = NULL;
|
|
ocsp = NULL;
|
|
|
|
/* 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 error;
|
|
}
|
|
|
|
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;
|
|
staple->valid = 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(valid - 300, now + 3600), now + 300);
|
|
|
|
ngx_ssl_ocsp_done(ctx);
|
|
return;
|
|
|
|
error:
|
|
|
|
staple->loading = 0;
|
|
staple->refresh = now + 300;
|
|
|
|
if (id) {
|
|
OCSP_CERTID_free(id);
|
|
}
|
|
|
|
if (basic) {
|
|
OCSP_BASICRESP_free(basic);
|
|
}
|
|
|
|
if (ocsp) {
|
|
OCSP_RESPONSE_free(ocsp);
|
|
}
|
|
|
|
ngx_ssl_ocsp_done(ctx);
|
|
}
|
|
|
|
|
|
static time_t
|
|
ngx_ssl_stapling_time(ASN1_GENERALIZEDTIME *asn1time)
|
|
{
|
|
u_char *value;
|
|
size_t len;
|
|
time_t time;
|
|
BIO *bio;
|
|
|
|
/*
|
|
* 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(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);
|
|
}
|
|
}
|
|
|
|
|
|
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);
|
|
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;
|
|
in_port_t port;
|
|
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;
|
|
}
|
|
|
|
port = htons(ctx->port);
|
|
|
|
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);
|
|
|
|
switch (sockaddr->sa_family) {
|
|
#if (NGX_HAVE_INET6)
|
|
case AF_INET6:
|
|
((struct sockaddr_in6 *) sockaddr)->sin6_port = port;
|
|
break;
|
|
#endif
|
|
default: /* AF_INET */
|
|
((struct sockaddr_in *) sockaddr)->sin_port = 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_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");
|
|
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, 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) {
|
|
#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)
|
|
{
|
|
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 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 *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;
|
|
}
|
|
|
|
|
|
#endif
|