changeset 8694: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;
+}