Hello! On Wed, Aug 23, 2017 at 09:22:43PM -0500, Nate Karstens wrote:
> # HG changeset patch > # User Nate Karstens <[email protected]> > # Date 1503540237 18000 > # Wed Aug 23 21:03:57 2017 -0500 > # Node ID 62b4032371bd45217d40e2f0daf8ecd6956601d8 > # Parent a11e114a2bcde4afb515dd0b70f3ef39693f475a > [PATCH 4 of 4] SSL: add identity hint config directive. As in previous patches, there should be no "[PATCH 4 of 4] ". Otherwise loos good. Following this and previous patches review, here are all four patches adjusted according to the comments. Please take a look if it works for you. # HG changeset patch # User Nate Karstens <[email protected]> # Date 1503540018 18000 # Wed Aug 23 21:00:18 2017 -0500 # Node ID a87e224e8d6b2993dfcd8903bfb0e7eb7fd934fa # Parent c7d4017c8876af6d8570e400320537d7d39e9578 Core: add function to decode hexadecimal strings. Adds functionality to convert a hexadecimal string into binary data. This will be used to decode PSKs stored in hexadecimal representation. Signed-off-by: Nate Karstens <[email protected]> diff --git a/src/core/ngx_string.c b/src/core/ngx_string.c --- a/src/core/ngx_string.c +++ b/src/core/ngx_string.c @@ -1118,6 +1118,56 @@ ngx_hex_dump(u_char *dst, u_char *src, s } +ngx_int_t +ngx_hex_decode(u_char *dst, u_char *src, size_t len) +{ + u_char ch, decoded; + + if (len & 1) { + return NGX_ERROR; + } + + while (len) { + ch = *src++; + len -= 2; + + if (ch >= '0' && ch <= '9') { + decoded = ch - '0'; + goto second; + } + + ch |= 0x20; + + if (ch >= 'a' && ch <= 'f') { + decoded = ch - 'a' + 10; + goto second; + } + + return NGX_ERROR; + + second: + + ch = *src++; + + if (ch >= '0' && ch <= '9') { + *dst++ = (u_char) ((decoded << 4) + ch - '0'); + continue; + } + + ch |= 0x20; + + if (ch >= 'a' && ch <= 'f') { + *dst++ = (u_char) ((decoded << 4) + ch - 'a' + 10); + continue; + } + + return NGX_ERROR; + } + + return NGX_OK; +} + + void ngx_encode_base64(ngx_str_t *dst, ngx_str_t *src) { diff --git a/src/core/ngx_string.h b/src/core/ngx_string.h --- a/src/core/ngx_string.h +++ b/src/core/ngx_string.h @@ -177,6 +177,7 @@ time_t ngx_atotm(u_char *line, size_t n) ngx_int_t ngx_hextoi(u_char *line, size_t n); u_char *ngx_hex_dump(u_char *dst, u_char *src, size_t len); +ngx_int_t ngx_hex_decode(u_char *dst, u_char *src, size_t len); #define ngx_base64_encoded_length(len) (((len + 2) / 3) * 4) # HG changeset patch # User Nate Karstens <[email protected]> # Date 1503540059 18000 # Wed Aug 23 21:00:59 2017 -0500 # Node ID d89f77108fa8a20bc1fb9cdbaf43fefbc5e07119 # Parent a87e224e8d6b2993dfcd8903bfb0e7eb7fd934fa SSL: add support for PSK cipher suites. Adds support for TLS connections using PSK cipher suites. A new configuration directive, ssl_psk_file, specifies the file that contains a list of identities and associated PSKs. Each line of the file begins with the identity, followed by a colon character (':'), and ending with the PSK. As required by RFC 4279 section 5.4, PSKs may be entered either as plain text or using hexadecimal encoding. Hexadecimal PSKs must begin with "{HEX}". PSKs without this prefix are assumed to be plain text, but they may optionally begin with "{PLAIN}" to denote this. Some examples: gary:plain_text_password min:{PLAIN}another_text_password cliff:{HEX}ab0123CD PSK functionality can be easily tested with the OpenSSL s_client using the "-psk" and "-psk_identity" options. Signed-off-by: Nate Karstens <[email protected]> diff --git a/contrib/vim/syntax/nginx.vim b/contrib/vim/syntax/nginx.vim --- a/contrib/vim/syntax/nginx.vim +++ b/contrib/vim/syntax/nginx.vim @@ -550,6 +550,7 @@ syn keyword ngxDirective contained ssl_p syn keyword ngxDirective contained ssl_prefer_server_ciphers syn keyword ngxDirective contained ssl_preread syn keyword ngxDirective contained ssl_protocols +syn keyword ngxDirective contained ssl_psk_file syn keyword ngxDirective contained ssl_session_cache syn keyword ngxDirective contained ssl_session_ticket_key syn keyword ngxDirective contained ssl_session_tickets diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -11,6 +11,7 @@ #define NGX_SSL_PASSWORD_BUFFER_SIZE 4096 +#define NGX_SSL_PSK_BUFFER_SIZE 4096 typedef struct { @@ -24,6 +25,10 @@ static int ngx_ssl_verify_callback(int o static void ngx_ssl_info_callback(const ngx_ssl_conn_t *ssl_conn, int where, int ret); static void ngx_ssl_passwords_cleanup(void *data); +#ifdef PSK_MAX_IDENTITY_LEN +static unsigned int ngx_ssl_psk_callback(ngx_ssl_conn_t *ssl_conn, + const char *identity, unsigned char *psk, unsigned int max_psk_len); +#endif static void ngx_ssl_handshake_handler(ngx_event_t *ev); static ngx_int_t ngx_ssl_handle_recv(ngx_connection_t *c, int n); static void ngx_ssl_write_handler(ngx_event_t *wev); @@ -110,6 +115,7 @@ int ngx_ssl_connection_index; int ngx_ssl_server_conf_index; int ngx_ssl_session_cache_index; int ngx_ssl_session_ticket_keys_index; +int ngx_ssl_psk_index; int ngx_ssl_certificate_index; int ngx_ssl_next_certificate_index; int ngx_ssl_certificate_name_index; @@ -195,6 +201,14 @@ ngx_ssl_init(ngx_log_t *log) return NGX_ERROR; } + ngx_ssl_psk_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL); + + if (ngx_ssl_psk_index == -1) { + ngx_ssl_error(NGX_LOG_ALERT, log, 0, + "SSL_CTX_get_ex_new_index() failed"); + return NGX_ERROR; + } + ngx_ssl_certificate_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL); if (ngx_ssl_certificate_index == -1) { @@ -1163,6 +1177,170 @@ ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_s ngx_int_t +ngx_ssl_psk_file(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file) +{ +#ifdef PSK_MAX_IDENTITY_LEN + + if (ngx_conf_full_name(cf->cycle, file, 1) != NGX_OK) { + return NGX_ERROR; + } + + if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_psk_index, file) == 0) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_set_ex_data() failed"); + return NGX_ERROR; + } + + SSL_CTX_set_psk_server_callback(ssl->ctx, ngx_ssl_psk_callback); + +#endif + + return NGX_OK; +} + + +#ifdef PSK_MAX_IDENTITY_LEN + +static unsigned int +ngx_ssl_psk_callback(ngx_ssl_conn_t *ssl_conn, const char *identity, + unsigned char *psk, unsigned int max_psk_len) +{ + u_char *p, *last, *end, *colon; + size_t len; + ssize_t n; + SSL_CTX *ssl_ctx; + ngx_fd_t fd; + ngx_str_t *file; + unsigned int psk_len; + ngx_connection_t *c; + u_char buf[NGX_SSL_PSK_BUFFER_SIZE]; + + c = ngx_ssl_get_connection(ssl_conn); + + ssl_ctx = SSL_get_SSL_CTX(ssl_conn); + file = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_psk_index); + + fd = ngx_open_file(file->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); + if (fd == NGX_INVALID_FILE) { + ngx_ssl_error(NGX_LOG_ERR, c->log, ngx_errno, + ngx_open_file_n " \"%V\" failed", file); + return 0; + } + + psk_len = 0; + + len = 0; + last = buf; + + do { + n = ngx_read_fd(fd, last, NGX_SSL_PSK_BUFFER_SIZE - len); + + if (n == -1) { + ngx_ssl_error(NGX_LOG_ERR, c->log, ngx_errno, + ngx_read_fd_n " \"%V\" failed", file); + goto cleanup; + } + + end = last + n; + + if (len && n == 0) { + *end++ = LF; + } + + for (p = buf; /* void */; p = last) { + last = ngx_strlchr(last, end, LF); + + if (last == NULL) { + break; + } + + len = last++ - p; + + if (len && p[len - 1] == CR) { + len--; + } + + if (len == 0) { + continue; + } + + colon = ngx_strlchr(p, p + len, ':'); + + if (colon == NULL) { + continue; + } + + *colon = '\0'; + + if (ngx_strcmp(p, identity) != 0) { + continue; + } + + len -= colon + 1 - p; + p = colon + 1; + + if (ngx_strncmp(p, "{HEX}", sizeof("{HEX}") - 1) == 0) { + + p += sizeof("{HEX}") - 1; + len -= sizeof("{HEX}") - 1; + + if (len / 2 > max_psk_len) { + goto cleanup; + } + + if (ngx_hex_decode(psk, p, len) != NGX_OK) { + ngx_memzero(psk, len / 2); + goto cleanup; + } + + psk_len = len / 2; + + goto cleanup; + + } else if (ngx_strncmp(p, "{PLAIN}", sizeof("{PLAIN}") - 1) == 0) { + p += sizeof("{PLAIN}") - 1; + len -= sizeof("{PLAIN}") - 1; + } + + if (len > max_psk_len) { + goto cleanup; + } + + ngx_memcpy(psk, p, len); + psk_len = len; + + goto cleanup; + } + + len = end - p; + + if (len == NGX_SSL_PSK_BUFFER_SIZE) { + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, + "too long line in \"%V\"", file); + goto cleanup; + } + + ngx_memmove(buf, p, len); + last = buf + len; + + } while (n != 0); + +cleanup: + + if (ngx_close_file(fd) == NGX_FILE_ERROR) { + ngx_ssl_error(NGX_LOG_ALERT, c->log, ngx_errno, + ngx_close_file_n " %V failed", file); + } + + ngx_memzero(buf, NGX_SSL_PSK_BUFFER_SIZE); + + return psk_len; +} + +#endif + + +ngx_int_t ngx_ssl_create_connection(ngx_ssl_t *ssl, ngx_connection_t *c, ngx_uint_t flags) { ngx_ssl_connection_t *sc; @@ -2071,6 +2249,9 @@ ngx_ssl_connection_error(ngx_connection_ || n == SSL_R_NO_COMPRESSION_SPECIFIED /* 187 */ || n == SSL_R_NO_SHARED_CIPHER /* 193 */ || n == SSL_R_RECORD_LENGTH_MISMATCH /* 213 */ +#ifdef SSL_R_PSK_IDENTITY_NOT_FOUND + || n == SSL_R_PSK_IDENTITY_NOT_FOUND /* 223 */ +#endif #ifdef SSL_R_PARSE_TLSEXT || n == SSL_R_PARSE_TLSEXT /* 227 */ #endif diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -167,6 +167,7 @@ RSA *ngx_ssl_rsa512_key_callback(ngx_ssl ngx_array_t *ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file); ngx_int_t ngx_ssl_dhparam(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file); ngx_int_t ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *name); +ngx_int_t ngx_ssl_psk_file(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file); ngx_int_t ngx_ssl_session_cache(ngx_ssl_t *ssl, ngx_str_t *sess_ctx, ssize_t builtin_session_cache, ngx_shm_zone_t *shm_zone, time_t timeout); ngx_int_t ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl, @@ -253,6 +254,7 @@ extern int ngx_ssl_connection_index; extern int ngx_ssl_server_conf_index; extern int ngx_ssl_session_cache_index; extern int ngx_ssl_session_ticket_keys_index; +extern int ngx_ssl_psk_index; extern int ngx_ssl_certificate_index; extern int ngx_ssl_next_certificate_index; extern int ngx_ssl_certificate_name_index; diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -234,6 +234,13 @@ static ngx_command_t ngx_http_ssl_comma offsetof(ngx_http_ssl_srv_conf_t, stapling_verify), NULL }, + { ngx_string("ssl_psk_file"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, psk_file), + NULL }, + ngx_null_command }; @@ -543,6 +550,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t * sscf->shm_zone = NULL; * sscf->stapling_file = { 0, NULL }; * sscf->stapling_responder = { 0, NULL }; + * sscf->psk_file = { 0, NULL }; */ sscf->enable = NGX_CONF_UNSET; @@ -624,6 +632,8 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t * ngx_conf_merge_str_value(conf->stapling_responder, prev->stapling_responder, ""); + ngx_conf_merge_str_value(conf->psk_file, prev->psk_file, ""); + conf->ssl.log = cf->log; if (conf->enable) { @@ -804,6 +814,10 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t * } + if (ngx_ssl_psk_file(cf, &conf->ssl, &conf->psk_file) != NGX_OK) { + return NGX_CONF_ERROR; + } + return NGX_CONF_OK; } diff --git a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h --- a/src/http/modules/ngx_http_ssl_module.h +++ b/src/http/modules/ngx_http_ssl_module.h @@ -55,6 +55,8 @@ typedef struct { ngx_str_t stapling_file; ngx_str_t stapling_responder; + ngx_str_t psk_file; + u_char *file; ngx_uint_t line; } ngx_http_ssl_srv_conf_t; # HG changeset patch # User Nate Karstens <[email protected]> # Date 1503540211 18000 # Wed Aug 23 21:03:31 2017 -0500 # Node ID 3d2e75b562a3615757d17a00cc54db2d2e90ddd0 # Parent d89f77108fa8a20bc1fb9cdbaf43fefbc5e07119 SSL: add PSK identity variable. Adds the variable $ssl_psk_identity to get the PSK identity used in a connnection secured with a PSK cipher suite. Signed-off-by: Nate Karstens <[email protected]> diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -4350,6 +4350,37 @@ ngx_ssl_parse_time( } +ngx_int_t +ngx_ssl_get_psk_identity(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) +{ +#ifdef PSK_MAX_IDENTITY_LEN + + size_t len; + const char *identity; + + identity = SSL_get_psk_identity(c->ssl->connection); + + if (identity) { + len = ngx_strlen(identity); + + s->len = len; + s->data = ngx_pnalloc(pool, len); + if (s->data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(s->data, identity, len); + + return NGX_OK; + } + +#endif + + s->len = 0; + return NGX_OK; +} + + static void * ngx_openssl_create_conf(ngx_cycle_t *cycle) { diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -235,6 +235,8 @@ ngx_int_t ngx_ssl_get_client_v_end(ngx_c ngx_str_t *s); ngx_int_t ngx_ssl_get_client_v_remain(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); +ngx_int_t ngx_ssl_get_psk_identity(ngx_connection_t *c, ngx_pool_t *pool, + ngx_str_t *s); ngx_int_t ngx_ssl_handshake(ngx_connection_t *c); diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -340,6 +340,9 @@ static ngx_http_variable_t ngx_http_ssl { ngx_string("ssl_client_v_remain"), NULL, ngx_http_ssl_variable, (uintptr_t) ngx_ssl_get_client_v_remain, NGX_HTTP_VAR_CHANGEABLE, 0 }, + { ngx_string("ssl_psk_identity"), NULL, ngx_http_ssl_variable, + (uintptr_t) ngx_ssl_get_psk_identity, NGX_HTTP_VAR_CHANGEABLE, 0 }, + ngx_http_null_variable }; # HG changeset patch # User Nate Karstens <[email protected]> # Date 1503540237 18000 # Wed Aug 23 21:03:57 2017 -0500 # Node ID 3876f3a8d4bb1bdaabc61492bf8be838d9dae5fb # Parent 3d2e75b562a3615757d17a00cc54db2d2e90ddd0 SSL: add identity hint config directive. Adds the directive "ssl_psk_identity_hint" to the ngx_http_ssl_module. This allows the user to specify the PSK identity hint given to the connecting client. Signed-off-by: Nate Karstens <[email protected]> diff --git a/contrib/vim/syntax/nginx.vim b/contrib/vim/syntax/nginx.vim --- a/contrib/vim/syntax/nginx.vim +++ b/contrib/vim/syntax/nginx.vim @@ -551,6 +551,7 @@ syn keyword ngxDirective contained ssl_p syn keyword ngxDirective contained ssl_preread syn keyword ngxDirective contained ssl_protocols syn keyword ngxDirective contained ssl_psk_file +syn keyword ngxDirective contained ssl_psk_identity_hint syn keyword ngxDirective contained ssl_session_cache syn keyword ngxDirective contained ssl_session_ticket_key syn keyword ngxDirective contained ssl_session_tickets diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -1177,7 +1177,8 @@ ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_s ngx_int_t -ngx_ssl_psk_file(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file) +ngx_ssl_psk_file(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file, + ngx_str_t *identity_hint) { #ifdef PSK_MAX_IDENTITY_LEN @@ -1191,6 +1192,14 @@ ngx_ssl_psk_file(ngx_conf_t *cf, ngx_ssl return NGX_ERROR; } + if (SSL_CTX_use_psk_identity_hint(ssl->ctx, (char *) identity_hint->data) + == 0) + { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_use_psk_identity_hint() failed"); + return NGX_ERROR; + } + SSL_CTX_set_psk_server_callback(ssl->ctx, ngx_ssl_psk_callback); #endif diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -167,7 +167,8 @@ RSA *ngx_ssl_rsa512_key_callback(ngx_ssl ngx_array_t *ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file); ngx_int_t ngx_ssl_dhparam(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file); ngx_int_t ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *name); -ngx_int_t ngx_ssl_psk_file(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file); +ngx_int_t ngx_ssl_psk_file(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file, + ngx_str_t *identity_hint); ngx_int_t ngx_ssl_session_cache(ngx_ssl_t *ssl, ngx_str_t *sess_ctx, ssize_t builtin_session_cache, ngx_shm_zone_t *shm_zone, time_t timeout); ngx_int_t ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl, diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -241,6 +241,13 @@ static ngx_command_t ngx_http_ssl_comma offsetof(ngx_http_ssl_srv_conf_t, psk_file), NULL }, + { ngx_string("ssl_psk_identity_hint"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_ssl_srv_conf_t, psk_identity_hint), + NULL }, + ngx_null_command }; @@ -554,6 +561,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t * sscf->stapling_file = { 0, NULL }; * sscf->stapling_responder = { 0, NULL }; * sscf->psk_file = { 0, NULL }; + * sscf->psk_identity_hint = { 0, NULL }; */ sscf->enable = NGX_CONF_UNSET; @@ -636,6 +644,8 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t * prev->stapling_responder, ""); ngx_conf_merge_str_value(conf->psk_file, prev->psk_file, ""); + ngx_conf_merge_str_value(conf->psk_identity_hint, + prev->psk_identity_hint, ""); conf->ssl.log = cf->log; @@ -817,7 +827,10 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t * } - if (ngx_ssl_psk_file(cf, &conf->ssl, &conf->psk_file) != NGX_OK) { + if (ngx_ssl_psk_file(cf, &conf->ssl, &conf->psk_file, + &conf->psk_identity_hint) + != NGX_OK) + { return NGX_CONF_ERROR; } diff --git a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h --- a/src/http/modules/ngx_http_ssl_module.h +++ b/src/http/modules/ngx_http_ssl_module.h @@ -56,6 +56,7 @@ typedef struct { ngx_str_t stapling_responder; ngx_str_t psk_file; + ngx_str_t psk_identity_hint; u_char *file; ngx_uint_t line; -- Maxim Dounin http://nginx.org/ _______________________________________________ nginx-devel mailing list [email protected] http://mailman.nginx.org/mailman/listinfo/nginx-devel
