[PATCH 2 of 3] PSK: add configuration directives

Karstens, Nate Nate.Karstens at garmin.com
Thu Jun 1 17:21:18 UTC 2017


# HG changeset patch
# User Nate Karstens <nate.karstens at garmin.com>
# Date 1496332865 18000
#      Thu Jun 01 11:01:05 2017 -0500
# Node ID 7aa7771191d61ef635478460017446bca1f6db55
# Parent  a38066b79d71b6ecb62a9f7618afe2cf3ed8a4f9
PSK: add configuration directives

Adds the directive "ssl_psk_dir" to the ngx_http_ssl_module. This
allows the user to specify a folder that contains the set of PSKs
that can be used to form a connection. Each key in the directory
is read into memory and the file name is saved as the key identity.

Also added the "ssl_psk_hash_max_size" and "ssl_psk_hash_bucket_size"
directives to configure the hash that stores PSK data.

This functionality can be easily tested with the OpenSSL s_client
using the "-psk" and "-psk_identity" options.

Signed-off-by: Nate Karstens <nate.karstens at garmin.com>

diff -r a38066b79d71 -r 7aa7771191d6 contrib/vim/syntax/nginx.vim
--- a/contrib/vim/syntax/nginx.vim      Thu Jun 01 10:55:04 2017 -0500
+++ b/contrib/vim/syntax/nginx.vim      Thu Jun 01 11:01:05 2017 -0500
@@ -551,6 +551,9 @@
 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_path
+syn keyword ngxDirective contained ssl_psk_hash_bucket_size
+syn keyword ngxDirective contained ssl_psk_hash_max_size
 syn keyword ngxDirective contained ssl_session_cache
 syn keyword ngxDirective contained ssl_session_ticket_key
 syn keyword ngxDirective contained ssl_session_tickets
diff -r a38066b79d71 -r 7aa7771191d6 src/event/ngx_event_openssl.c
--- a/src/event/ngx_event_openssl.c     Thu Jun 01 10:55:04 2017 -0500
+++ b/src/event/ngx_event_openssl.c     Thu Jun 01 11:01:05 2017 -0500
@@ -23,6 +23,8 @@
 static int ngx_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store);
 static void ngx_ssl_info_callback(const ngx_ssl_conn_t *ssl_conn, int where,
     int ret);
+static unsigned int ngx_ssl_psk_callback(SSL *ssl, const char *identity,
+    unsigned char *psk, unsigned int max_psk_len);
 static void ngx_ssl_passwords_cleanup(void *data);
 static void ngx_ssl_handshake_handler(ngx_event_t *ev);
 static ngx_int_t ngx_ssl_handle_recv(ngx_connection_t *c, int n);
@@ -114,6 +116,7 @@
 int  ngx_ssl_next_certificate_index;
 int  ngx_ssl_certificate_name_index;
 int  ngx_ssl_stapling_index;
+int  ngx_ssl_psk_index;


 ngx_int_t
@@ -225,6 +228,13 @@
         return NGX_ERROR;
     }

+    ngx_ssl_psk_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+
+    if (ngx_ssl_psk_index == -1) {
+        ngx_ssl_error(NGX_LOG_ALERT, log, 0, "SSL_get_ex_new_index() failed");
+        return NGX_ERROR;
+    }
+
     return NGX_OK;
 }

@@ -345,6 +355,7 @@
     SSL_CTX_set_read_ahead(ssl->ctx, 1);

     SSL_CTX_set_info_callback(ssl->ctx, ngx_ssl_info_callback);
+    SSL_CTX_set_psk_server_callback(ssl->ctx, ngx_ssl_psk_callback);

     return NGX_OK;
 }
@@ -875,6 +886,40 @@
 }


