diff --git a/auto/modules b/auto/modules index 90e32d3..4fa09a1 100644 --- a/auto/modules +++ b/auto/modules @@ -1477,4 +1477,4 @@ have=T_NGX_HTTP_UPSTREAM_ID . auto/have have=T_NGX_HTTP_IMPROVED_REWRITE . auto/have have=T_NGX_SHOW_INFO . auto/have have=T_NGX_HTTP_IMAGE_FILTER . auto/have - +have=T_NGX_HAVE_DTLS . auto/have diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index c027c59..4bcdebc 100755 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -96,6 +96,25 @@ static void ngx_ssl_write_async_handler(ngx_event_t * aev); static void ngx_ssl_shutdown_async_handler(ngx_event_t *aev); #endif +#if defined(T_NGX_HAVE_DTLS) +#define NGX_DTLS_MAX_RETRANSMIT_TIME 3 + +static void ngx_dtls_retransmit(ngx_event_t *ev); +static ngx_int_t ngx_dtls_handshake(ngx_connection_t *c); + +static int ngx_dtls_client_hmac(SSL *ssl, u_char res[EVP_MAX_MD_SIZE], + unsigned int *rlen); + +static int ngx_dtls_generate_cookie_cb(SSL *ssl, unsigned char *cookie, + unsigned int *cookie_len); + +static int ngx_dtls_verify_cookie_cb(SSL *ssl, +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + const +#endif +unsigned char *cookie, unsigned int cookie_len); + +#endif static ngx_command_t ngx_openssl_commands[] = { { ngx_string("ssl_engine"), @@ -425,13 +444,65 @@ ngx_ssl_init(ngx_log_t *log) ngx_int_t ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data) { +#if defined(T_NGX_HAVE_DTLS) + if (protocols & NGX_SSL_DTLSv1 || protocols & NGX_SSL_DTLSv1_2) { + + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + + if (protocols & NGX_SSL_DTLSv1_2) { + + /* DTLS 1.2 is only supported since 1.0.2 */ + + /* DTLSv1_x_method() functions are deprecated in 1.1.0 */ + +#if OPENSSL_VERSION_NUMBER < 0x10002000L + + /* ancient ... 1.0.2 */ + ngx_log_error(NGX_LOG_EMERG, ssl->log, 0, + "DTLSv1.2 is not supported by " + "the used version of OpenSSL"); + return NGX_ERROR; + +#else + /* 1.0.2 ... 1.1 */ + ssl->ctx = SSL_CTX_new(DTLSv1_2_method()); +#endif + } + + /* note: either 1.2 or 1.1 methods may be initialized, not both, + * preferred is 1.2 if both specified in ssl_protocols + */ + + if (protocols & NGX_SSL_DTLSv1 && ssl->ctx == NULL) { + ssl->ctx = SSL_CTX_new(DTLSv1_method()); + } +#else + ssl->ctx = SSL_CTX_new(DTLS_method()); +#endif + + } else { + ssl->ctx = SSL_CTX_new(SSLv23_method()); + } +#else + ssl->ctx = SSL_CTX_new(SSLv23_method()); +#endif if (ssl->ctx == NULL) { ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "SSL_CTX_new() failed"); return NGX_ERROR; } +#if defined(T_NGX_HAVE_DTLS) + if (protocols & NGX_SSL_DTLSv1 + || protocols & NGX_SSL_DTLSv1_2) { + + SSL_CTX_set_cookie_generate_cb(ssl->ctx, ngx_dtls_generate_cookie_cb); + SSL_CTX_set_cookie_verify_cb(ssl->ctx, ngx_dtls_verify_cookie_cb); + } +#endif + if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_server_conf_index, data) == 0) { ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "SSL_CTX_set_ex_data() failed"); @@ -1785,6 +1856,21 @@ ngx_ssl_handshake(ngx_connection_t *c) } #endif +#if (T_NGX_HAVE_DTLS) + ngx_int_t rc; + struct timeval timeout; + + if (c->type == SOCK_DGRAM + && !c->ssl->client + && !c->ssl->bio_changed) + { + rc = ngx_dtls_handshake(c); + if (rc != NGX_OK) { + return rc; + } + } +#endif + ngx_ssl_clear_error(c->log); n = SSL_do_handshake(c->ssl->connection); @@ -1807,6 +1893,13 @@ ngx_ssl_handshake(ngx_connection_t *c) return NGX_ERROR; } +#if (T_NGX_HAVE_DTLS) + if (c->ssl->retrans && + c->ssl->retrans->timer_set) { + ngx_del_timer(c->ssl->retrans); + } +#endif + #if (NGX_DEBUG) ngx_ssl_handshake_log(c); #endif @@ -1864,6 +1957,34 @@ ngx_ssl_handshake(ngx_connection_t *c) return NGX_ERROR; } +#if (T_NGX_HAVE_DTLS) + if (c->type == SOCK_DGRAM) { + + /*At least on packet should be sent(SH or HR)*/ + if (!c->ssl->dtls_send) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "unexcepted message of dtls session"); + return NGX_ERROR; + } + + if (DTLSv1_get_timeout(c->ssl->connection, &timeout)) { + ngx_msec_t timer = timeout.tv_sec * 1000 + timeout.tv_usec/1000; + + if (timer) { + c->ssl->retrans->handler = ngx_dtls_retransmit; + ngx_add_timer(c->ssl->retrans, timer); + } + + } else { + /*No need to retransmit, delete timer*/ + if (c->ssl->retrans && + c->ssl->retrans->timer_set) { + ngx_del_timer(c->ssl->retrans); + } + } + } +#endif + return NGX_AGAIN; } @@ -2990,6 +3111,12 @@ ngx_ssl_shutdown(ngx_connection_t *c) int n, sslerr, mode; ngx_err_t err; +#if (T_NGX_HAVE_DTLS) + if (c->ssl->retrans && c->ssl->retrans->timer_set) { + ngx_del_timer(c->ssl->retrans); + } +#endif + if (SSL_in_init(c->ssl->connection)) { /* * OpenSSL 1.0.2f complains if SSL_shutdown() is called during @@ -5588,3 +5715,345 @@ ngx_openssl_exit(ngx_cycle_t *cycle) #endif } + +#if defined(T_NGX_HAVE_DTLS) +void ngx_dtls_retransmit(ngx_event_t *ev) +{ + ngx_connection_t *c = ev->data; + + ngx_ssl_error(NGX_LOG_DEBUG, c->log, 0, + "ngx_dtls_retransmit %i", (ngx_int_t)c->ssl->retrans_times); + + if (c->ssl->retrans_times > NGX_DTLS_MAX_RETRANSMIT_TIME) { + c->ssl->handler(c); + return; + } + + c->ssl->retrans_times++; + if (ngx_ssl_handshake(c) == NGX_AGAIN) { + return; + } + + c->ssl->handler(c); +} + +/* + * RFC 6347, 4.2.1: + * + * When responding to a HelloVerifyRequest, the client MUST use the same + * parameter values (version, random, session_id, cipher_suites, + * compression_method) as it did in the original ClientHello. The + * server SHOULD use those values to generate its cookie and verify that + * they are correct upon cookie receipt. + */ + +static int +ngx_dtls_client_hmac(SSL *ssl, u_char res[EVP_MAX_MD_SIZE], unsigned int *rlen) +{ + u_char *p; + size_t len; + ngx_connection_t *c; + + u_char buffer[64]; + + c = ngx_ssl_get_connection(ssl); + + p = buffer; + + p = ngx_cpymem(p, c->addr_text.data, c->addr_text.len); + p = ngx_sprintf(p, "%d", ngx_inet_get_port(c->sockaddr)); + + len = p - buffer; + + HMAC(EVP_sha1(), (const void*) c->ssl->dtls_cookie_secret, + sizeof(c->ssl->dtls_cookie_secret), (const u_char*) buffer, len, res, rlen); + + return NGX_OK; +} + + +static int +ngx_dtls_generate_cookie_cb(SSL *ssl, unsigned char *cookie, + unsigned int *cookie_len) +{ + unsigned int rlen; + u_char res[EVP_MAX_MD_SIZE]; + + if (ngx_dtls_client_hmac(ssl, res, &rlen) != NGX_OK) { + return 0; + } + + ngx_memcpy(cookie, res, rlen); + *cookie_len = rlen; + + return 1; +} + + +static int +ngx_dtls_verify_cookie_cb(SSL *ssl, +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + const +#endif + unsigned char *cookie, unsigned int cookie_len) +{ + unsigned int rlen; + u_char res[EVP_MAX_MD_SIZE]; + + if (ngx_dtls_client_hmac(ssl, res, &rlen) != NGX_OK) { + return 0; + } + + if (cookie_len == rlen && ngx_memcmp(res, cookie, rlen) == 0) { + return 1; + } + + return 0; +} + +static int +ngx_dtls_new(BIO *bi) +{ + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + BIO_set_init(bi, 0); + BIO_set_data(bi, NULL); + BIO_clear_flags(bi, ~0); +#else + bi->init = 0; + bi->num = 0; + bi->ptr = NULL; + bi->flags = 0; +#endif + return (1); +} + +static int +ngx_dtls_free(BIO *a) +{ + int shutdown, init, num; + + if (a == NULL) + return (0); + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + ngx_connection_t *c; + shutdown = BIO_get_shutdown(a); + init = BIO_get_init(a); + c = BIO_get_data(a); + num = c->fd; +#else + shutdown = a->shutdown; + init = a->init; + num = a->num; +#endif + // + if (shutdown) { + //BIO_get_init + if (init) { + ngx_close_socket(num); + } +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + BIO_set_data(a, NULL); + BIO_set_init(a, 0); +#else + a->init = 0; + a->flags = 0; +#endif + } + + return (1); +} + +static int +ngx_dtls_read(BIO *b, char *out, int outl) +{ + ssize_t ret = 0, n = 0; + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + ngx_connection_t *c = BIO_get_data(b); +#else + ngx_connection_t *c = (ngx_connection_t *)b->ptr; +#endif + + if (out != NULL) { + + if (c->buffer && c->buffer->pos < c->buffer->last) { + n = c->buffer->last - c->buffer->pos; + + if (n > (ssize_t)outl) { + n = outl; + } + ngx_memcpy(out, c->buffer->pos, n); + c->buffer->pos += n; + } + + /*ngx_udp_shared_recv*/ + ret = c->recv(c, (u_char*)out, (size_t)outl); + + BIO_clear_retry_flags(b); + + if (ret == NGX_AGAIN) { + BIO_set_retry_read(b); + + } else { + n += ret; + } + + if (n == 0) { + n = -1; + } + } + + return n; +} + +static int +ngx_dtls_write(BIO *b, const char *in, int inl) +{ + ssize_t ret; + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + ngx_connection_t *c = BIO_get_data(b); +#else + ngx_connection_t *c = (ngx_connection_t *)b->ptr; +#endif + + c->ssl->dtls_send = 1; + ret = ngx_udp_send(c, (u_char *)in, (size_t)inl); + + BIO_clear_retry_flags(b); + + if (ret <= 0) { + if (ret == NGX_AGAIN) { + BIO_set_retry_write(b); + } + } + + return (ret); +} + +static int +ngx_dtls_puts(BIO *bp, const char *str) +{ + return ngx_dtls_write(bp, str, strlen(str)); +} + +static long +ngx_dtls_ctrl(BIO *b, int cmd, long num, void *ptr) +{ + long ret = 1; + ngx_connection_t *c; + + /*BIO_get_data*/ + + switch (cmd) { + case BIO_C_SET_CONNECT: +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + BIO_set_data(b, ptr); + BIO_set_init(b, 1); + BIO_set_shutdown(b, num); + (void)c; +#else + b->ptr = ptr; + c = (ngx_connection_t *)ptr; + b->num = c->fd; + b->init = 1; + b->shutdown = (int)num; +#endif + break; + case BIO_CTRL_GET_CLOSE: +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + ret = BIO_get_shutdown(b); +#else + ret = b->shutdown; +#endif + break; + case BIO_CTRL_SET_CLOSE: +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + BIO_set_shutdown(b, num); +#else + b->shutdown = (int)num; +#endif + break; + case BIO_CTRL_DUP: + case BIO_CTRL_FLUSH: + ret = 1; + break; + default: + ret = 0; + break; + } + return (ret); +} + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L +static BIO_METHOD *methods_ngx_dtls = NULL; +#else +static BIO_METHOD methods_ngx_dtls = { + BIO_TYPE_SOCKET, + "ngx dtls", + ngx_dtls_write, + ngx_dtls_read, + ngx_dtls_puts, + NULL, + ngx_dtls_ctrl, + ngx_dtls_new, + ngx_dtls_free, + NULL, +}; + +#endif +static ngx_int_t +ngx_dtls_handshake(ngx_connection_t *c) +{ + BIO *rbio; + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + if (methods_ngx_dtls == NULL) { + methods_ngx_dtls = BIO_meth_new(BIO_TYPE_SOCKET, "ngx dtls"); + if (!methods_ngx_dtls + || !BIO_meth_set_create(methods_ngx_dtls, ngx_dtls_new) + || !BIO_meth_set_destroy(methods_ngx_dtls, ngx_dtls_free) + || !BIO_meth_set_write(methods_ngx_dtls, ngx_dtls_write) + || !BIO_meth_set_read(methods_ngx_dtls, ngx_dtls_read) + || !BIO_meth_set_puts(methods_ngx_dtls, ngx_dtls_puts) + || !BIO_meth_set_ctrl(methods_ngx_dtls, ngx_dtls_ctrl)) { + + return NGX_ERROR; + } + } + rbio = BIO_new(methods_ngx_dtls); +#else + /*Need to replace rbio*/ + rbio = BIO_new(&methods_ngx_dtls); +#endif + + if (rbio == NULL) { + return NGX_ERROR; + } + + BIO_ctrl(rbio, BIO_C_SET_CONNECT, BIO_NOCLOSE, (char *)c); + + SSL_set_bio(c->ssl->connection, rbio, rbio); + /*TODO: directive*/ + SSL_set_options(c->ssl->connection, SSL_OP_COOKIE_EXCHANGE); + + c->ssl->retrans = ngx_pcalloc(c->pool, sizeof(ngx_event_t)); + if (!c->ssl->retrans) { + return NGX_ERROR; + } + + c->ssl->retrans->log = c->log; + c->ssl->retrans->data = c; + + c->ssl->bio_changed = 1; + + if (!RAND_bytes(c->ssl->dtls_cookie_secret, sizeof(c->ssl->dtls_cookie_secret))) { + return NGX_ERROR; + } + return NGX_OK; +} + +#endif + diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index 06507ab..8a9501e 100755 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -107,6 +107,17 @@ struct ngx_ssl_connection_s { unsigned in_early:1; unsigned early_preread:1; unsigned write_blocked:1; + + + +#if (T_NGX_HAVE_DTLS) + unsigned bio_changed:1; + unsigned dtls_send:1; + unsigned client:1; + unsigned retrans_times; + ngx_event_t *retrans; + u_char dtls_cookie_secret[32]; +#endif }; @@ -160,6 +171,10 @@ typedef struct { #define NGX_SSL_TLSv1_2 0x0020 #define NGX_SSL_TLSv1_3 0x0040 +#if (T_NGX_HAVE_DTLS) +#define NGX_SSL_DTLSv1 0x0080 +#define NGX_SSL_DTLSv1_2 0x0200 +#endif #define NGX_SSL_BUFFER 1 #define NGX_SSL_CLIENT 2 diff --git a/src/stream/ngx_stream_core_module.c b/src/stream/ngx_stream_core_module.c index 04ff818..892b160 100755 --- a/src/stream/ngx_stream_core_module.c +++ b/src/stream/ngx_stream_core_module.c @@ -931,10 +931,12 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } #if (NGX_STREAM_SSL) +#if !(T_NGX_HAVE_DTLS) if (ls->ssl) { return "\"ssl\" parameter is incompatible with \"udp\""; } #endif +#endif if (ls->so_keepalive) { return "\"so_keepalive\" parameter is incompatible with \"udp\""; diff --git a/src/stream/ngx_stream_proxy_module.c b/src/stream/ngx_stream_proxy_module.c index 7484a72..2a1f132 100644 --- a/src/stream/ngx_stream_proxy_module.c +++ b/src/stream/ngx_stream_proxy_module.c @@ -109,6 +109,11 @@ static ngx_conf_bitmask_t ngx_stream_proxy_ssl_protocols[] = { { ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 }, { ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 }, { ngx_string("TLSv1.3"), NGX_SSL_TLSv1_3 }, + +#if (T_NGX_HAVE_DTLS) + { ngx_string("DTLSv1"), NGX_SSL_DTLSv1 }, + { ngx_string("DTLSv1.2"), NGX_SSL_DTLSv1_2 }, +#endif { ngx_null_string, 0 } }; diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c index 312fb56..28c6f26 100755 --- a/src/stream/ngx_stream_ssl_module.c +++ b/src/stream/ngx_stream_ssl_module.c @@ -52,6 +52,10 @@ int ngx_stream_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg); #endif +#if (T_NGX_HAVE_DTLS) +static char *ngx_stream_set_ssl_protocols(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +#endif static ngx_conf_bitmask_t ngx_stream_ssl_protocols[] = { { ngx_string("SSLv2"), NGX_SSL_SSLv2 }, @@ -60,6 +64,11 @@ static ngx_conf_bitmask_t ngx_stream_ssl_protocols[] = { { ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 }, { ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 }, { ngx_string("TLSv1.3"), NGX_SSL_TLSv1_3 }, + +#if (T_NGX_HAVE_DTLS) + { ngx_string("DTLSv1"), NGX_SSL_DTLSv1 }, + { ngx_string("DTLSv1.2"), NGX_SSL_DTLSv1_2 }, +#endif { ngx_null_string, 0 } }; @@ -119,7 +128,11 @@ static ngx_command_t ngx_stream_ssl_commands[] = { { ngx_string("ssl_protocols"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE, +#if (T_NGX_HAVE_DTLS) + ngx_stream_set_ssl_protocols, +#else ngx_conf_set_bitmask_slot, +#endif NGX_STREAM_SRV_CONF_OFFSET, offsetof(ngx_stream_ssl_conf_t, protocols), &ngx_stream_ssl_protocols }, @@ -393,7 +406,11 @@ ngx_stream_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t *c) cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module); - if (cscf->tcp_nodelay && ngx_tcp_nodelay(c) != NGX_OK) { + if ( +#if (T_NGX_HAVE_DTLS) + c->type == SOCK_STREAM && +#endif + cscf->tcp_nodelay && ngx_tcp_nodelay(c) != NGX_OK) { return NGX_ERROR; } @@ -1084,6 +1101,33 @@ ngx_stream_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_OK; } +#if (T_NGX_HAVE_DTLS) +static char * +ngx_stream_set_ssl_protocols(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_ssl_conf_t *scf = conf; + + char *rv; + + rv = ngx_conf_set_bitmask_slot(cf, cmd, conf); + + if (rv != NGX_CONF_OK) { + return rv; + } + + /* DTLS protocol requires corresponding TLS version to be set */ + + if (scf->protocols & NGX_SSL_DTLSv1) { + scf->protocols |= NGX_SSL_TLSv1; + } + + if (scf->protocols & NGX_SSL_DTLSv1_2) { + scf->protocols |= NGX_SSL_TLSv1_2; + } + + return NGX_CONF_OK; +} +#endif static char * ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) @@ -1202,6 +1246,13 @@ ngx_stream_ssl_init(ngx_conf_t *cf) { ngx_stream_handler_pt *h; ngx_stream_core_main_conf_t *cmcf; +#if (T_NGX_HAVE_DTLS) + ngx_uint_t i; + ngx_stream_listen_t *ls; + ngx_stream_conf_ctx_t *sctx; + ngx_stream_ssl_conf_t **sscfp, *sscf; + ngx_stream_core_srv_conf_t **cscfp, *cscf; +#endif cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); @@ -1212,5 +1263,54 @@ ngx_stream_ssl_init(ngx_conf_t *cf) *h = ngx_stream_ssl_handler; +#if (T_NGX_HAVE_DTLS) + cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); + + ls = cmcf->listen.elts; + + for (i = 0; i < cmcf->listen.nelts; i++) { + if (ls[i].ssl) { + sctx = ls[i].ctx; + + sscfp = (ngx_stream_ssl_conf_t **)sctx->srv_conf; + cscfp = (ngx_stream_core_srv_conf_t **)sctx->srv_conf; + + sscf = sscfp[ngx_stream_ssl_module.ctx_index]; + cscf = cscfp[ngx_stream_core_module.ctx_index]; + + if (sscf->certificates == NULL) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no \"ssl_certificate\" is defined " + "in server listening on SSL port at %s:%ui", + cscf->file_name, cscf->line); + return NGX_ERROR; + } + + if (ls[i].type == SOCK_DGRAM) { + if (!(sscf->protocols & NGX_SSL_DTLSv1 + || sscf->protocols & NGX_SSL_DTLSv1_2)) + { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "\"ssl_protocols\" does not enable DTLS in a " + "server listening on UDP SSL port at %s:%ui", + cscf->file_name, cscf->line); + return NGX_ERROR; + } + + } else { + if (sscf->protocols & NGX_SSL_DTLSv1 + || sscf->protocols & NGX_SSL_DTLSv1_2 ) + { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "\"ssl_protocols\" includes DTLS in a server " + "listening on SSL port at %s:%ui", + cscf->file_name, cscf->line); + return NGX_ERROR; + } + } + } + } +#endif + return NGX_OK; }