# HG changeset patch # User Vladimir Homutov # Date 1482224714 -10800 # Node ID 41cb1b64561df81f3f3715171691ff704bd33732 # Parent 01adb18a5d23910c518062d7e6f30fdf7653e310 Stream: client SSL certificates verification support. New directives: "ssl_verify_client", "ssl_verify_depth", "ssl_client_certificate", "ssl_trusted_certificate", and "ssl_crl". New variables: $ssl_client_cert, $ssl_client_raw_cert, $ssl_client_s_dn, $ssl_client_i_dn, $ssl_client_serial, $ssl_client_fingerprint, $ssl_client_verify, $ssl_client_v_start, $ssl_client_v_end, and $ssl_client_v_remain. diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c --- a/src/stream/ngx_stream_ssl_module.c +++ b/src/stream/ngx_stream_ssl_module.c @@ -49,6 +49,15 @@ static ngx_conf_bitmask_t ngx_stream_ss }; +static ngx_conf_enum_t ngx_stream_ssl_verify[] = { + { ngx_string("off"), 0 }, + { ngx_string("on"), 1 }, + { ngx_string("optional"), 2 }, + { ngx_string("optional_no_ca"), 3 }, + { ngx_null_string, 0 } +}; + + static ngx_command_t ngx_stream_ssl_commands[] = { { ngx_string("ssl_handshake_timeout"), @@ -107,6 +116,34 @@ static ngx_command_t ngx_stream_ssl_com offsetof(ngx_stream_ssl_conf_t, ciphers), NULL }, + { ngx_string("ssl_verify_client"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_enum_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, verify), + &ngx_stream_ssl_verify }, + + { ngx_string("ssl_verify_depth"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, verify_depth), + NULL }, + + { ngx_string("ssl_client_certificate"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, client_certificate), + NULL }, + + { ngx_string("ssl_trusted_certificate"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, trusted_certificate), + NULL }, + { ngx_string("ssl_prefer_server_ciphers"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, @@ -142,6 +179,13 @@ static ngx_command_t ngx_stream_ssl_com offsetof(ngx_stream_ssl_conf_t, session_timeout), NULL }, + { ngx_string("ssl_crl"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_conf_t, crl), + NULL }, + ngx_null_command }; @@ -197,6 +241,37 @@ static ngx_stream_variable_t ngx_stream { ngx_string("ssl_server_name"), NULL, ngx_stream_ssl_variable, (uintptr_t) ngx_ssl_get_server_name, NGX_STREAM_VAR_CHANGEABLE, 0 }, + { ngx_string("ssl_client_cert"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_certificate, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_client_raw_cert"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_raw_certificate, + NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_client_s_dn"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_subject_dn, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_client_i_dn"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_issuer_dn, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_client_serial"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_serial_number, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_client_fingerprint"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_fingerprint, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_client_verify"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_client_verify, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_client_v_start"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_client_v_start, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_client_v_end"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_client_v_end, NGX_STREAM_VAR_CHANGEABLE, 0 }, + + { ngx_string("ssl_client_v_remain"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_client_v_remain, NGX_STREAM_VAR_CHANGEABLE, 0 }, + { ngx_null_string, NULL, NULL, 0, 0, 0 } }; @@ -207,6 +282,8 @@ static ngx_str_t ngx_stream_ssl_sess_id_ static ngx_int_t ngx_stream_ssl_handler(ngx_stream_session_t *s) { + long rc; + X509 *cert; ngx_connection_t *c; ngx_stream_ssl_conf_t *sslcf; @@ -227,6 +304,37 @@ ngx_stream_ssl_handler(ngx_stream_sessio return ngx_stream_ssl_init_connection(&sslcf->ssl, c); } + if (sslcf->verify) { + rc = SSL_get_verify_result(c->ssl->connection); + + if (rc != X509_V_OK + && (sslcf->verify != 3 || !ngx_ssl_verify_error_optional(rc))) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client SSL certificate verify error: (%l:%s)", + rc, X509_verify_cert_error_string(rc)); + + ngx_ssl_remove_cached_session(sslcf->ssl.ctx, + (SSL_get0_session(c->ssl->connection))); + return NGX_ERROR; + } + + if (sslcf->verify == 1) { + cert = SSL_get_peer_certificate(c->ssl->connection); + + if (cert == NULL) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent no required SSL certificate"); + + ngx_ssl_remove_cached_session(sslcf->ssl.ctx, + (SSL_get0_session(c->ssl->connection))); + return NGX_ERROR; + } + + X509_free(cert); + } + } + return NGX_OK; } @@ -384,6 +492,9 @@ ngx_stream_ssl_create_conf(ngx_conf_t *c * scf->protocols = 0; * scf->dhparam = { 0, NULL }; * scf->ecdh_curve = { 0, NULL }; + * scf->client_certificate = { 0, NULL }; + * scf->trusted_certificate = { 0, NULL }; + * scf->crl = { 0, NULL }; * scf->ciphers = { 0, NULL }; * scf->shm_zone = NULL; */ @@ -393,6 +504,8 @@ ngx_stream_ssl_create_conf(ngx_conf_t *c scf->certificate_keys = NGX_CONF_UNSET_PTR; scf->passwords = NGX_CONF_UNSET_PTR; scf->prefer_server_ciphers = NGX_CONF_UNSET; + scf->verify = NGX_CONF_UNSET_UINT; + scf->verify_depth = NGX_CONF_UNSET_UINT; scf->builtin_session_cache = NGX_CONF_UNSET; scf->session_timeout = NGX_CONF_UNSET; scf->session_tickets = NGX_CONF_UNSET; @@ -423,6 +536,9 @@ ngx_stream_ssl_merge_conf(ngx_conf_t *cf (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1 |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2)); + ngx_conf_merge_uint_value(conf->verify, prev->verify, 0); + ngx_conf_merge_uint_value(conf->verify_depth, prev->verify_depth, 1); + ngx_conf_merge_ptr_value(conf->certificates, prev->certificates, NULL); ngx_conf_merge_ptr_value(conf->certificate_keys, prev->certificate_keys, NULL); @@ -431,6 +547,12 @@ ngx_stream_ssl_merge_conf(ngx_conf_t *cf ngx_conf_merge_str_value(conf->dhparam, prev->dhparam, ""); + ngx_conf_merge_str_value(conf->client_certificate, prev->client_certificate, + ""); + ngx_conf_merge_str_value(conf->trusted_certificate, + prev->trusted_certificate, ""); + ngx_conf_merge_str_value(conf->crl, prev->crl, ""); + ngx_conf_merge_str_value(conf->ecdh_curve, prev->ecdh_curve, NGX_DEFAULT_ECDH_CURVE); @@ -480,6 +602,35 @@ ngx_stream_ssl_merge_conf(ngx_conf_t *cf return NGX_CONF_ERROR; } + if (conf->verify) { + + if (conf->client_certificate.len == 0 && conf->verify != 3) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no ssl_client_certificate for ssl_client_verify"); + return NGX_CONF_ERROR; + } + + if (ngx_ssl_client_certificate(cf, &conf->ssl, + &conf->client_certificate, + conf->verify_depth) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + if (ngx_ssl_trusted_certificate(cf, &conf->ssl, + &conf->trusted_certificate, + conf->verify_depth) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + if (ngx_ssl_crl(cf, &conf->ssl, &conf->crl) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + if (ngx_ssl_dhparam(cf, &conf->ssl, &conf->dhparam) != NGX_OK) { return NGX_CONF_ERROR; } diff --git a/src/stream/ngx_stream_ssl_module.h b/src/stream/ngx_stream_ssl_module.h --- a/src/stream/ngx_stream_ssl_module.h +++ b/src/stream/ngx_stream_ssl_module.h @@ -23,6 +23,9 @@ typedef struct { ngx_uint_t protocols; + ngx_uint_t verify; + ngx_uint_t verify_depth; + ssize_t builtin_session_cache; time_t session_timeout; @@ -32,6 +35,9 @@ typedef struct { ngx_str_t dhparam; ngx_str_t ecdh_curve; + ngx_str_t client_certificate; + ngx_str_t trusted_certificate; + ngx_str_t crl; ngx_str_t ciphers;