+static unsigned int ngx_ssl_psk_callback(SSL *ssl, const char *identity,
+    unsigned char *psk, unsigned int max_psk_len)
+{
+    SSL_CTX        *ssl_ctx;
+    size_t          identity_len;
+    ngx_hash_t     *psk_hash;
+    ngx_uint_t      key;
+    ngx_str_t      *psk_str;
+
+    ssl_ctx = SSL_get_SSL_CTX(ssl);
+    identity_len = strlen(identity);
+
+    psk_hash = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_psk_index);
+    if (psk_hash == NULL) {
+        return 0;
+    }
+
+    key = ngx_hash_key((u_char *)identity, identity_len);
+
+    psk_str = ngx_hash_find(psk_hash, key, (u_char *)identity, identity_len);
+    if (psk_str == NULL) {
+        return 0;
+    }
+
+    if (psk_str->len > max_psk_len) {
+        return 0;
+    }
+
+    ngx_memcpy(psk, psk_str->data, psk_str->len);
+
+    return psk_str->len;
+}
+
+
 RSA *
 ngx_ssl_rsa512_key_callback(ngx_ssl_conn_t *ssl_conn, int is_export,
     int key_length)
@@ -3137,6 +3182,158 @@
 #endif


+ngx_int_t
+ngx_ssl_psk_path(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *path,
+                 ngx_uint_t psk_hash_max_size, ngx_uint_t psk_hash_bucket_size)
+
+{
+    ngx_err_t               err;
+    ngx_uint_t              level;
+    ngx_dir_t               dir;
+    ngx_file_t              file;
+    ssize_t                 read;
+    ngx_str_t              *psk_str;
+    ngx_hash_keys_arrays_t  psk_keys;
+    ngx_str_t               key;
+    ngx_hash_t             *psk_hash;
+    ngx_hash_init_t         hash;
+
+    if (path->len == 0) {
+        return NGX_OK;
+    }
+
+    psk_keys.pool = cf->pool;
+    psk_keys.temp_pool = cf->temp_pool;
+
+    ngx_hash_keys_array_init(&psk_keys, NGX_HASH_SMALL);
+
+    if (ngx_open_dir(path, &dir) == NGX_ERROR) {
+        err = ngx_errno;
+
+        level = (err == NGX_ENOENT
+                 || err == NGX_ENOTDIR
+                 || err == NGX_ENAMETOOLONG
+                 || err == NGX_EACCES) ? NGX_LOG_ERR : NGX_LOG_CRIT;
+
+        ngx_ssl_error(level, ssl->log, err,
+                      ngx_open_dir_n " \"%s\" failed", path->data);
+
+        return NGX_ERROR;
+    }
+
+    for ( ;; ) {
+        ngx_set_errno(0);
+
+        if (ngx_read_dir(&dir) == NGX_ERROR) {
+            err = ngx_errno;
+
+            if (err == NGX_ENOMOREFILES) {
+                break;
+            }
+
+            ngx_ssl_error(NGX_LOG_CRIT, ssl->log, err,
+                          ngx_read_dir_n " \"%V\" failed", &path);
+            return NGX_ERROR;
+        }
+
+        if (!ngx_de_is_file(&dir)) {
+            continue;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ssl->log, 0,
+                       "loading PSK from file: \"%s\"", ngx_de_name(&dir));
+
+        file.name.len = ngx_de_namelen(&dir) + 2;
+        file.name.data = ngx_pnalloc(cf->pool, file.name.len);
+        if (file.name.data == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_sprintf(file.name.data, "%V/%s%Z", path, ngx_de_name(&dir));
+
+        file.log = ssl->log;
+
+        file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY, 0, 0);
+        if (file.fd == NGX_INVALID_FILE) {
+            err = ngx_errno;
+            ngx_ssl_error(NGX_LOG_CRIT, ssl->log, err,
+                          ngx_open_file_n " \"%s\" failed", file.name.data);
+            return NGX_ERROR;
+        }
+
+        if (ngx_fd_info(file.fd, &file.info) == NGX_FILE_ERROR) {
+            ngx_ssl_error(NGX_LOG_CRIT, ssl->log, ngx_errno,
+                          ngx_fd_info_n " \"%s\" failed", file.name.data);
+            return NGX_ERROR;
+        }
+
+        psk_str = ngx_palloc(cf->pool, sizeof(ngx_str_t));
+        if (psk_str == NULL) {
+            return NGX_ERROR;
+        }
+
+        psk_str->len = file.info.st_size;
+        psk_str->data = ngx_pnalloc(cf->pool, psk_str->len);
+
+        read = ngx_read_file(&file, psk_str->data, psk_str->len, 0);
+        if (read == NGX_ERROR) {
+            ngx_ssl_error(NGX_LOG_CRIT, ssl->log, ngx_errno,
+                          ngx_read_file_n " \"%s\" failed", file.name.data);
+            return NGX_ERROR;
+        }
+
+        if ((size_t)read != psk_str->len) {
+            ngx_ssl_error(NGX_LOG_CRIT, ssl->log, 0,
+                          ngx_read_file_n
+                          " \"%s\" returned only %z bytes instead of %z",
+                          file.name.data, read, psk_str->len);
+            return NGX_ERROR;
+        }
+
+        if (ngx_close_file(file.fd) == NGX_FILE_ERROR) {
+            ngx_ssl_error(NGX_LOG_ALERT, ssl->log, ngx_errno,
+                          ngx_close_file_n " \"%s\" failed", file.name.data);
+        }
+
+        ngx_pfree(cf->pool, file.name.data);
+
+        key.data = ngx_de_name(&dir);
+        key.len = ngx_de_namelen(&dir);
+
+        ngx_hash_add_key(&psk_keys, &key, psk_str, NGX_HASH_READONLY_KEY);
+    }
+
+    if (ngx_close_dir(&dir) == NGX_ERROR) {
+        ngx_log_error(NGX_LOG_CRIT, ssl->log, ngx_errno,
+                      ngx_close_dir_n " \"%s\" failed", path->data);
+    }
+
+    psk_hash = ngx_palloc(cf->pool, sizeof(ngx_hash_t));
+
+    hash.hash = psk_hash;
+    hash.key = ngx_hash_key;
+    hash.max_size = psk_hash_max_size;
+    hash.bucket_size = psk_hash_bucket_size;
+    hash.name = "psk_hash";
+    hash.pool = cf->pool;
+    hash.temp_pool = cf->temp_pool;
+
+    if (ngx_hash_init(&hash, psk_keys.keys.elts, psk_keys.keys.nelts)
+        != NGX_OK)
+    {
+        return NGX_ERROR;
+    }
+
+    if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_psk_index, psk_hash) == 0) {
+        ngx_ssl_error(NGX_LOG_ALERT, ssl->log, 0,
+                      "SSL_CTX_set_ex_data() failed");
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
 void
 ngx_ssl_cleanup_ctx(void *data)
 {
diff -r a38066b79d71 -r 7aa7771191d6 src/event/ngx_event_openssl.h
--- a/src/event/ngx_event_openssl.h     Thu Jun 01 10:55:04 2017 -0500
+++ b/src/event/ngx_event_openssl.h     Thu Jun 01 11:01:05 2017 -0500
@@ -171,6 +171,9 @@
     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,
     ngx_array_t *paths);
+ngx_int_t ngx_ssl_psk_path(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *path,
+                           ngx_uint_t psk_hash_max_size,
+                           ngx_uint_t psk_hash_bucket_size);
 ngx_int_t ngx_ssl_session_cache_init(ngx_shm_zone_t *shm_zone, void *data);
 ngx_int_t ngx_ssl_create_connection(ngx_ssl_t *ssl, ngx_connection_t *c,
     ngx_uint_t flags);
@@ -255,6 +258,7 @@
 extern int  ngx_ssl_next_certificate_index;
 extern int  ngx_ssl_certificate_name_index;
 extern int  ngx_ssl_stapling_index;
+extern int  ngx_ssl_psk_index;


 #endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */
diff -r a38066b79d71 -r 7aa7771191d6 src/http/modules/ngx_http_ssl_module.c
--- a/src/http/modules/ngx_http_ssl_module.c    Thu Jun 01 10:55:04 2017 -0500
+++ b/src/http/modules/ngx_http_ssl_module.c    Thu Jun 01 11:01:05 2017 -0500
@@ -240,6 +240,27 @@
       NGX_HTTP_SRV_CONF_OFFSET,
       offsetof(ngx_http_ssl_srv_conf_t, stapling_verify),
       NULL },
