[PATCH] Nginx secure link module with HMAC construction

Adrian Kotelba adrian.kotelba at barbasyn.org
Sat Apr 28 08:48:02 UTC 2012


Hello,

Attached is the proposed patch to http_secure_link module. With the
patch, the security and functionality of the module is extended. First
of all, the secure token is created using much more secure HMAC
construction with an arbitrary hash algorithm supported by OpenSSL,
e.g., md5, sha1, sha256, sha512. Secure token is created in the
standard way as in RFC2104, that is, H(secret_key XOR opad,
H(secret_key XOR ipad, message)) instead of a simple MD5(secret_key,
message, expire). Message to be hashed is defined by
secure_link_hmac_message, secret_key is given by
secure_link_hmac_secret, and hashing algorithm H is defined by
secure_link_hmac_algorithm. The expiration timestamp can be either
appended to secret key, or message to be hashed, or both.
Configuration example below.

location ^~ /files/ {
	secure_link                $arg_st,$arg_e;
	secure_link_hmac_secret    my_secret_key$arg_e;
	secure_link_hmac_message   $uri;
	secure_link_hmac_algorithm sha256;
	
	if ($secure_link = "") {
		return 403;
	}
		
	if ($secure_link = "0") {
		return 410;
	}

	rewrite ^/files/(.$)$ /files/$1 break;
}

Application side can use a standard hash_hmac function to generate
hash, which then needs to be base64 encoded. Example in PHP

    $expire = time() + 3600;
    $secret = "my_secret_key" . $expire;
    $algo = "sha256";
    $path = "/files/top_secret.pdf";
    $hashmac = base64_encode(hash_hmac($algo,$path,$secret,true));
    $hashmac = strtr($hashmac,"+/","-_"));
    $hashmac = str_replace("=","",$hashmac);
    $host = $_SERVER['HTTP_HOST'];
    $loc = "https://" . $host . "/files/top_secret.pdf" . "?st=" .
$hashmac . "&e=" . $expire;

Patch below.

--- ngx_http_secure_link_module.c	2012-01-18 17:07:43.000000000 +0200
+++ ngx_http_hmac_secure_link_module.c	2012-04-28 10:19:00.000000000 +0300
@@ -9,12 +9,17 @@
 #include <ngx_core.h>
 #include <ngx_http.h>
 #include <ngx_md5.h>
+#include <openssl/evp.h>
+#include <openssl/hmac.h>


 typedef struct {
     ngx_http_complex_value_t  *variable;
     ngx_http_complex_value_t  *md5;
+    ngx_http_complex_value_t  *hmac_message;
+    ngx_http_complex_value_t  *hmac_secret;
     ngx_str_t                  secret;
+    ngx_str_t                  hmac_algorithm;
 } ngx_http_secure_link_conf_t;


@@ -26,6 +31,9 @@
 static ngx_int_t ngx_http_secure_link_old_variable(ngx_http_request_t *r,
     ngx_http_secure_link_conf_t *conf, ngx_http_variable_value_t *v,
     uintptr_t data);
+static ngx_int_t ngx_http_secure_link_hmac_variable(ngx_http_request_t *r,
+    ngx_http_secure_link_conf_t *conf, ngx_http_variable_value_t *v,
+    uintptr_t data);
 static ngx_int_t ngx_http_secure_link_expires_variable(ngx_http_request_t *r,
     ngx_http_variable_value_t *v, uintptr_t data);
 static void *ngx_http_secure_link_create_conf(ngx_conf_t *cf);
@@ -57,6 +65,27 @@
       offsetof(ngx_http_secure_link_conf_t, secret),
       NULL },

+    { ngx_string("secure_link_hmac_message"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_set_complex_value_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_secure_link_conf_t, hmac_message),
+      NULL },
+
+    { ngx_string("secure_link_hmac_secret"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_http_set_complex_value_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_secure_link_conf_t, hmac_secret),
+      NULL },
+
+    { ngx_string("secure_link_hmac_algorithm"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_secure_link_conf_t, hmac_algorithm),
+      NULL },
+
       ngx_null_command
 };

@@ -115,6 +144,10 @@
         return ngx_http_secure_link_old_variable(r, conf, v, data);
     }

+    if (conf->hmac_algorithm.len) {
+        return ngx_http_secure_link_hmac_variable(r, conf, v, data);
+    }
+
     if (conf->variable == NULL || conf->md5 == NULL) {
         goto not_found;
     }
