Mercurial > hg > nginx-quic
changeset 8287:cef042935003 quic
QUIC: the "quic_host_key" directive.
The token generation in QUIC is reworked. Single host key is used to generate
all required keys of needed sizes using HKDF.
The "quic_stateless_reset_token_key" directive is removed. Instead, the
"quic_host_key" directive is used, which reads key from file, or sets it
to random bytes if not specified.
author | Vladimir Homutov <vl@nginx.com> |
---|---|
date | Mon, 08 Feb 2021 16:49:33 +0300 |
parents | 3956bbf91002 |
children | 0b3d70fbba99 |
files | src/event/quic/ngx_event_quic.c src/event/quic/ngx_event_quic.h src/event/quic/ngx_event_quic_protection.c src/event/quic/ngx_event_quic_protection.h src/http/modules/ngx_http_quic_module.c src/stream/ngx_stream_quic_module.c |
diffstat | 6 files changed, 346 insertions(+), 88 deletions(-) [+] |
line wrap: on
line diff
--- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -225,6 +225,8 @@ static ngx_quic_connection_t *ngx_quic_n ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, + u_char *secret, u_char *token); static ngx_int_t ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c, @@ -1245,7 +1247,7 @@ ngx_quic_send_stateless_reset(ngx_connec token = &buf[len - NGX_QUIC_SR_TOKEN_LEN]; - if (ngx_quic_new_sr_token(c, &pkt->dcid, &conf->sr_token_key, token) + if (ngx_quic_new_sr_token(c, &pkt->dcid, conf->sr_token_key, token) != NGX_OK) { return NGX_ERROR; @@ -1258,6 +1260,32 @@ ngx_quic_send_stateless_reset(ngx_connec static ngx_int_t +ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret, + u_char *token) +{ + ngx_str_t tmp; + + tmp.data = secret; + tmp.len = NGX_QUIC_SR_KEY_LEN; + + if (ngx_quic_derive_key(c->log, "sr_token_key", &tmp, cid, token, + NGX_QUIC_SR_TOKEN_LEN) + != NGX_OK) + { + return NGX_ERROR; + } + +#if (NGX_DEBUG) + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stateless reset token %*xs", + (size_t) NGX_QUIC_SR_TOKEN_LEN, token); +#endif + + return NGX_OK; +} + + +static ngx_int_t ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) { u_char *tail, ch; @@ -1391,9 +1419,10 @@ ngx_quic_send_retry(ngx_connection_t *c, u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE]; u_char dcid[NGX_QUIC_SERVER_CID_LEN]; - expires = ngx_time() + NGX_QUIC_RETRY_LIFETIME; - - if (ngx_quic_new_token(c, conf->token_key, &token, &inpkt->dcid, expires, 1) + expires = ngx_time() + NGX_QUIC_RETRY_TOKEN_LIFETIME; + + if (ngx_quic_new_token(c, conf->av_token_key, &token, &inpkt->dcid, + expires, 1) != NGX_OK) { return NGX_ERROR; @@ -1723,7 +1752,7 @@ ngx_quic_init_connection(ngx_connection_ } #endif - if (ngx_quic_new_sr_token(c, &qc->dcid, &qc->conf->sr_token_key, + if (ngx_quic_new_sr_token(c, &qc->dcid, qc->conf->sr_token_key, qc->tp.sr_token) != NGX_OK) { @@ -2235,7 +2264,7 @@ ngx_quic_process_packet(ngx_connection_t if (pkt->level == ssl_encryption_initial) { ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic token len:%uz %xV", + "quic address validation token len:%uz %xV", pkt->token.len, &pkt->token); } #endif @@ -2306,7 +2335,7 @@ ngx_quic_process_packet(ngx_connection_t if (pkt->token.len) { - rc = ngx_quic_validate_token(c, conf->token_key, pkt); + rc = ngx_quic_validate_token(c, conf->av_token_key, pkt); if (rc == NGX_ERROR) { /* internal error */ @@ -2321,10 +2350,10 @@ ngx_quic_process_packet(ngx_connection_t /* token is invalid */ if (pkt->retried) { - /* invalid Retry token */ + /* invalid address validation token */ return ngx_quic_send_early_cc(c, pkt, NGX_QUIC_ERR_INVALID_TOKEN, - "invalid token"); + "invalid address validation token"); } else if (conf->retry) { /* invalid NEW_TOKEN */ return ngx_quic_send_retry(c, conf, pkt); @@ -3286,7 +3315,7 @@ ngx_quic_send_new_token(ngx_connection_t expires = ngx_time() + NGX_QUIC_NEW_TOKEN_LIFETIME; - if (ngx_quic_new_token(c, qc->conf->token_key, &token, NULL, expires, 0) + if (ngx_quic_new_token(c, qc->conf->av_token_key, &token, NULL, expires, 0) != NGX_OK) { return NGX_ERROR; @@ -4667,7 +4696,7 @@ ngx_quic_issue_server_ids(ngx_connection frame->u.ncid.len = NGX_QUIC_SERVER_CID_LEN; ngx_memcpy(frame->u.ncid.cid, id, NGX_QUIC_SERVER_CID_LEN); - if (ngx_quic_new_sr_token(c, &dcid, &qc->conf->sr_token_key, + if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key, frame->u.ncid.srt) != NGX_OK) {
--- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -27,9 +27,11 @@ #define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3 #define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25 -#define NGX_QUIC_DEFAULT_SRT_KEY_LEN 32 +#define NGX_QUIC_DEFAULT_HOST_KEY_LEN 32 +#define NGX_QUIC_SR_KEY_LEN 32 +#define NGX_QUIC_AV_KEY_LEN 32 -#define NGX_QUIC_RETRY_LIFETIME 3 /* seconds */ +#define NGX_QUIC_RETRY_TOKEN_LIFETIME 3 /* seconds */ #define NGX_QUIC_NEW_TOKEN_LIFETIME 600 /* seconds */ #define NGX_QUIC_RETRY_BUFFER_SIZE 256 /* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(64) */ @@ -96,8 +98,9 @@ typedef struct { ngx_quic_tp_t tp; ngx_flag_t retry; ngx_flag_t require_alpn; - u_char token_key[32]; /* AES 256 */ - ngx_str_t sr_token_key; /* stateless reset token key */ + ngx_str_t host_key; + u_char av_token_key[NGX_QUIC_AV_KEY_LEN]; + u_char sr_token_key[NGX_QUIC_SR_KEY_LEN]; } ngx_quic_conf_t;
--- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -942,57 +942,48 @@ ngx_quic_create_retry_packet(ngx_quic_he ngx_int_t -ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, ngx_str_t *secret, - u_char *token) +ngx_quic_derive_key(ngx_log_t *log, const char *label, ngx_str_t *secret, + ngx_str_t *salt, u_char *out, size_t len) { + size_t is_len, info_len; uint8_t *p; - size_t is_len, key_len, info_len; - ngx_str_t label; const EVP_MD *digest; - uint8_t info[20]; - uint8_t is[SHA256_DIGEST_LENGTH]; - uint8_t key[SHA256_DIGEST_LENGTH]; - /* 10.4.2. Calculating a Stateless Reset Token */ + uint8_t is[SHA256_DIGEST_LENGTH]; + uint8_t info[20]; digest = EVP_sha256(); - ngx_str_set(&label, "sr_token_key"); if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len, - cid->data, cid->len) + salt->data, salt->len) != NGX_OK) { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "ngx_hkdf_extract(%V) failed", &label); + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "ngx_hkdf_extract(%s) failed", label); return NGX_ERROR; } - key_len = SHA256_DIGEST_LENGTH; - - info_len = 2 + 1 + label.len + 1; - info[0] = 0; - info[1] = key_len; - info[2] = label.len; + info[1] = len; + info[2] = ngx_strlen(label); - p = ngx_cpymem(&info[3], label.data, label.len); - *p = '\0'; + info_len = 2 + 1 + info[2] + 1; - if (ngx_hkdf_expand(key, key_len, digest, is, is_len, info, info_len) - != NGX_OK) - { - ngx_ssl_error(NGX_LOG_INFO, c->log, 0, - "ngx_hkdf_expand(%V) failed", &label); + if (info_len >= 20) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "ngx_quic_create_key label \"%s\" too long", label); return NGX_ERROR; } - ngx_memcpy(token, key, NGX_QUIC_SR_TOKEN_LEN); + p = ngx_cpymem(&info[3], label, info[2]); + *p = '\0'; -#if (NGX_DEBUG) - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stateless reset token %*xs", - (size_t) NGX_QUIC_SR_TOKEN_LEN, token); -#endif + if (ngx_hkdf_expand(out, len, digest, is, is_len, info, info_len) != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "ngx_hkdf_expand(%s) failed", label); + return NGX_ERROR; + } return NGX_OK; }
--- a/src/event/quic/ngx_event_quic_protection.h +++ b/src/event/quic/ngx_event_quic_protection.h @@ -11,6 +11,8 @@ #include <ngx_config.h> #include <ngx_core.h> +#include <ngx_event_quic_transport.h> + #define NGX_QUIC_ENCRYPTION_LAST ((ssl_encryption_application) + 1) @@ -27,10 +29,8 @@ void ngx_quic_keys_discard(ngx_quic_keys enum ssl_encryption_level_t level); void ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys); ngx_int_t ngx_quic_keys_update(ngx_connection_t *c, ngx_quic_keys_t *keys); - -ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, - ngx_str_t *key, u_char *token); - +ngx_int_t ngx_quic_derive_key(ngx_log_t *log, const char *label, + ngx_str_t *secret, ngx_str_t *salt, u_char *out, size_t len); ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res); ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn);
--- a/src/http/modules/ngx_http_quic_module.c +++ b/src/http/modules/ngx_http_quic_module.c @@ -9,6 +9,8 @@ #include <ngx_core.h> #include <ngx_http.h> +#include <ngx_event_quic_protection.h> + static ngx_int_t ngx_http_variable_quic(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); @@ -20,7 +22,8 @@ static char *ngx_http_quic_max_ack_delay void *data); static char *ngx_http_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, void *data); - +static char *ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); static ngx_conf_post_t ngx_http_quic_max_ack_delay_post = { ngx_http_quic_max_ack_delay }; @@ -125,11 +128,11 @@ static ngx_command_t ngx_http_quic_comm offsetof(ngx_quic_conf_t, retry), NULL }, - { ngx_string("quic_stateless_reset_token_key"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, - ngx_conf_set_str_slot, + { ngx_string("quic_host_key"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_http_quic_host_key, NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, sr_token_key), + 0, NULL }, ngx_null_command @@ -174,6 +177,8 @@ static ngx_http_variable_t ngx_http_qui ngx_http_null_variable }; +static ngx_str_t ngx_http_quic_salt = ngx_string("ngx_quic"); + ngx_int_t ngx_http_quic_init(ngx_connection_t *c) @@ -270,7 +275,7 @@ ngx_http_quic_create_srv_conf(ngx_conf_t * conf->tp.sr_token = { 0 } * conf->tp.sr_enabled = 0 * conf->tp.preferred_address = NULL - * conf->sr_token_key = { 0, NULL } + * conf->host_key = { 0, NULL } */ conf->tp.max_idle_timeout = NGX_CONF_UNSET_MSEC; @@ -346,23 +351,37 @@ ngx_http_quic_merge_srv_conf(ngx_conf_t ngx_conf_merge_value(conf->retry, prev->retry, 0); - if (RAND_bytes(conf->token_key, sizeof(conf->token_key)) <= 0) { + ngx_conf_merge_str_value(conf->host_key, prev->host_key, ""); + + if (conf->host_key.len == 0) { + + conf->host_key.len = NGX_QUIC_DEFAULT_HOST_KEY_LEN; + conf->host_key.data = ngx_palloc(cf->pool, conf->host_key.len); + if (conf->host_key.data == NULL) { + return NGX_CONF_ERROR; + } + + if (RAND_bytes(conf->host_key.data, NGX_QUIC_DEFAULT_HOST_KEY_LEN) + <= 0) + { + return NGX_CONF_ERROR; + } + } + + if (ngx_quic_derive_key(cf->log, "av_token_key", + &conf->host_key, &ngx_http_quic_salt, + conf->av_token_key, NGX_QUIC_AV_KEY_LEN) + != NGX_OK) + { return NGX_CONF_ERROR; } - ngx_conf_merge_str_value(conf->sr_token_key, prev->sr_token_key, ""); - - if (conf->sr_token_key.len == 0) { - conf->sr_token_key.len = NGX_QUIC_DEFAULT_SRT_KEY_LEN; - - conf->sr_token_key.data = ngx_pnalloc(cf->pool, conf->sr_token_key.len); - if (conf->sr_token_key.data == NULL) { - return NGX_CONF_ERROR; - } - - if (RAND_bytes(conf->sr_token_key.data, conf->sr_token_key.len) <= 0) { - return NGX_CONF_ERROR; - } + if (ngx_quic_derive_key(cf->log, "sr_token_key", + &conf->host_key, &ngx_http_quic_salt, + conf->sr_token_key, NGX_QUIC_SR_KEY_LEN) + != NGX_OK) + { + return NGX_CONF_ERROR; } sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module); @@ -407,3 +426,101 @@ ngx_http_quic_max_udp_payload_size(ngx_c return NGX_CONF_OK; } + + +static char * +ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_quic_conf_t *qcf = conf; + + u_char *buf; + size_t size; + ssize_t n; + ngx_str_t *value; + ngx_file_t file; + ngx_file_info_t fi; + + if (qcf->host_key.len) { + return "is duplicate"; + } + + buf = NULL; +#if (NGX_SUPPRESS_WARN) + size = 0; +#endif + + value = cf->args->elts; + + if (ngx_conf_full_name(cf->cycle, &value[1], 1) != NGX_OK) { + return NGX_CONF_ERROR; + } + + ngx_memzero(&file, sizeof(ngx_file_t)); + file.name = value[1]; + file.log = cf->log; + + file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); + + if (file.fd == NGX_INVALID_FILE) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, + ngx_open_file_n " \"%V\" failed", &file.name); + return NGX_CONF_ERROR; + } + + if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, + ngx_fd_info_n " \"%V\" failed", &file.name); + goto failed; + } + + size = ngx_file_size(&fi); + + if (size == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%V\" zero key size", &file.name); + goto failed; + } + + buf = ngx_pnalloc(cf->pool, size); + if (buf == NULL) { + goto failed; + } + + n = ngx_read_file(&file, buf, size, 0); + + if (n == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, + ngx_read_file_n " \"%V\" failed", &file.name); + goto failed; + } + + if ((size_t) n != size) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, 0, + ngx_read_file_n " \"%V\" returned only " + "%z bytes instead of %uz", &file.name, n, size); + goto failed; + } + + qcf->host_key.data = buf; + qcf->host_key.len = n; + + if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, + ngx_close_file_n " \"%V\" failed", &file.name); + } + + return NGX_CONF_OK; + +failed: + + if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, + ngx_close_file_n " \"%V\" failed", &file.name); + } + + if (buf) { + ngx_explicit_memzero(buf, size); + } + + return NGX_CONF_ERROR; +}
--- a/src/stream/ngx_stream_quic_module.c +++ b/src/stream/ngx_stream_quic_module.c @@ -9,6 +9,8 @@ #include <ngx_core.h> #include <ngx_stream.h> +#include <ngx_event_quic_protection.h> + static ngx_int_t ngx_stream_variable_quic(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); @@ -20,6 +22,8 @@ static char *ngx_stream_quic_max_ack_del void *data); static char *ngx_stream_quic_max_udp_payload_size(ngx_conf_t *cf, void *post, void *data); +static char *ngx_stream_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); static ngx_conf_post_t ngx_stream_quic_max_ack_delay_post = @@ -126,11 +130,11 @@ static ngx_command_t ngx_stream_quic_co offsetof(ngx_quic_conf_t, retry), NULL }, - { ngx_string("quic_stateless_reset_token_key"), + { ngx_string("quic_host_key"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, - ngx_conf_set_str_slot, + ngx_stream_quic_host_key, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_quic_conf_t, sr_token_key), + 0, NULL }, ngx_null_command @@ -172,6 +176,8 @@ static ngx_stream_variable_t ngx_stream ngx_stream_null_variable }; +static ngx_str_t ngx_stream_quic_salt = ngx_string("ngx_quic"); + static ngx_int_t ngx_stream_variable_quic(ngx_stream_session_t *s, @@ -229,7 +235,7 @@ ngx_stream_quic_create_srv_conf(ngx_conf * conf->tp.initial_scid = { 0, NULL }; * conf->tp.retry_scid = { 0, NULL }; * conf->tp.preferred_address = NULL - * conf->sr_token_key = { 0, NULL } + * conf->host_key = { 0, NULL } * conf->require_alpn = 0; */ @@ -305,23 +311,37 @@ ngx_stream_quic_merge_srv_conf(ngx_conf_ ngx_conf_merge_value(conf->retry, prev->retry, 0); - if (RAND_bytes(conf->token_key, sizeof(conf->token_key)) <= 0) { + ngx_conf_merge_str_value(conf->host_key, prev->host_key, ""); + + if (conf->host_key.len == 0) { + + conf->host_key.len = NGX_QUIC_DEFAULT_HOST_KEY_LEN; + conf->host_key.data = ngx_palloc(cf->pool, conf->host_key.len); + if (conf->host_key.data == NULL) { + return NGX_CONF_ERROR; + } + + if (RAND_bytes(conf->host_key.data, NGX_QUIC_DEFAULT_HOST_KEY_LEN) + <= 0) + { + return NGX_CONF_ERROR; + } + } + + if (ngx_quic_derive_key(cf->log, "av_token_key", + &conf->host_key, &ngx_stream_quic_salt, + conf->av_token_key, NGX_QUIC_AV_KEY_LEN) + != NGX_OK) + { return NGX_CONF_ERROR; } - ngx_conf_merge_str_value(conf->sr_token_key, prev->sr_token_key, ""); - - if (conf->sr_token_key.len == 0) { - conf->sr_token_key.len = NGX_QUIC_DEFAULT_SRT_KEY_LEN; - - conf->sr_token_key.data = ngx_pnalloc(cf->pool, conf->sr_token_key.len); - if (conf->sr_token_key.data == NULL) { - return NGX_CONF_ERROR; - } - - if (RAND_bytes(conf->sr_token_key.data, conf->sr_token_key.len) <= 0) { - return NGX_CONF_ERROR; - } + if (ngx_quic_derive_key(cf->log, "sr_token_key", + &conf->host_key, &ngx_stream_quic_salt, + conf->sr_token_key, NGX_QUIC_SR_KEY_LEN) + != NGX_OK) + { + return NGX_CONF_ERROR; } scf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_ssl_module); @@ -366,3 +386,101 @@ ngx_stream_quic_max_udp_payload_size(ngx return NGX_CONF_OK; } + + +static char * +ngx_stream_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_quic_conf_t *qcf = conf; + + u_char *buf; + size_t size; + ssize_t n; + ngx_str_t *value; + ngx_file_t file; + ngx_file_info_t fi; + + if (qcf->host_key.len) { + return "is duplicate"; + } + + buf = NULL; +#if (NGX_SUPPRESS_WARN) + size = 0; +#endif + + value = cf->args->elts; + + if (ngx_conf_full_name(cf->cycle, &value[1], 1) != NGX_OK) { + return NGX_CONF_ERROR; + } + + ngx_memzero(&file, sizeof(ngx_file_t)); + file.name = value[1]; + file.log = cf->log; + + file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); + + if (file.fd == NGX_INVALID_FILE) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, + ngx_open_file_n " \"%V\" failed", &file.name); + return NGX_CONF_ERROR; + } + + if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, + ngx_fd_info_n " \"%V\" failed", &file.name); + goto failed; + } + + size = ngx_file_size(&fi); + + if (size == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%V\" zero key size", &file.name); + goto failed; + } + + buf = ngx_pnalloc(cf->pool, size); + if (buf == NULL) { + goto failed; + } + + n = ngx_read_file(&file, buf, size, 0); + + if (n == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, + ngx_read_file_n " \"%V\" failed", &file.name); + goto failed; + } + + if ((size_t) n != size) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, 0, + ngx_read_file_n " \"%V\" returned only " + "%z bytes instead of %uz", &file.name, n, size); + goto failed; + } + + qcf->host_key.data = buf; + qcf->host_key.len = n; + + if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, + ngx_close_file_n " \"%V\" failed", &file.name); + } + + return NGX_CONF_OK; + +failed: + + if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, + ngx_close_file_n " \"%V\" failed", &file.name); + } + + if (buf) { + ngx_explicit_memzero(buf, size); + } + + return NGX_CONF_ERROR; +}