+
+    { ngx_string("ssl_psk_path"),
+      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_path),
+      NULL },
+
+    { ngx_string("ssl_psk_hash_max_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, psk_hash_max_size),
+      NULL },
+
+    { ngx_string("ssl_psk_hash_bucket_size"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_num_slot,
+      NGX_HTTP_SRV_CONF_OFFSET,
+      offsetof(ngx_http_ssl_srv_conf_t, psk_hash_bucket_size),
+      NULL },

       ngx_null_command
 };
@@ -546,6 +567,7 @@
      *     sscf->shm_zone = NULL;
      *     sscf->stapling_file = { 0, NULL };
      *     sscf->stapling_responder = { 0, NULL };
+     *     sscf->psk_path = { 0, NULL };
      */

     sscf->enable = NGX_CONF_UNSET;
@@ -563,6 +585,8 @@
     sscf->session_ticket_keys = NGX_CONF_UNSET_PTR;
     sscf->stapling = NGX_CONF_UNSET;
     sscf->stapling_verify = NGX_CONF_UNSET;
+    sscf->psk_hash_max_size = NGX_CONF_UNSET_UINT;
+    sscf->psk_hash_bucket_size = NGX_CONF_UNSET_UINT;

     return sscf;
 }
@@ -629,6 +653,14 @@
     ngx_conf_merge_str_value(conf->stapling_responder,
                          prev->stapling_responder, "");