@@ -266,6 +299,107 @@
     return NGX_OK;
 }

+static ngx_int_t
+ngx_http_secure_link_hmac_variable(ngx_http_request_t *r,
+    ngx_http_secure_link_conf_t *conf, ngx_http_variable_value_t *v,
+    uintptr_t data)
+{
+    u_char                       *p, *last;
+    ngx_str_t                     val, hash, key;
+    time_t                        expires;
+    ngx_http_secure_link_ctx_t   *ctx;
+    u_char                        hash_buf[EVP_MAX_MD_SIZE],
hmac_buf[EVP_MAX_MD_SIZE];
+    const EVP_MD                 *evp_md;
+    u_int                         hmac_len, hash_base64_len;
+
+    if (conf->variable == NULL || conf->hmac_message == NULL ||
conf->hmac_secret == NULL) {
+        goto not_found;
+    }
+
+    if (ngx_http_complex_value(r, conf->variable, &val) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "secure link: \"%V\"", &val);
+
+    last = val.data + val.len;
+
+    p = ngx_strlchr(val.data, last, ',');
+    expires = 0;
+
+    if (p) {
+        val.len = p++ - val.data;
+
+        expires = ngx_atotm(p, last - p);
+        if (expires <= 0) {
+            goto not_found;
+        }
+
+        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_secure_link_ctx_t));
+        if (ctx == NULL) {
+            return NGX_ERROR;
+        }
+
+        ngx_http_set_ctx(r, ctx, ngx_http_secure_link_module);
+
+        ctx->expires.len = last - p;
+        ctx->expires.data = p;
+    }
+
+    evp_md = EVP_get_digestbyname((const char*) conf->hmac_algorithm.data);
+    if (evp_md == NULL) {
+        return NGX_ERROR;
+    }
+
+    hash.len  = (u_int) EVP_MD_size(evp_md);
+    hash.data = hash_buf;
+
+    hash_base64_len = (4*hash.len+2)/3;
+    if (val.len > hash_base64_len+2) {
+        goto not_found;
+    }
+
+    if (ngx_decode_base64url(&hash, &val) != NGX_OK) {
+        goto not_found;
+    }
+
+    if (hash.len != (u_int) EVP_MD_size(evp_md)) {
+        goto not_found;
+    }
+
+    if (ngx_http_complex_value(r, conf->hmac_message, &val) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "secure link message: \"%V\"", &val);
+
+    if (ngx_http_complex_value(r, conf->hmac_secret, &key) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    HMAC(evp_md, key.data, key.len, val.data, val.len, hmac_buf, &hmac_len);
+
+    if (ngx_memcmp(hash_buf, hmac_buf, EVP_MD_size(evp_md)) != 0) {
+        goto not_found;
+    }
+
+    v->data = (u_char *) ((expires && expires < ngx_time()) ? "0" : "1");
+    v->len = 1;
+    v->valid = 1;
+    v->no_cacheable = 0;
+    v->not_found = 0;
+
+    return NGX_OK;
+
+not_found:
+
+    v->not_found = 1;
+
+    return NGX_OK;
+}
+

 static ngx_int_t
 ngx_http_secure_link_expires_variable(ngx_http_request_t *r,
@@ -306,6 +440,9 @@
      *     conf->variable = NULL;
      *     conf->md5 = NULL;
      *     conf->secret = { 0, NULL };
+     *     conf->hmac_message = NULL;
+     *     conf->hmac_secret = NULL;
+     *     conf->hmac_algorithm = {0,NULL};
      */

     return conf;
@@ -319,6 +456,7 @@
     ngx_http_secure_link_conf_t *conf = child;

     ngx_conf_merge_str_value(conf->secret, prev->secret, "");
+    ngx_conf_merge_str_value(conf->hmac_algorithm, prev->hmac_algorithm, "");

     if (conf->variable == NULL) {
         conf->variable = prev->variable;
@@ -328,6 +466,14 @@
         conf->md5 = prev->md5;
     }

+    if (conf->hmac_message == NULL) {
+        conf->hmac_message = prev->hmac_message;
+    }
+
+    if (conf->hmac_message == NULL) {
+        conf->hmac_message = prev->hmac_secret;
+    }
+
     return NGX_CONF_OK;
 }



More information about the nginx-devel mailing list