From 99662f84a1d25e0ab466e9a53591aaa25f9c33b4 Mon Sep 17 00:00:00 2001 From: Radu Viorel Cosnita Date: Wed, 28 Oct 2020 17:27:41 +0200 Subject: [PATCH 1/3] Variables are not supported for configuring the module. Closes #22. --- src/ngx_http_encrypted_session_module.c | 42 ++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/src/ngx_http_encrypted_session_module.c b/src/ngx_http_encrypted_session_module.c index a2206be..5e72cab 100644 --- a/src/ngx_http_encrypted_session_module.c +++ b/src/ngx_http_encrypted_session_module.c @@ -150,6 +150,26 @@ ngx_module_t ngx_http_encrypted_session_module = { NGX_MODULE_V1_PADDING }; +static ngx_str_t ngx_http_get_variable_by_name(ngx_http_request_t *r, + unsigned char *name, ngx_http_encrypted_session_conf_t *conf) +{ + ngx_http_variable_value_t *v; + ngx_str_t name_str; + name_str.data = name; + name_str.len = strlen((const char *)name); + + ngx_uint_t key = ngx_hash_strlow(name, name, name_str.len); + v = ngx_http_get_variable(r, &name_str, key); + + if (v->not_found) { + return name_str; + } + + ngx_str_t var_value; + var_value.len = v->len; + var_value.data = v->data; + return var_value; +} static ngx_int_t ngx_http_set_encode_encrypted_session(ngx_http_request_t *r, @@ -176,9 +196,11 @@ ngx_http_set_encode_encrypted_session(ngx_http_request_t *r, ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "encrypted_session: expires=%T", conf->expires); + ngx_str_t iv = ngx_http_get_variable_by_name(r, conf->iv, conf); + ngx_str_t key = ngx_http_get_variable_by_name(r, conf->key, conf); + rc = ngx_http_encrypted_session_aes_mac_encrypt(emcf, r->pool, - r->connection->log, conf->iv, ngx_http_encrypted_session_iv_length, - conf->key, ngx_http_encrypted_session_key_length, + r->connection->log, iv.data, iv.len, key.data, key.len, v->data, v->len, (ngx_uint_t) conf->expires, &dst, &len); if (rc != NGX_OK) { @@ -218,9 +240,11 @@ ngx_http_set_decode_encrypted_session(ngx_http_request_t *r, return NGX_ERROR; } + ngx_str_t iv = ngx_http_get_variable_by_name(r, conf->iv, conf); + ngx_str_t key = ngx_http_get_variable_by_name(r, conf->key, conf); + rc = ngx_http_encrypted_session_aes_mac_decrypt(emcf, r->pool, - r->connection->log, conf->iv, ngx_http_encrypted_session_iv_length, - conf->key, ngx_http_encrypted_session_key_length, + r->connection->log, iv.data, iv.len, key.data, key.len, v->data, v->len, &dst, &len); if (rc != NGX_OK) { @@ -248,6 +272,11 @@ ngx_http_encrypted_session_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) value = cf->args->elts; + if (value[1].len > 1 && value[1].data[0] == '$') { + llcf->key = &(value[1].data[1]); + return NGX_CONF_OK; + } + if (value[1].len != ngx_http_encrypted_session_key_length) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "encrypted_session_key: the key must be of %d " @@ -276,6 +305,11 @@ ngx_http_encrypted_session_iv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) value = cf->args->elts; + if (value[1].len > 1 && value[1].data[0] == '$') { + llcf->iv = &(value[1].data[1]); + return NGX_CONF_OK; + } + if (value[1].len > ngx_http_encrypted_session_iv_length) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "encrypted_session_iv: the init vector must NOT " From 654a175ca92367c95970b69cf8c293b102f7aa7f Mon Sep 17 00:00:00 2001 From: Radu Viorel Cosnita Date: Wed, 28 Oct 2020 20:49:23 +0200 Subject: [PATCH 2/3] An IV should be generated for each encryption We now have the ability to decide if the IV is communicated to the client in a non forgeable manner or we only keep it on the server side. Closes #2 --- src/ngx_http_encrypted_session_cipher.c | 19 ++ src/ngx_http_encrypted_session_cipher.h | 5 + src/ngx_http_encrypted_session_module.c | 262 ++++++++++++++++++++++-- 3 files changed, 266 insertions(+), 20 deletions(-) diff --git a/src/ngx_http_encrypted_session_cipher.c b/src/ngx_http_encrypted_session_cipher.c index fe968e7..598e6d9 100644 --- a/src/ngx_http_encrypted_session_cipher.c +++ b/src/ngx_http_encrypted_session_cipher.c @@ -12,6 +12,7 @@ #include "ngx_http_encrypted_session_cipher.h" #include +#include #include #include @@ -291,3 +292,21 @@ ngx_http_encrypted_session_htonll(uint64_t n) + htonl((unsigned long) (n >> 32)); #endif } + +unsigned char* +ngx_http_encrypted_session_hmac(ngx_pool_t *pool, + const u_char *key, size_t key_len, + const u_char *data, size_t data_len, u_char **dst, size_t *dst_len) +{ + u_char *result = NULL; + u_char *input = ngx_pcalloc(pool, data_len + 1); + ngx_memcpy(input, data, data_len); + + unsigned int len; + result = HMAC(EVP_sha256(), key, key_len, input, data_len, result, &len); + *dst_len = len; + *dst = (u_char*)ngx_pcalloc(pool, len + 1); + ngx_memcpy(*dst, result, len); + + return *dst; +} \ No newline at end of file diff --git a/src/ngx_http_encrypted_session_cipher.h b/src/ngx_http_encrypted_session_cipher.h index 2ac4718..0cac7a1 100644 --- a/src/ngx_http_encrypted_session_cipher.h +++ b/src/ngx_http_encrypted_session_cipher.h @@ -5,6 +5,7 @@ #include #include #include +#include typedef int (*cipher_ctx_reset_handle) (EVP_CIPHER_CTX *ctx); @@ -34,6 +35,10 @@ ngx_int_t ngx_http_encrypted_session_aes_mac_decrypt( size_t key_len, const u_char *in, size_t in_len, u_char **dst, size_t *dst_len); +unsigned char* ngx_http_encrypted_session_hmac( + ngx_pool_t *pool, + const u_char *key, size_t key_len, + const u_char *data, size_t data_len, u_char **dst, size_t *dst_len); #endif /* NGX_HTTP_ENCRYPTED_SESSION_CIPHER_H */ diff --git a/src/ngx_http_encrypted_session_module.c b/src/ngx_http_encrypted_session_module.c index 5e72cab..8126316 100644 --- a/src/ngx_http_encrypted_session_module.c +++ b/src/ngx_http_encrypted_session_module.c @@ -10,21 +10,25 @@ #include "ddebug.h" #include +#include #include "ngx_http_encrypted_session_cipher.h" #define ngx_http_encrypted_session_default_iv (u_char *) "deadbeefdeadbeef" #define ngx_http_encrypted_session_default_expires 86400 +const size_t IV_LENGTH = 16; +const size_t SIGNATURE_LENGTH = 32; typedef struct { u_char *key; + size_t key_len; u_char *iv; + size_t iv_len; time_t expires; - + ngx_flag_t iv_in_content; } ngx_http_encrypted_session_conf_t; - static ngx_int_t ngx_http_set_encode_encrypted_session(ngx_http_request_t *r, ngx_str_t *res, ngx_http_variable_value_t *v); @@ -42,6 +46,8 @@ static char *ngx_http_encrypted_session_iv(ngx_conf_t *cf, ngx_command_t *cmd, static char *ngx_http_encrypted_session_expires(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_http_encrypted_iv_in_content(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); static ngx_int_t ngx_http_encrypted_session_init(ngx_conf_t *cf); static void *ngx_http_encrypted_session_create_main_conf(ngx_conf_t *cf); @@ -53,7 +59,6 @@ static void *ngx_http_encrypted_session_create_conf(ngx_conf_t *cf); static char *ngx_http_encrypted_session_merge_conf(ngx_conf_t *cf, void *parent, void *child); - static ndk_set_var_t ngx_http_set_encode_encrypted_session_filter = { NDK_SET_VAR_VALUE, (void *) ngx_http_set_encode_encrypted_session, @@ -115,7 +120,14 @@ static ngx_command_t ngx_http_encrypted_session_commands[] = { 0, &ngx_http_set_decode_encrypted_session_filter }, - + { ngx_string("encrypted_session_iv_in_content"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF + |NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_NOARGS, + ngx_http_encrypted_iv_in_content, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL + }, ngx_null_command }; @@ -151,14 +163,14 @@ ngx_module_t ngx_http_encrypted_session_module = { }; static ngx_str_t ngx_http_get_variable_by_name(ngx_http_request_t *r, - unsigned char *name, ngx_http_encrypted_session_conf_t *conf) + unsigned char *name, size_t name_len, ngx_http_encrypted_session_conf_t *conf) { ngx_http_variable_value_t *v; ngx_str_t name_str; name_str.data = name; - name_str.len = strlen((const char *)name); + name_str.len = name_len; - ngx_uint_t key = ngx_hash_strlow(name, name, name_str.len); + ngx_uint_t key = ngx_hash_strlow(name, name, name_len); v = ngx_http_get_variable(r, &name_str, key); if (v->not_found) { @@ -171,6 +183,88 @@ static ngx_str_t ngx_http_get_variable_by_name(ngx_http_request_t *r, return var_value; } +static u_char* +ngx_http_encrypted_session_build_payload(ngx_http_request_t *r, + ngx_str_t *content, ngx_str_t *iv, size_t *len) +{ + size_t new_len = iv->len + content->len; + u_char *data = (u_char *)ngx_pcalloc(r->pool, new_len + 1); + ngx_memcpy(data, iv->data, iv->len); + ngx_memcpy(data + iv->len, content->data, content->len); + *len = new_len; + + return data; +} + +static ngx_str_t* +ngx_http_session_encrypted_compute_hmac(ngx_http_request_t *r, + ngx_str_t *key, ngx_str_t *content) +{ + size_t signature_len; + u_char* signature; + + ngx_http_encrypted_session_hmac(r->pool, key->data, key->len, + content->data, content->len, + &signature, &signature_len); + + ngx_str_t *result = (ngx_str_t*)ngx_pcalloc(r->pool, sizeof(ngx_str_t)); + result->len = signature_len; + result->data = (u_char*)ngx_pcalloc(r->pool, signature_len + 1); + result->data = signature; + return result; +} + +static ngx_str_t* +ngx_http_session_generate_signature(ngx_http_request_t *r, + ngx_str_t *iv, ngx_str_t *key, ngx_str_t *content) +{ + size_t signature_content_len = iv->len + content->len; + u_char* signature_content = (u_char*)ngx_pcalloc(r->pool, signature_content_len + 1); + ngx_memcpy(signature_content, iv->data, iv->len); + ngx_memcpy(signature_content + iv->len, content->data, content->len); + + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "encrypted_session: signature content len=%d", + signature_content_len); + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "encrypted_session: signature content=%s", + signature_content); + + ngx_str_t signature_input; + signature_input.len = signature_content_len; + signature_input.data = (u_char*)signature_content; + ngx_str_t *signature = ngx_http_session_encrypted_compute_hmac(r, key, + &signature_input); + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "encrypted_session: signature=%s", signature->data); + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "encrypted_session: signature len=%d", signature->len); + + return signature; +} + +static ngx_str_t* +ngx_http_session_generate_response_with_iv(ngx_http_request_t *r, + ngx_str_t *iv, ngx_str_t *key, ngx_str_t *content) +{ + ngx_str_t *signature = ngx_http_session_generate_signature(r, iv, key, content); + + size_t new_len = iv->len + content->len + signature->len; + u_char *new_content = (u_char*)ngx_pcalloc(r->pool, new_len + 1); + ngx_memcpy(new_content, iv->data, iv->len); + ngx_memcpy(new_content + iv->len, signature->data, signature->len); + ngx_memcpy(new_content + iv->len + signature->len, content->data, content->len); + + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "encrypted_session: encrypted data len=%d", content->len); + + ngx_str_t *payload = (ngx_str_t*)ngx_palloc(r->pool, sizeof(ngx_str_t)); + payload->len = new_len; + payload->data = (u_char*)new_content; + + return payload; +} + static ngx_int_t ngx_http_set_encode_encrypted_session(ngx_http_request_t *r, ngx_str_t *res, ngx_http_variable_value_t *v) @@ -196,23 +290,61 @@ ngx_http_set_encode_encrypted_session(ngx_http_request_t *r, ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "encrypted_session: expires=%T", conf->expires); - ngx_str_t iv = ngx_http_get_variable_by_name(r, conf->iv, conf); - ngx_str_t key = ngx_http_get_variable_by_name(r, conf->key, conf); + ngx_str_t iv = ngx_http_get_variable_by_name(r, conf->iv, conf->iv_len, + conf); + ngx_str_t key = ngx_http_get_variable_by_name(r, conf->key, conf->key_len, + conf); + + ngx_str_t content; + content.data = (u_char*)ngx_pcalloc(r->pool, v->len + 1); + ngx_memcpy(content.data, v->data, v->len); + content.len = v->len; + + if (conf->iv_in_content) { + size_t new_len; + content.data = ngx_http_encrypted_session_build_payload(r, + &content, &iv, &new_len); + content.len = new_len; + + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "encrypted_session: content to encrypt len=%d", + content.len); + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "encrypted_session: content to encrypt=%s", + content.data); + } rc = ngx_http_encrypted_session_aes_mac_encrypt(emcf, r->pool, r->connection->log, iv.data, iv.len, key.data, key.len, - v->data, v->len, (ngx_uint_t) conf->expires, &dst, &len); + content.data, content.len, (ngx_uint_t) conf->expires, &dst, &len); if (rc != NGX_OK) { - dst = NULL; - len = 0; + res->data = NULL; + res->len = 0; ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "encrypted_session: failed to encrypt"); + return NGX_OK; } - res->data = dst; - res->len = len; + if (conf->iv_in_content) { + ngx_str_t encrypted_content; + encrypted_content.len = len; + encrypted_content.data = dst; + + ngx_str_t *result = ngx_http_session_generate_response_with_iv(r, &iv, + &key, &encrypted_content); + res->data = result->data; + res->len = result->len; + + } else { + res->data = dst; + res->len = len; + } + + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "encrypted_session: full response len=%d", + res->len); return NGX_OK; } @@ -240,16 +372,86 @@ ngx_http_set_decode_encrypted_session(ngx_http_request_t *r, return NGX_ERROR; } - ngx_str_t iv = ngx_http_get_variable_by_name(r, conf->iv, conf); - ngx_str_t key = ngx_http_get_variable_by_name(r, conf->key, conf); + ngx_str_t key = ngx_http_get_variable_by_name(r, conf->key, conf->key_len, + conf); + + ngx_str_t iv; + ngx_str_t content; + content.data = v->data; + content.len = v->len; + + if (!conf->iv_in_content) + { + iv = ngx_http_get_variable_by_name(r, conf->iv, conf->iv_len, conf); + } + else + { + if (content.len < IV_LENGTH + SIGNATURE_LENGTH + 1) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "encrypted_session: input to decrypt is too short."); + res->data = NULL; + res->len = 0; + + return NGX_OK; + } + + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "encrypted_session: input to decrypt len=%d", + content.len); + iv.len = IV_LENGTH; + iv.data = (u_char*)ngx_pcalloc(r->pool, iv.len + 1); + ngx_memcpy(iv.data, content.data, iv.len); + + u_char* signature = (u_char*)ngx_pcalloc(r->pool, SIGNATURE_LENGTH + 1); + ngx_memcpy(signature, content.data + iv.len, SIGNATURE_LENGTH); + + ngx_str_t encrypted_content; + encrypted_content.len = content.len - iv.len - SIGNATURE_LENGTH; + encrypted_content.data = (u_char*)ngx_pcalloc(r->pool, encrypted_content.len + 1); + ngx_memcpy(encrypted_content.data, v->data + iv.len + SIGNATURE_LENGTH, encrypted_content.len); + + content.data = encrypted_content.data; + content.len = encrypted_content.len; + + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "encrypted_session: data len=%d", content.len); + + ngx_str_t *computed_signature = ngx_http_session_generate_signature(r, + &iv, &key, &encrypted_content); + if (SIGNATURE_LENGTH != computed_signature->len || + ngx_memcmp(computed_signature->data, signature, SIGNATURE_LENGTH) != 0) + { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "encrypted_session: signatures do not match"); + res->data = NULL; + res->len = 0; + + return NGX_OK; + } + } rc = ngx_http_encrypted_session_aes_mac_decrypt(emcf, r->pool, r->connection->log, iv.data, iv.len, key.data, key.len, - v->data, v->len, &dst, &len); + content.data, content.len, &dst, &len); if (rc != NGX_OK) { - dst = NULL; - len = 0; + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "encrypted_session: failed to decrypt"); + res->data = NULL; + res->len = 0; + + return NGX_OK; + } + + if (conf->iv_in_content) { + size_t payload_len = len - iv.len; + u_char *result = ngx_pcalloc(r->pool, payload_len + 1); + ngx_memcpy(result, dst + iv.len, payload_len); + dst = result; + len = payload_len; + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, + "encrypted_session: decrypted content=%s", + dst); } res->data = dst; @@ -273,7 +475,9 @@ ngx_http_encrypted_session_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) value = cf->args->elts; if (value[1].len > 1 && value[1].data[0] == '$') { - llcf->key = &(value[1].data[1]); + llcf->key_len = value[1].len - 1; + llcf->key = (u_char*)ngx_pcalloc(cf->pool, llcf->key_len); + ngx_memcpy(llcf->key, value[1].data + 1, llcf->key_len); return NGX_CONF_OK; } @@ -287,6 +491,7 @@ ngx_http_encrypted_session_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } llcf->key = value[1].data; + llcf->key_len = value[1].len; return NGX_CONF_OK; } @@ -307,6 +512,7 @@ ngx_http_encrypted_session_iv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (value[1].len > 1 && value[1].data[0] == '$') { llcf->iv = &(value[1].data[1]); + llcf->iv_len = value[1].len - 1; return NGX_CONF_OK; } @@ -320,6 +526,7 @@ ngx_http_encrypted_session_iv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } llcf->iv = ngx_pcalloc(cf->pool, ngx_http_encrypted_session_iv_length); + llcf->iv_len = ngx_http_encrypted_session_iv_length; if (llcf->iv == NULL) { return NGX_CONF_ERROR; @@ -360,6 +567,13 @@ ngx_http_encrypted_session_expires(ngx_conf_t *cf, ngx_command_t *cmd, return NGX_CONF_OK; } +static char *ngx_http_encrypted_iv_in_content(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf) +{ + ngx_http_encrypted_session_conf_t *llcf = conf; + llcf->iv_in_content = 1; + return NGX_CONF_OK; +} static void ngx_http_encrypted_session_free_cipher_ctx(void *data) @@ -435,8 +649,11 @@ ngx_http_encrypted_session_create_conf(ngx_conf_t *cf) } conf->key = NGX_CONF_UNSET_PTR; + conf->key_len = NGX_CONF_UNSET; conf->iv = NGX_CONF_UNSET_PTR; + conf->iv_len = NGX_CONF_UNSET; conf->expires = NGX_CONF_UNSET; + conf->iv_in_content = NGX_CONF_UNSET; return conf; } @@ -449,12 +666,17 @@ ngx_http_encrypted_session_merge_conf(ngx_conf_t *cf, void *parent, void *child) ngx_http_encrypted_session_conf_t *conf = child; ngx_conf_merge_ptr_value(conf->key, prev->key, NULL); + ngx_conf_merge_size_value(conf->key_len, prev->key_len, + (size_t)ngx_http_encrypted_session_key_length); ngx_conf_merge_ptr_value(conf->iv, prev->iv, ngx_http_encrypted_session_default_iv); + ngx_conf_merge_size_value(conf->iv_len, prev->iv_len, + (size_t)ngx_http_encrypted_session_iv_length); ngx_conf_merge_value(conf->expires, prev->expires, ngx_http_encrypted_session_default_expires); + ngx_conf_merge_value(conf->iv_in_content, prev->iv_in_content, 0); return NGX_CONF_OK; } From dd7470a8d0885b38f730d033ec053df2af604a1b Mon Sep 17 00:00:00 2001 From: Radu Viorel Cosnita Date: Tue, 3 Nov 2020 15:29:39 +0200 Subject: [PATCH 3/3] The module should support AES gcm mode. Closes #25 --- README.md | 13 +- src/ngx_http_encrypted_session_cipher.c | 50 +++++- src/ngx_http_encrypted_session_cipher.h | 17 ++- src/ngx_http_encrypted_session_module.c | 192 +++++++++++++++++++++--- 4 files changed, 240 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 890222f..3b54a96 100644 --- a/README.md +++ b/README.md @@ -38,15 +38,24 @@ Synopsis ======== ```nginx +set $key "abcdefghijklmnopqrstuvwxyz123456"; +set $iv "1234567812345678"; +set $ses_expires "3600"; + # key must be of 32 bytes long +encrypted_session_key $key; + encrypted_session_key "abcdefghijklmnopqrstuvwxyz123456"; # iv must not be longer than 16 bytes # default: "deadbeefdeadbeef" (w/o quotes) -encrypted_session_iv "1234567812345678"; +encrypted_session_iv $iv; + +encrypted_session_iv_in_content; # enable this only if you rotate IV for each encryption +encrypted_session_mode gcm; # cbc is the default mode. # default: 1d (1 day) -encrypted_session_expires 3600; # in sec +encrypted_session_expires $ses_expires; # in sec location /encrypt { set $raw 'text to encrypted'; # from the ngx_rewrite module diff --git a/src/ngx_http_encrypted_session_cipher.c b/src/ngx_http_encrypted_session_cipher.c index 598e6d9..269ea84 100644 --- a/src/ngx_http_encrypted_session_cipher.c +++ b/src/ngx_http_encrypted_session_cipher.c @@ -20,13 +20,28 @@ static uint64_t ngx_http_encrypted_session_ntohll(uint64_t n); static uint64_t ngx_http_encrypted_session_htonll(uint64_t n); +const EVP_CIPHER* +ngx_http_encrypted_session_get_cipher(enum ngx_http_encrypted_session_mode mode) +{ + if (mode == ngx_http_encrypted_session_mode_cbc) + { + return EVP_aes_256_cbc(); + } + else if (mode == ngx_http_encrypted_session_mode_gcm) + { + return EVP_aes_256_gcm(); + } + + return NULL; +} ngx_int_t ngx_http_encrypted_session_aes_mac_encrypt( ngx_http_encrypted_session_main_conf_t *emcf, ngx_pool_t *pool, ngx_log_t *log, const u_char *iv, size_t iv_len, const u_char *key, size_t key_len, const u_char *in, size_t in_len, ngx_uint_t expires, - u_char **dst, size_t *dst_len) + enum ngx_http_encrypted_session_mode mode, + u_char **dst, size_t *dst_len, u_char **tag) { const EVP_CIPHER *cipher; u_char *p, *data; @@ -50,7 +65,10 @@ ngx_http_encrypted_session_aes_mac_encrypt( } } - cipher = EVP_aes_256_cbc(); + cipher = ngx_http_encrypted_session_get_cipher(mode); + if (!cipher) { + goto evp_error; + } block_size = EVP_CIPHER_block_size(cipher); @@ -107,6 +125,15 @@ ngx_http_encrypted_session_aes_mac_encrypt( p += len; ret = EVP_EncryptFinal(emcf->session_ctx, p, &len); + if (!ret) { + goto evp_error; + } + + if (mode == ngx_http_encrypted_session_mode_gcm) { + *tag = (u_char*)ngx_pcalloc(pool, ngx_http_encrypted_session_aes_tag_size); + ret = EVP_CIPHER_CTX_ctrl(emcf->session_ctx, EVP_CTRL_GCM_GET_TAG, + ngx_http_encrypted_session_aes_tag_size, *tag); + } emcf->reset_cipher_ctx(emcf->session_ctx); @@ -139,8 +166,10 @@ ngx_int_t ngx_http_encrypted_session_aes_mac_decrypt( ngx_http_encrypted_session_main_conf_t *emcf, ngx_pool_t *pool, ngx_log_t *log, const u_char *iv, size_t iv_len, const u_char *key, - size_t key_len, const u_char *in, size_t in_len, u_char **dst, - size_t *dst_len) + size_t key_len, const u_char *in, size_t in_len, + enum ngx_http_encrypted_session_mode mode, + u_char *tag, + u_char **dst, size_t *dst_len) { const EVP_CIPHER *cipher; int ret; @@ -171,7 +200,10 @@ ngx_http_encrypted_session_aes_mac_decrypt( } } - cipher = EVP_aes_256_cbc(); + cipher = ngx_http_encrypted_session_get_cipher(mode); + if (!cipher) { + goto evp_error; + } ret = EVP_DecryptInit(emcf->session_ctx, cipher, key, iv); if (!ret) { @@ -200,6 +232,14 @@ ngx_http_encrypted_session_aes_mac_decrypt( p += len; + if (mode == ngx_http_encrypted_session_mode_gcm) { + ret = EVP_CIPHER_CTX_ctrl(emcf->session_ctx, EVP_CTRL_GCM_SET_TAG, + ngx_http_encrypted_session_aes_tag_size, tag); + if (!ret) { + goto evp_error; + } + } + ret = EVP_DecryptFinal(emcf->session_ctx, p, &len); emcf->reset_cipher_ctx(emcf->session_ctx); diff --git a/src/ngx_http_encrypted_session_cipher.h b/src/ngx_http_encrypted_session_cipher.h index 0cac7a1..4f9f176 100644 --- a/src/ngx_http_encrypted_session_cipher.h +++ b/src/ngx_http_encrypted_session_cipher.h @@ -19,21 +19,30 @@ typedef struct { enum { ngx_http_encrypted_session_key_length = 256 / 8, - ngx_http_encrypted_session_iv_length = EVP_MAX_IV_LENGTH + ngx_http_encrypted_session_iv_length = EVP_MAX_IV_LENGTH, + ngx_http_encrypted_session_aes_tag_size = 16 }; +enum ngx_http_encrypted_session_mode { + ngx_http_encrypted_session_mode_unknown = 0, // unknown / unset value. + ngx_http_encrypted_session_mode_cbc = 1, // equivalent of setting cbc string in config or nothing at all. + ngx_http_encrypted_session_mode_gcm = 2 // equivalent of explicitly setting gcm in nginx config. +}; ngx_int_t ngx_http_encrypted_session_aes_mac_encrypt( ngx_http_encrypted_session_main_conf_t *emcf, ngx_pool_t *pool, ngx_log_t *log, const u_char *iv, size_t iv_len, const u_char *key, size_t key_len, const u_char *in, size_t in_len, - ngx_uint_t expires, u_char **dst, size_t *dst_len); + ngx_uint_t expires, enum ngx_http_encrypted_session_mode mode, + u_char **dst, size_t *dst_len, u_char **tag); ngx_int_t ngx_http_encrypted_session_aes_mac_decrypt( ngx_http_encrypted_session_main_conf_t *emcf, ngx_pool_t *pool, ngx_log_t *log, const u_char *iv, size_t iv_len, const u_char *key, - size_t key_len, const u_char *in, size_t in_len, u_char **dst, - size_t *dst_len); + size_t key_len, const u_char *in, size_t in_len, + enum ngx_http_encrypted_session_mode mode, + u_char *tag, + u_char **dst, size_t *dst_len); unsigned char* ngx_http_encrypted_session_hmac( ngx_pool_t *pool, diff --git a/src/ngx_http_encrypted_session_module.c b/src/ngx_http_encrypted_session_module.c index 8126316..348306b 100644 --- a/src/ngx_http_encrypted_session_module.c +++ b/src/ngx_http_encrypted_session_module.c @@ -21,14 +21,21 @@ const size_t IV_LENGTH = 16; const size_t SIGNATURE_LENGTH = 32; typedef struct { - u_char *key; - size_t key_len; - u_char *iv; - size_t iv_len; - time_t expires; - ngx_flag_t iv_in_content; + u_char *key; + size_t key_len; + u_char *iv; + size_t iv_len; + time_t expires; + u_char *expires_var; + size_t expires_var_len; + ngx_flag_t iv_in_content; + enum ngx_http_encrypted_session_mode encryption_mode; } ngx_http_encrypted_session_conf_t; +static time_t ngx_http_encrypted_session_get_expires_from_conf( + ngx_http_request_t *r, + ngx_http_encrypted_session_conf_t *conf); + static ngx_int_t ngx_http_set_encode_encrypted_session(ngx_http_request_t *r, ngx_str_t *res, ngx_http_variable_value_t *v); @@ -43,6 +50,9 @@ static char *ngx_http_encrypted_session_key(ngx_conf_t *cf, ngx_command_t *cmd, static char *ngx_http_encrypted_session_iv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_http_encrypted_session_mode_set(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf); + static char *ngx_http_encrypted_session_expires(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); @@ -93,6 +103,15 @@ static ngx_command_t ngx_http_encrypted_session_commands[] = { 0, NULL }, + { + ngx_string("encrypted_session_mode"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF + |NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, + ngx_http_encrypted_session_mode_set, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL + }, { ngx_string("encrypted_session_expires"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF @@ -183,6 +202,29 @@ static ngx_str_t ngx_http_get_variable_by_name(ngx_http_request_t *r, return var_value; } +static time_t ngx_http_encrypted_session_parse_expires(ngx_str_t* value) +{ + return ngx_parse_time(value, 1); +} + +static time_t ngx_http_encrypted_session_get_expires_from_conf( + ngx_http_request_t *r, + ngx_http_encrypted_session_conf_t *conf) +{ + if (!conf->expires_var) { + return conf->expires; + } + + ngx_str_t expires = ngx_http_get_variable_by_name( + r, conf->expires_var, conf->expires_var_len, conf); + time_t expires_val = ngx_http_encrypted_session_parse_expires(&expires); + if (expires_val == NGX_ERROR) { + dd("expires %s has an invalid value.", conf->expires_var); + } + + return expires_val; +} + static u_char* ngx_http_encrypted_session_build_payload(ngx_http_request_t *r, ngx_str_t *content, ngx_str_t *iv, size_t *len) @@ -216,12 +258,28 @@ ngx_http_session_encrypted_compute_hmac(ngx_http_request_t *r, static ngx_str_t* ngx_http_session_generate_signature(ngx_http_request_t *r, - ngx_str_t *iv, ngx_str_t *key, ngx_str_t *content) + ngx_str_t *iv, ngx_str_t *key, ngx_str_t *content, + ngx_str_t *tag, enum ngx_http_encrypted_session_mode mode) { size_t signature_content_len = iv->len + content->len; + if (mode == ngx_http_encrypted_session_mode_gcm) + { + signature_content_len += tag->len; + } + u_char* signature_content = (u_char*)ngx_pcalloc(r->pool, signature_content_len + 1); ngx_memcpy(signature_content, iv->data, iv->len); - ngx_memcpy(signature_content + iv->len, content->data, content->len); + + if (mode == ngx_http_encrypted_session_mode_gcm) + { + ngx_memcpy(signature_content + iv->len, tag->data, tag->len); + ngx_memcpy(signature_content + iv->len + tag->len, + content->data, content->len); + } + else + { + ngx_memcpy(signature_content + iv->len, content->data, content->len); + } ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "encrypted_session: signature content len=%d", @@ -245,15 +303,32 @@ ngx_http_session_generate_signature(ngx_http_request_t *r, static ngx_str_t* ngx_http_session_generate_response_with_iv(ngx_http_request_t *r, - ngx_str_t *iv, ngx_str_t *key, ngx_str_t *content) + ngx_str_t *iv, ngx_str_t *key, ngx_str_t *content, + ngx_str_t *tag, enum ngx_http_encrypted_session_mode mode) { - ngx_str_t *signature = ngx_http_session_generate_signature(r, iv, key, content); + ngx_str_t *signature = ngx_http_session_generate_signature(r, iv, key, + content, tag, mode); + + size_t new_len = iv->len + signature->len + content->len; + + if (mode == ngx_http_encrypted_session_mode_gcm) + { + new_len += tag->len; + } - size_t new_len = iv->len + content->len + signature->len; u_char *new_content = (u_char*)ngx_pcalloc(r->pool, new_len + 1); ngx_memcpy(new_content, iv->data, iv->len); ngx_memcpy(new_content + iv->len, signature->data, signature->len); - ngx_memcpy(new_content + iv->len + signature->len, content->data, content->len); + + if (mode == ngx_http_encrypted_session_mode_gcm) + { + ngx_memcpy(new_content + iv->len + signature->len, tag->data, tag->len); + ngx_memcpy(new_content + iv->len + signature->len + tag->len, content->data, content->len); + } + else + { + ngx_memcpy(new_content + iv->len + signature->len, content->data, content->len); + } ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "encrypted_session: encrypted data len=%d", content->len); @@ -287,8 +362,17 @@ ngx_http_set_encode_encrypted_session(ngx_http_request_t *r, return NGX_ERROR; } + time_t expires_val = ngx_http_encrypted_session_get_expires_from_conf(r, + conf); + if (expires_val == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "encrypted_session: invalid session expires numeric value " + "defined by the encrypted_session_expires directive"); + return NGX_ERROR; + } + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "encrypted_session: expires=%T", conf->expires); + "encrypted_session: expires=%T", expires_val); ngx_str_t iv = ngx_http_get_variable_by_name(r, conf->iv, conf->iv_len, conf); @@ -314,9 +398,12 @@ ngx_http_set_encode_encrypted_session(ngx_http_request_t *r, content.data); } + u_char *tag; rc = ngx_http_encrypted_session_aes_mac_encrypt(emcf, r->pool, r->connection->log, iv.data, iv.len, key.data, key.len, - content.data, content.len, (ngx_uint_t) conf->expires, &dst, &len); + content.data, content.len, + (ngx_uint_t) expires_val, + conf->encryption_mode, &dst, &len, &tag); if (rc != NGX_OK) { res->data = NULL; @@ -332,8 +419,12 @@ ngx_http_set_encode_encrypted_session(ngx_http_request_t *r, encrypted_content.len = len; encrypted_content.data = dst; + ngx_str_t tag_content; + tag_content.len = ngx_http_encrypted_session_aes_tag_size; + tag_content.data = tag; + ngx_str_t *result = ngx_http_session_generate_response_with_iv(r, &iv, - &key, &encrypted_content); + &key, &encrypted_content, &tag_content, conf->encryption_mode); res->data = result->data; res->len = result->len; @@ -377,6 +468,8 @@ ngx_http_set_decode_encrypted_session(ngx_http_request_t *r, ngx_str_t iv; ngx_str_t content; + ngx_str_t tag; + content.data = v->data; content.len = v->len; @@ -405,10 +498,28 @@ ngx_http_set_decode_encrypted_session(ngx_http_request_t *r, u_char* signature = (u_char*)ngx_pcalloc(r->pool, SIGNATURE_LENGTH + 1); ngx_memcpy(signature, content.data + iv.len, SIGNATURE_LENGTH); + if (conf->encryption_mode == ngx_http_encrypted_session_mode_gcm) + { + tag.len = ngx_http_encrypted_session_aes_tag_size; + tag.data = (u_char*)ngx_pcalloc(r->pool, tag.len); + ngx_memcpy(tag.data, content.data + iv.len + SIGNATURE_LENGTH, tag.len); + } + ngx_str_t encrypted_content; - encrypted_content.len = content.len - iv.len - SIGNATURE_LENGTH; - encrypted_content.data = (u_char*)ngx_pcalloc(r->pool, encrypted_content.len + 1); - ngx_memcpy(encrypted_content.data, v->data + iv.len + SIGNATURE_LENGTH, encrypted_content.len); + if (conf->encryption_mode == ngx_http_encrypted_session_mode_gcm) + { + encrypted_content.len = content.len - iv.len - SIGNATURE_LENGTH - tag.len; + encrypted_content.data = (u_char*)ngx_pcalloc(r->pool, encrypted_content.len + 1); + ngx_memcpy(encrypted_content.data, + v->data + iv.len + SIGNATURE_LENGTH + tag.len, + encrypted_content.len); + } + else + { + encrypted_content.len = content.len - iv.len - SIGNATURE_LENGTH; + encrypted_content.data = (u_char*)ngx_pcalloc(r->pool, encrypted_content.len + 1); + ngx_memcpy(encrypted_content.data, v->data + iv.len + SIGNATURE_LENGTH, encrypted_content.len); + } content.data = encrypted_content.data; content.len = encrypted_content.len; @@ -417,7 +528,7 @@ ngx_http_set_decode_encrypted_session(ngx_http_request_t *r, "encrypted_session: data len=%d", content.len); ngx_str_t *computed_signature = ngx_http_session_generate_signature(r, - &iv, &key, &encrypted_content); + &iv, &key, &encrypted_content, &tag, conf->encryption_mode); if (SIGNATURE_LENGTH != computed_signature->len || ngx_memcmp(computed_signature->data, signature, SIGNATURE_LENGTH) != 0) { @@ -432,7 +543,8 @@ ngx_http_set_decode_encrypted_session(ngx_http_request_t *r, rc = ngx_http_encrypted_session_aes_mac_decrypt(emcf, r->pool, r->connection->log, iv.data, iv.len, key.data, key.len, - content.data, content.len, &dst, &len); + content.data, content.len, conf->encryption_mode, tag.data, + &dst, &len); if (rc != NGX_OK) { ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, @@ -542,6 +654,23 @@ ngx_http_encrypted_session_iv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_OK; } +static char * +ngx_http_encrypted_session_mode_set(ngx_conf_t *cf, + ngx_command_t *cmd, void *conf) +{ + ngx_str_t *value; + ngx_http_encrypted_session_conf_t *llcf = conf; + + value = cf->args->elts; + if (value[1].len == 3 && strncmp("cbc", (char*)value[1].data, 3) == 0) { + llcf->encryption_mode = ngx_http_encrypted_session_mode_cbc; + } + else if (value[1].len == 3 && strncmp("gcm", (char*)value[1].data, 3) == 0) { + llcf->encryption_mode = ngx_http_encrypted_session_mode_gcm; + } + + return NGX_CONF_OK; +} static char * ngx_http_encrypted_session_expires(ngx_conf_t *cf, ngx_command_t *cmd, @@ -556,7 +685,13 @@ ngx_http_encrypted_session_expires(ngx_conf_t *cf, ngx_command_t *cmd, value = cf->args->elts; - llcf->expires = ngx_parse_time(&value[1], 1); + if (value[1].len > 1 && value[1].data[0] == '$') { + llcf->expires_var = &(value[1].data[1]); + llcf->expires_var_len = value[1].len - 1; + return NGX_CONF_OK; + } + + llcf->expires = ngx_http_encrypted_session_parse_expires(&value[1]); if (llcf->expires == NGX_ERROR) { return "invalid value"; @@ -653,7 +788,10 @@ ngx_http_encrypted_session_create_conf(ngx_conf_t *cf) conf->iv = NGX_CONF_UNSET_PTR; conf->iv_len = NGX_CONF_UNSET; conf->expires = NGX_CONF_UNSET; + conf->expires_var = NGX_CONF_UNSET_PTR; + conf->expires_var_len = NGX_CONF_UNSET; conf->iv_in_content = NGX_CONF_UNSET; + conf->encryption_mode = ngx_http_encrypted_session_mode_unknown; return conf; } @@ -676,7 +814,19 @@ ngx_http_encrypted_session_merge_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->expires, prev->expires, ngx_http_encrypted_session_default_expires); + ngx_conf_merge_size_value(conf->expires_var_len, prev->expires_var_len, + (size_t)0); + ngx_conf_merge_ptr_value(conf->expires_var, prev->expires_var, + NULL); ngx_conf_merge_value(conf->iv_in_content, prev->iv_in_content, 0); + if (conf->encryption_mode == ngx_http_encrypted_session_mode_unknown) { + conf->encryption_mode = prev->encryption_mode; + } + + if (conf->encryption_mode == ngx_http_encrypted_session_mode_unknown) { + conf->encryption_mode = ngx_http_encrypted_session_mode_cbc; + } + return NGX_CONF_OK; }