+    ngx_conf_merge_str_value(conf->psk_path, prev->psk_path, "");
+    ngx_conf_merge_uint_value(conf->psk_hash_max_size,
+                              prev->psk_hash_max_size, 512);
+    ngx_conf_merge_uint_value(conf->psk_hash_bucket_size,
+                              prev->psk_hash_bucket_size, 64);
+    conf->psk_hash_bucket_size = ngx_align(conf->psk_hash_bucket_size,
+                                           ngx_cacheline_size);
+
     conf->ssl.log = cf->log;

        if (!conf->nocert) {
@@ -815,6 +847,13 @@

     }

+    if (ngx_ssl_psk_path(cf, &conf->ssl, &conf->psk_path,
+                         conf->psk_hash_max_size, conf->psk_hash_bucket_size)
+        != NGX_OK)
+    {
+        return NGX_CONF_ERROR;
+    }
+
     return NGX_CONF_OK;
 }

diff -r a38066b79d71 -r 7aa7771191d6 src/http/modules/ngx_http_ssl_module.h
--- a/src/http/modules/ngx_http_ssl_module.h    Thu Jun 01 10:55:04 2017 -0500
+++ b/src/http/modules/ngx_http_ssl_module.h    Thu Jun 01 11:01:05 2017 -0500
@@ -56,6 +56,10 @@
     ngx_str_t                       stapling_file;
     ngx_str_t                       stapling_responder;

+    ngx_str_t                       psk_path;
+    ngx_uint_t                      psk_hash_max_size;
+    ngx_uint_t                      psk_hash_bucket_size;
+
     u_char                         *file;
     ngx_uint_t                      line;
 } ngx_http_ssl_srv_conf_t;

________________________________

CONFIDENTIALITY NOTICE: This email and any attachments are for the sole use of the intended recipient(s) and contain information that may be Garmin confidential and/or Garmin legally privileged. If you have received this email in error, please notify the sender by reply email and delete the message. Any disclosure, copying, distribution or use of this communication (including attachments) by someone other than the intended recipient is prohibited. Thank you.


More information about the nginx-devel mailing list