├── cht ├── ngx_http_secure_token_cht_commands.h ├── ngx_http_secure_token_cht.h └── ngx_http_secure_token_cht.c ├── akamai ├── ngx_http_secure_token_akamai_commands.h ├── ngx_http_secure_token_akamai.h └── ngx_http_secure_token_akamai.c ├── iijpta ├── ngx_http_secure_token_iijpta_commands.h ├── ngx_http_secure_token_iijpta.h └── ngx_http_secure_token_iijpta.c ├── broadpeak ├── ngx_http_secure_token_broadpeak_commands.h ├── ngx_http_secure_token_broadpeak.h └── ngx_http_secure_token_broadpeak.c ├── cdnvideo ├── ngx_http_secure_token_cdnvideo_commands.h ├── ngx_http_secure_token_cdnvideo.h └── ngx_http_secure_token_cdnvideo.c ├── chinacache ├── ngx_http_secure_token_chinacache_commands.h ├── ngx_http_secure_token_chinacache.h └── ngx_http_secure_token_chinacache.c ├── cloudfront ├── ngx_http_secure_token_cloudfront_commands.h ├── ngx_http_secure_token_cloudfront.h └── ngx_http_secure_token_cloudfront.c ├── ngx_http_secure_token_filter_module.h ├── ngx_http_secure_token_encrypt_uri.h ├── .travis.yml ├── scripts ├── encryptUrl.py └── encryptUrl.php ├── ngx_http_secure_token_m3u8.h ├── ngx_http_secure_token_xml.h ├── ngx_http_secure_token_conf.h ├── CHANGELOG.md ├── ngx_http_secure_token_utils.h ├── travis_build.sh ├── ngx_http_secure_token_processor_base.h ├── config ├── ngx_http_secure_token_xml.c ├── ngx_http_secure_token_m3u8.c ├── ngx_http_secure_token_encrypt_uri.c ├── ngx_http_secure_token_utils.c ├── README.md ├── ngx_http_secure_token_processor_base.c ├── ngx_http_secure_token_filter_module.c └── LICENSE /cht/ngx_http_secure_token_cht_commands.h: -------------------------------------------------------------------------------- 1 | { ngx_string("secure_token_cht"), 2 | NGX_HTTP_MAIN_CONF | NGX_CONF_BLOCK | NGX_CONF_TAKE1, 3 | ngx_secure_token_cht_block, 4 | NGX_HTTP_MAIN_CONF_OFFSET, 5 | 0, 6 | NULL }, 7 | -------------------------------------------------------------------------------- /akamai/ngx_http_secure_token_akamai_commands.h: -------------------------------------------------------------------------------- 1 | { ngx_string("secure_token_akamai"), 2 | NGX_HTTP_MAIN_CONF | NGX_CONF_BLOCK | NGX_CONF_TAKE1, 3 | ngx_secure_token_akamai_block, 4 | NGX_HTTP_MAIN_CONF_OFFSET, 5 | 0, 6 | NULL }, 7 | -------------------------------------------------------------------------------- /iijpta/ngx_http_secure_token_iijpta_commands.h: -------------------------------------------------------------------------------- 1 | { ngx_string("secure_token_iijpta"), 2 | NGX_HTTP_MAIN_CONF | NGX_CONF_BLOCK | NGX_CONF_TAKE1, 3 | ngx_secure_token_iijpta_block, 4 | NGX_HTTP_MAIN_CONF_OFFSET, 5 | 0, 6 | NULL }, 7 | -------------------------------------------------------------------------------- /broadpeak/ngx_http_secure_token_broadpeak_commands.h: -------------------------------------------------------------------------------- 1 | { ngx_string("secure_token_broadpeak"), 2 | NGX_HTTP_MAIN_CONF | NGX_CONF_BLOCK | NGX_CONF_TAKE1, 3 | ngx_secure_token_broadpeak_block, 4 | NGX_HTTP_MAIN_CONF_OFFSET, 5 | 0, 6 | NULL }, 7 | -------------------------------------------------------------------------------- /cdnvideo/ngx_http_secure_token_cdnvideo_commands.h: -------------------------------------------------------------------------------- 1 | { ngx_string("secure_token_cdnvideo"), 2 | NGX_HTTP_MAIN_CONF | NGX_CONF_BLOCK | NGX_CONF_TAKE1, 3 | ngx_secure_token_cdnvideo_block, 4 | NGX_HTTP_MAIN_CONF_OFFSET, 5 | 0, 6 | NULL }, 7 | -------------------------------------------------------------------------------- /chinacache/ngx_http_secure_token_chinacache_commands.h: -------------------------------------------------------------------------------- 1 | { ngx_string("secure_token_chinacache"), 2 | NGX_HTTP_MAIN_CONF | NGX_CONF_BLOCK | NGX_CONF_TAKE1, 3 | ngx_secure_token_chinacache_block, 4 | NGX_HTTP_MAIN_CONF_OFFSET, 5 | 0, 6 | NULL }, 7 | -------------------------------------------------------------------------------- /cloudfront/ngx_http_secure_token_cloudfront_commands.h: -------------------------------------------------------------------------------- 1 | { ngx_string("secure_token_cloudfront"), 2 | NGX_HTTP_MAIN_CONF | NGX_CONF_BLOCK | NGX_CONF_TAKE1, 3 | ngx_secure_token_cloudfront_block, 4 | NGX_HTTP_MAIN_CONF_OFFSET, 5 | 0, 6 | NULL }, 7 | -------------------------------------------------------------------------------- /cht/ngx_http_secure_token_cht.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGX_HTTP_SECURE_TOKEN_CHT_H_INCLUDED_ 2 | #define _NGX_HTTP_SECURE_TOKEN_CHT_H_INCLUDED_ 3 | 4 | #include "../ngx_http_secure_token_conf.h" 5 | 6 | // functions 7 | char* ngx_secure_token_cht_block( 8 | ngx_conf_t *cf, 9 | ngx_command_t *cmd, 10 | void *conf); 11 | 12 | #endif // _NGX_HTTP_SECURE_TOKEN_CHT_H_INCLUDED_ 13 | -------------------------------------------------------------------------------- /akamai/ngx_http_secure_token_akamai.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGX_HTTP_SECURE_TOKEN_AKAMAI_H_INCLUDED_ 2 | #define _NGX_HTTP_SECURE_TOKEN_AKAMAI_H_INCLUDED_ 3 | 4 | #include "../ngx_http_secure_token_conf.h" 5 | 6 | // functions 7 | char* ngx_secure_token_akamai_block( 8 | ngx_conf_t *cf, 9 | ngx_command_t *cmd, 10 | void *conf); 11 | 12 | #endif // _NGX_HTTP_SECURE_TOKEN_AKAMAI_H_INCLUDED_ 13 | -------------------------------------------------------------------------------- /iijpta/ngx_http_secure_token_iijpta.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGX_HTTP_SECURE_TOKEN_IIJPTA_H_INCLUDED_ 2 | #define _NGX_HTTP_SECURE_TOKEN_IIJPTA_H_INCLUDED_ 3 | 4 | #include "../ngx_http_secure_token_conf.h" 5 | 6 | // functions 7 | char* ngx_secure_token_iijpta_block( 8 | ngx_conf_t *cf, 9 | ngx_command_t *cmd, 10 | void *conf); 11 | 12 | #endif // _NGX_HTTP_SECURE_TOKEN_IIJPTA_H_INCLUDED_ 13 | -------------------------------------------------------------------------------- /cdnvideo/ngx_http_secure_token_cdnvideo.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGX_HTTP_SECURE_TOKEN_CDNVIDEO_H_INCLUDED_ 2 | #define _NGX_HTTP_SECURE_TOKEN_CDNVIDEO_H_INCLUDED_ 3 | 4 | #include "../ngx_http_secure_token_conf.h" 5 | 6 | // functions 7 | char* ngx_secure_token_cdnvideo_block( 8 | ngx_conf_t *cf, 9 | ngx_command_t *cmd, 10 | void *conf); 11 | 12 | #endif // _NGX_HTTP_SECURE_TOKEN_CDNVIDEO_H_INCLUDED_ 13 | -------------------------------------------------------------------------------- /broadpeak/ngx_http_secure_token_broadpeak.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGX_HTTP_SECURE_TOKEN_BROADPEAK_H_INCLUDED_ 2 | #define _NGX_HTTP_SECURE_TOKEN_BROADPEAK_H_INCLUDED_ 3 | 4 | #include "../ngx_http_secure_token_conf.h" 5 | 6 | // functions 7 | char* ngx_secure_token_broadpeak_block( 8 | ngx_conf_t *cf, 9 | ngx_command_t *cmd, 10 | void *conf); 11 | 12 | #endif // _NGX_HTTP_SECURE_TOKEN_BROADPEAK_H_INCLUDED_ 13 | -------------------------------------------------------------------------------- /chinacache/ngx_http_secure_token_chinacache.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGX_HTTP_SECURE_TOKEN_CHINACACHE_H_INCLUDED_ 2 | #define _NGX_HTTP_SECURE_TOKEN_CHINACACHE_H_INCLUDED_ 3 | 4 | #include "../ngx_http_secure_token_conf.h" 5 | 6 | // functions 7 | char* ngx_secure_token_chinacache_block( 8 | ngx_conf_t *cf, 9 | ngx_command_t *cmd, 10 | void *conf); 11 | 12 | #endif // _NGX_HTTP_SECURE_TOKEN_CHINACACHE_H_INCLUDED_ 13 | -------------------------------------------------------------------------------- /cloudfront/ngx_http_secure_token_cloudfront.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGX_HTTP_SECURE_TOKEN_CLOUDFRONT_H_INCLUDED_ 2 | #define _NGX_HTTP_SECURE_TOKEN_CLOUDFRONT_H_INCLUDED_ 3 | 4 | #include "../ngx_http_secure_token_conf.h" 5 | 6 | // functions 7 | char* ngx_secure_token_cloudfront_block( 8 | ngx_conf_t *cf, 9 | ngx_command_t *cmd, 10 | void *conf); 11 | 12 | #endif // _NGX_HTTP_SECURE_TOKEN_CLOUDFRONT_H_INCLUDED_ 13 | -------------------------------------------------------------------------------- /ngx_http_secure_token_filter_module.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGX_HTTP_SECURE_TOKEN_FILTER_MODULE_H_INCLUDED_ 2 | #define _NGX_HTTP_SECURE_TOKEN_FILTER_MODULE_H_INCLUDED_ 3 | 4 | // includes 5 | #include 6 | 7 | // functions 8 | ngx_int_t ngx_http_secure_token_get_acl(ngx_http_request_t *r, ngx_http_complex_value_t *acl_conf, ngx_str_t* acl); 9 | 10 | // globals 11 | extern ngx_module_t ngx_http_secure_token_filter_module; 12 | 13 | #endif // _NGX_HTTP_SECURE_TOKEN_FILTER_MODULE_H_INCLUDED_ 14 | -------------------------------------------------------------------------------- /ngx_http_secure_token_encrypt_uri.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGX_HTTP_SECURE_TOKEN_ENCRYPT_URI_H_INCLUDED_ 2 | #define _NGX_HTTP_SECURE_TOKEN_ENCRYPT_URI_H_INCLUDED_ 3 | 4 | // includes 5 | #include 6 | 7 | // functions 8 | ngx_int_t ngx_http_secure_token_decrypt_uri(ngx_http_request_t *r); 9 | 10 | ngx_int_t ngx_http_secure_token_encrypt_uri(ngx_http_request_t* r, ngx_str_t* src, ngx_str_t* dest); 11 | 12 | ngx_int_t ngx_http_secure_token_encrypt_uri_add_variables(ngx_conf_t *cf); 13 | 14 | #endif // _NGX_HTTP_SECURE_TOKEN_ENCRYPT_URI_H_INCLUDED_ 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | before_install: 2 | - sudo apt-get update -qq 3 | - sudo apt-get install -y libssl-dev libpcre3-dev wget unzip 4 | language: c 5 | compiler: 6 | - clang 7 | - gcc 8 | env: 9 | - NGX_SECURE_TOKEN_MODULE=--add-module=./nginx-secure-token-module 10 | - NGX_SECURE_TOKEN_MODULE=--add-dynamic-module=./nginx-secure-token-module 11 | script: ./travis_build.sh $NGX_SECURE_TOKEN_MODULE 12 | notifications: 13 | email: 14 | recipients: 15 | - eran.kornblau@kaltura.com 16 | - jess.portnoy@kaltura.com 17 | on_success: change 18 | on_failure: always 19 | -------------------------------------------------------------------------------- /scripts/encryptUrl.py: -------------------------------------------------------------------------------- 1 | from Crypto.Cipher import AES 2 | import hashlib 3 | import base64 4 | import sys 5 | 6 | if len(sys.argv) < 5: 7 | print 'Usage:\n\tphp encryptUrl.php ' 8 | sys.exit(1) 9 | 10 | BASE_URL = sys.argv[1] 11 | ENC_URL = sys.argv[2] 12 | ENC_KEY = sys.argv[3].decode('hex') 13 | ENC_IV = sys.argv[4].decode('hex') 14 | HASH_SIZE = 8 15 | 16 | def pkcs7encode(text, blockSize = 16): 17 | val = blockSize - (len(text) % blockSize) 18 | for _ in xrange(val): 19 | text += chr(val) 20 | return text 21 | 22 | m = hashlib.md5() 23 | m.update(ENC_URL) 24 | signedUrl = m.digest()[:HASH_SIZE] + ENC_URL 25 | 26 | aes = AES.new(ENC_KEY, AES.MODE_CBC, ENC_IV) 27 | encryptedUrl = base64.urlsafe_b64encode(aes.encrypt(pkcs7encode(signedUrl))).rstrip('=') 28 | 29 | print BASE_URL + encryptedUrl 30 | -------------------------------------------------------------------------------- /ngx_http_secure_token_m3u8.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGX_HTTP_SECURE_TOKEN_M3U8_H_INCLUDED_ 2 | #define _NGX_HTTP_SECURE_TOKEN_M3U8_H_INCLUDED_ 3 | 4 | // includes 5 | #include 6 | #include "ngx_http_secure_token_processor_base.h" 7 | 8 | // constants 9 | #define M3U8_MAX_TAG_NAME_LEN (50) 10 | #define M3U8_MAX_ATTR_NAME_LEN (50) 11 | 12 | // typedefs 13 | typedef struct { 14 | ngx_http_secure_token_base_ctx_t base; 15 | size_t tag_name_len; 16 | u_char tag_name[M3U8_MAX_TAG_NAME_LEN]; 17 | size_t attr_name_len; 18 | u_char attr_name[M3U8_MAX_ATTR_NAME_LEN]; 19 | } ngx_http_secure_token_m3u8_ctx_t; 20 | 21 | // functions 22 | ngx_int_t ngx_http_secure_token_m3u8_processor( 23 | ngx_http_request_t* r, 24 | ngx_http_secure_token_processor_conf_t* conf, 25 | void* params, 26 | u_char** pos, 27 | u_char* last, 28 | ngx_http_secure_token_m3u8_ctx_t* ctx, 29 | ngx_http_secure_token_processor_output_t* output); 30 | 31 | #endif // _NGX_HTTP_SECURE_TOKEN_M3U8_H_INCLUDED_ 32 | -------------------------------------------------------------------------------- /ngx_http_secure_token_xml.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGX_HTTP_SECURE_TOKEN_XML_H_INCLUDED_ 2 | #define _NGX_HTTP_SECURE_TOKEN_XML_H_INCLUDED_ 3 | 4 | // includes 5 | #include 6 | #include "ngx_http_secure_token_processor_base.h" 7 | 8 | // constants 9 | #define XML_MAX_TAG_NAME_LEN (50) 10 | #define XML_MAX_ATTR_NAME_LEN (50) 11 | 12 | // typedefs 13 | typedef struct { 14 | ngx_str_t tag_name; 15 | ngx_str_t* attr_names; 16 | } ngx_http_secure_token_xml_node_attrs_t; 17 | 18 | typedef struct { 19 | ngx_http_secure_token_base_ctx_t base; 20 | size_t tag_name_len; 21 | u_char tag_name[XML_MAX_TAG_NAME_LEN]; 22 | size_t attr_name_len; 23 | u_char attr_name[XML_MAX_ATTR_NAME_LEN]; 24 | } ngx_http_secure_token_xml_ctx_t; 25 | 26 | // functions 27 | ngx_int_t ngx_http_secure_token_xml_processor( 28 | ngx_http_request_t* r, 29 | ngx_http_secure_token_processor_conf_t* conf, 30 | ngx_http_secure_token_xml_node_attrs_t* nodes, 31 | u_char** pos, 32 | u_char* last, 33 | ngx_http_secure_token_xml_ctx_t* ctx, 34 | ngx_http_secure_token_processor_output_t* output); 35 | 36 | #endif // _NGX_HTTP_SECURE_TOKEN_XML_H_INCLUDED_ 37 | -------------------------------------------------------------------------------- /scripts/encryptUrl.php: -------------------------------------------------------------------------------- 1 | \n"); 6 | } 7 | 8 | $BASE_URL = $argv[1]; 9 | $ENC_URL = $argv[2]; 10 | $ENC_KEY = pack("H*", $argv[3]); 11 | $ENC_IV = pack("H*", $argv[4]); 12 | 13 | $HASH_SIZE = 8; 14 | 15 | // add signature 16 | $signedUrl = substr(md5($ENC_URL, true), 0, $HASH_SIZE) . $ENC_URL; 17 | 18 | // add PKCS#7 padding 19 | $pad = 16 - (strlen($signedUrl) % 16); 20 | $signedUrl .= str_repeat(chr($pad), $pad); 21 | 22 | $encrypted = null; 23 | 24 | // AES encrypt 25 | if (function_exists('mcrypt_encrypt')) 26 | { 27 | $encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $ENC_KEY, $signedUrl, MCRYPT_MODE_CBC, $ENC_IV); 28 | } 29 | else if (function_exists('openssl_encrypt')) 30 | { 31 | $encrypted = openssl_encrypt($signedUrl, 'aes-256-cbc', $ENC_KEY, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $ENC_IV); 32 | } 33 | else 34 | { 35 | die("need either mcrypt or openssl extension\n"); 36 | } 37 | 38 | // base64 encrypt 39 | $base64Encoded = rtrim(strtr(base64_encode($encrypted), '+/', '-_'), '='); 40 | 41 | echo $BASE_URL . $base64Encoded . "\n"; 42 | -------------------------------------------------------------------------------- /ngx_http_secure_token_conf.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGX_HTTP_SECURE_TOKEN_CONF_H_INCLUDED_ 2 | #define _NGX_HTTP_SECURE_TOKEN_CONF_H_INCLUDED_ 3 | 4 | #include 5 | 6 | struct ngx_http_secure_token_loc_conf_s; 7 | typedef struct ngx_http_secure_token_loc_conf_s ngx_http_secure_token_loc_conf_t; 8 | 9 | typedef ngx_int_t (*ngx_http_secure_token_build_t)( 10 | ngx_http_request_t* r, 11 | ngx_http_secure_token_loc_conf_t *conf, 12 | ngx_str_t* result); 13 | 14 | typedef struct { 15 | ngx_flag_t tokenize_segments; 16 | ngx_flag_t encrypt_uri; 17 | } ngx_http_secure_token_processor_conf_t; 18 | 19 | struct ngx_http_secure_token_loc_conf_s { 20 | ngx_flag_t avoid_cookies; 21 | ngx_hash_t processors_hash; 22 | 23 | ngx_hash_t types; 24 | ngx_array_t *types_keys; 25 | ngx_array_t* filename_prefixes; 26 | 27 | time_t expires_time; 28 | time_t cookie_token_expires_time; 29 | time_t query_token_expires_time; 30 | ngx_str_t cache_scope; 31 | ngx_str_t token_cache_scope; 32 | ngx_str_t last_modified; 33 | ngx_str_t token_last_modified; 34 | time_t last_modified_time; 35 | time_t token_last_modified_time; 36 | ngx_str_t content_type_m3u8; 37 | ngx_str_t content_type_mpd; 38 | ngx_str_t content_type_f4m; 39 | 40 | ngx_http_complex_value_t *token; 41 | ngx_http_secure_token_processor_conf_t processor_conf; 42 | 43 | ngx_str_t encrypt_uri_key; 44 | ngx_str_t encrypt_uri_iv; 45 | ngx_http_complex_value_t *encrypt_uri_part; 46 | size_t encrypt_uri_hash_size; 47 | }; 48 | 49 | #endif // _NGX_HTTP_SECURE_TOKEN_CONF_H_INCLUDED_ 50 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | Note: the list of changes below may not include all changes, it will include mostly "breaking" changes. 4 | Usually, these are changes that require some update to nginx.conf in order to retain the existing behavior. 5 | 6 | ## 2016/12/08 - refactor conf structure 7 | 8 | The following configuration settings were removed: 9 | * secure_token - this parameter no longer gets the token type (akamai/cloudfront) it now gets an expression that evaluates to the token. 10 | use a secure_token_akamai / secure_token_cloudfront block to define a token variable, and use the token variable as the secure_token expression. 11 | * secure_token_window - use the end param inside a secure_token_akamai / secure_token_cloudfront block 12 | * secure_token_end_time - use the end param inside a secure_token_akamai / secure_token_cloudfront block 13 | * secure_token_ip_address - use the ip_address param inside a secure_token_akamai / secure_token_cloudfront block 14 | * secure_token_akamai_key - use the key param inside a secure_token_akamai block 15 | * secure_token_akamai_param_name - use the param_name param inside a secure_token_akamai block 16 | * secure_token_akamai_acl - use the acl param inside a secure_token_akamai block 17 | * secure_token_cloudfront_private_key_file - use the private_key_file param inside a secure_token_cloudfront block 18 | * secure_token_cloudfront_key_pair_id - use the key_pair_id param inside a secure_token_cloudfront block 19 | * secure_token_cloudfront_acl - use the acl param inside a secure_token_cloudfront block 20 | 21 | ## 2016/12/10 - rename $baseuri variable 22 | 23 | Added $secure_token_baseuri, renamed $baseuri to $secure_token_baseuri_comma 24 | -------------------------------------------------------------------------------- /ngx_http_secure_token_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGX_HTTP_SECURE_TOKEN_UTILS_H_INCLUDED_ 2 | #define _NGX_HTTP_SECURE_TOKEN_UTILS_H_INCLUDED_ 3 | 4 | // includes 5 | #include 6 | #include 7 | #include 8 | 9 | // constants 10 | #ifndef MD5_DIGEST_LENGTH 11 | #define MD5_DIGEST_LENGTH (16) 12 | #endif // MD5_DIGEST_LENGTH 13 | 14 | #ifndef AES_BLOCK_SIZE 15 | #define AES_BLOCK_SIZE (16) 16 | #endif // AES_BLOCK_SIZE 17 | 18 | // typedefs 19 | typedef enum { 20 | NGX_HTTP_SECURE_TOKEN_TIME_UNSET, 21 | NGX_HTTP_SECURE_TOKEN_TIME_RELATIVE, 22 | NGX_HTTP_SECURE_TOKEN_TIME_ABSOLUTE, 23 | } ngx_secure_token_time_type_t; 24 | 25 | typedef struct { 26 | ngx_secure_token_time_type_t type; 27 | time_t val; 28 | } ngx_secure_token_time_t; 29 | 30 | // conf functions 31 | char* ngx_http_secure_token_conf_set_hex_str_slot( 32 | ngx_conf_t *cf, 33 | ngx_command_t *cmd, 34 | void *conf); 35 | 36 | char* ngx_http_secure_token_conf_set_time_slot( 37 | ngx_conf_t *cf, 38 | ngx_command_t *cmd, 39 | void *conf); 40 | 41 | char* ngx_http_secure_token_conf_set_private_key_slot( 42 | ngx_conf_t *cf, 43 | ngx_command_t *cmd, 44 | void *conf); 45 | 46 | char* ngx_http_secure_token_conf_block( 47 | ngx_conf_t *cf, 48 | ngx_command_t *cmds, 49 | void *conf, 50 | ngx_http_get_variable_pt get_handler); 51 | 52 | // token functions 53 | u_char* ngx_http_secure_token_encode_base64_internal( 54 | u_char *d, 55 | ngx_str_t *src, 56 | const u_char *basis, 57 | u_char padding); 58 | 59 | ngx_int_t ngx_http_secure_token_sign( 60 | ngx_http_request_t* r, 61 | EVP_PKEY* private_key, 62 | ngx_str_t* message, 63 | ngx_str_t* signature); 64 | 65 | ngx_int_t ngx_http_secure_token_escape_xml( 66 | ngx_pool_t* pool, 67 | ngx_str_t* src, 68 | ngx_str_t* dest); 69 | 70 | char* ngx_conf_check_str_len_bounds( 71 | ngx_conf_t *cf, 72 | void *post, 73 | void *data); 74 | 75 | #endif // _NGX_HTTP_SECURE_TOKEN_UTILS_H_INCLUDED_ 76 | -------------------------------------------------------------------------------- /travis_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -o nounset # Treat unset variables as an error 3 | 4 | NGINX_VERSION=1.10.2 5 | NGINX_URI="http://nginx.org/download/nginx-$NGINX_VERSION.tar.gz" 6 | 7 | 8 | if [ ! -x "`which wget 2>/dev/null`" ];then 9 | echo "Need to install wget." 10 | exit 2 11 | fi 12 | mkdir -p /tmp/builddir/nginx-$NGINX_VERSION 13 | cp -r . /tmp/builddir/nginx-$NGINX_VERSION/nginx-secure-token-module 14 | cd /tmp/builddir 15 | wget $NGINX_URI -O kaltura-nginx-$NGINX_VERSION.tar.gz 16 | tar zxvf kaltura-nginx-$NGINX_VERSION.tar.gz 17 | cd nginx-$NGINX_VERSION 18 | 19 | LD_LIBRARY_PATH=/opt/kaltura/ffmpeg-2.1.3/lib 20 | LIBRARY_PATH=/opt/kaltura/ffmpeg-2.1.3/lib 21 | C_INCLUDE_PATH=/opt/kaltura/ffmpeg-2.1.3/include 22 | export LD_LIBRARY_PATH LIBRARY_PATH C_INCLUDE_PATH 23 | 24 | ./configure \ 25 | --prefix=/etc/nginx \ 26 | --sbin-path=/sbin/nginx \ 27 | --conf-path=/etc/nginx/nginx.conf \ 28 | --error-log-path=/var/log/log/nginx/error.log \ 29 | --http-log-path=/var/log/log/nginx/access.log \ 30 | --pid-path=/var/log/run/nginx.pid \ 31 | --lock-path=/var/log/run/nginx.lock \ 32 | --http-client-body-temp-path=/var/log/cache/nginx/client_temp \ 33 | --http-proxy-temp-path=/var/log/cache/nginx/proxy_temp \ 34 | --http-fastcgi-temp-path=/var/log/cache/nginx/fastcgi_temp \ 35 | --http-uwsgi-temp-path=/var/log/cache/nginx/uwsgi_temp \ 36 | --http-scgi-temp-path=/var/log/cache/nginx/scgi_temp \ 37 | --with-http_ssl_module \ 38 | --with-http_realip_module \ 39 | --with-http_addition_module \ 40 | --with-http_sub_module \ 41 | --with-http_dav_module \ 42 | --with-http_flv_module \ 43 | --with-http_mp4_module \ 44 | --with-http_gunzip_module \ 45 | --with-http_gzip_static_module \ 46 | --with-http_random_index_module \ 47 | --with-http_secure_link_module \ 48 | --with-http_stub_status_module \ 49 | --with-http_auth_request_module \ 50 | --with-mail \ 51 | --with-mail_ssl_module \ 52 | --with-file-aio \ 53 | --with-ipv6 \ 54 | --with-debug \ 55 | --with-threads \ 56 | $* 57 | make 58 | -------------------------------------------------------------------------------- /ngx_http_secure_token_processor_base.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGX_HTTP_SECURE_TOKEN_PROCESSOR_BASE_H_INCLUDED_ 2 | #define _NGX_HTTP_SECURE_TOKEN_PROCESSOR_BASE_H_INCLUDED_ 3 | 4 | // includes 5 | #include 6 | #include "ngx_http_secure_token_conf.h" 7 | 8 | #define MAX_SCHEME_LEN (10) 9 | 10 | // enums 11 | enum { 12 | STATE_INITIAL, 13 | 14 | STATE_URL_SCHEME, 15 | STATE_URL_NON_HTTP, 16 | STATE_URL_HOST, 17 | STATE_URL_PATH, 18 | STATE_URL_QUERY, 19 | 20 | STATE_URL_LAST, 21 | }; 22 | 23 | // typedefs 24 | struct ngx_http_secure_token_ctx_s; 25 | typedef struct ngx_http_secure_token_ctx_s ngx_http_secure_token_ctx_t; 26 | 27 | typedef struct { 28 | int url_end_state; 29 | u_char url_end_char; 30 | ngx_flag_t tokenize; 31 | 32 | int state; 33 | unsigned scheme_pos; 34 | u_char scheme[MAX_SCHEME_LEN]; 35 | unsigned scheme_delim_pos; 36 | u_char last_url_char; 37 | size_t uri_path_alloc_size; 38 | ngx_str_t uri_path; 39 | } ngx_http_secure_token_base_ctx_t; 40 | 41 | typedef struct { 42 | ngx_flag_t copy_input; 43 | ngx_str_t output_buffer; 44 | int token_index; 45 | } ngx_http_secure_token_processor_output_t; 46 | 47 | typedef ngx_int_t (*ngx_http_secure_token_body_processor_t)( 48 | ngx_http_request_t* r, 49 | ngx_http_secure_token_processor_conf_t* conf, 50 | void* params, 51 | u_char** pos, 52 | u_char* last, 53 | void* ctx, 54 | ngx_http_secure_token_processor_output_t* output); 55 | 56 | // processor utility functions 57 | 58 | void ngx_http_secure_token_url_state_machine_init( 59 | ngx_http_secure_token_base_ctx_t* ctx, 60 | ngx_flag_t tokenize, 61 | int url_end_state, 62 | u_char url_end_char); 63 | 64 | ngx_int_t ngx_http_secure_token_url_state_machine( 65 | ngx_http_request_t* r, 66 | ngx_http_secure_token_processor_conf_t* conf, 67 | ngx_http_secure_token_base_ctx_t* ctx, 68 | u_char** cur_pos, 69 | u_char* buffer_end, 70 | ngx_http_secure_token_processor_output_t* output); 71 | 72 | // main functions 73 | 74 | ngx_int_t ngx_http_secure_token_init_processors_hash( 75 | ngx_conf_t* cf, 76 | ngx_http_secure_token_loc_conf_t* conf); 77 | 78 | ngx_int_t ngx_http_secure_token_init_body_filter( 79 | ngx_http_request_t* r, 80 | ngx_str_t* token); 81 | 82 | void ngx_http_secure_token_install_body_filter(); 83 | 84 | #endif // _NGX_HTTP_SECURE_TOKEN_PROCESSOR_BASE_H_INCLUDED_ 85 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_secure_token_filter_module 2 | 3 | SEC_TOKEN_DEPS="$ngx_addon_dir/akamai/ngx_http_secure_token_akamai.h \ 4 | $ngx_addon_dir/akamai/ngx_http_secure_token_akamai_commands.h \ 5 | $ngx_addon_dir/broadpeak/ngx_http_secure_token_broadpeak.h \ 6 | $ngx_addon_dir/broadpeak/ngx_http_secure_token_broadpeak_commands.h \ 7 | $ngx_addon_dir/cdnvideo/ngx_http_secure_token_cdnvideo.h \ 8 | $ngx_addon_dir/cdnvideo/ngx_http_secure_token_cdnvideo_commands.h \ 9 | $ngx_addon_dir/chinacache/ngx_http_secure_token_chinacache.h \ 10 | $ngx_addon_dir/chinacache/ngx_http_secure_token_chinacache_commands.h \ 11 | $ngx_addon_dir/cht/ngx_http_secure_token_cht.h \ 12 | $ngx_addon_dir/cht/ngx_http_secure_token_cht_commands.h \ 13 | $ngx_addon_dir/cloudfront/ngx_http_secure_token_cloudfront.h \ 14 | $ngx_addon_dir/cloudfront/ngx_http_secure_token_cloudfront_commands.h \ 15 | $ngx_addon_dir/iijpta/ngx_http_secure_token_iijpta.h \ 16 | $ngx_addon_dir/iijpta/ngx_http_secure_token_iijpta_commands.h \ 17 | $ngx_addon_dir/ngx_http_secure_token_conf.h \ 18 | $ngx_addon_dir/ngx_http_secure_token_encrypt_uri.h \ 19 | $ngx_addon_dir/ngx_http_secure_token_filter_module.h \ 20 | $ngx_addon_dir/ngx_http_secure_token_m3u8.h \ 21 | $ngx_addon_dir/ngx_http_secure_token_processor_base.h \ 22 | $ngx_addon_dir/ngx_http_secure_token_utils.h \ 23 | $ngx_addon_dir/ngx_http_secure_token_xml.h \ 24 | " 25 | 26 | SEC_TOKEN_SRCS="$ngx_addon_dir/akamai/ngx_http_secure_token_akamai.c \ 27 | $ngx_addon_dir/broadpeak/ngx_http_secure_token_broadpeak.c \ 28 | $ngx_addon_dir/cdnvideo/ngx_http_secure_token_cdnvideo.c \ 29 | $ngx_addon_dir/chinacache/ngx_http_secure_token_chinacache.c \ 30 | $ngx_addon_dir/cht/ngx_http_secure_token_cht.c \ 31 | $ngx_addon_dir/cloudfront/ngx_http_secure_token_cloudfront.c \ 32 | $ngx_addon_dir/iijpta/ngx_http_secure_token_iijpta.c \ 33 | $ngx_addon_dir/ngx_http_secure_token_encrypt_uri.c \ 34 | $ngx_addon_dir/ngx_http_secure_token_filter_module.c \ 35 | $ngx_addon_dir/ngx_http_secure_token_m3u8.c \ 36 | $ngx_addon_dir/ngx_http_secure_token_processor_base.c \ 37 | $ngx_addon_dir/ngx_http_secure_token_utils.c \ 38 | $ngx_addon_dir/ngx_http_secure_token_xml.c \ 39 | " 40 | 41 | if [ -n "$ngx_module_link" ]; then 42 | ngx_module_type=HTTP_AUX_FILTER 43 | ngx_module_name=ngx_http_secure_token_filter_module 44 | ngx_module_deps="$SEC_TOKEN_DEPS" 45 | ngx_module_srcs="$SEC_TOKEN_SRCS" 46 | 47 | . auto/module 48 | else 49 | HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_secure_token_filter_module" 50 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS $SEC_TOKEN_DEPS" 51 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $SEC_TOKEN_SRCS" 52 | fi 53 | -------------------------------------------------------------------------------- /cht/ngx_http_secure_token_cht.c: -------------------------------------------------------------------------------- 1 | #include "ngx_http_secure_token_cht.h" 2 | #include "../ngx_http_secure_token_filter_module.h" 3 | #include "../ngx_http_secure_token_utils.h" 4 | 5 | #include 6 | 7 | // constants 8 | #define TOKEN_PART1 "token=" 9 | #define TOKEN_PART2 "&expires=" 10 | 11 | // typedefs 12 | typedef struct { 13 | ngx_http_complex_value_t *acl; 14 | ngx_str_t key; 15 | ngx_secure_token_time_t end; 16 | } ngx_secure_token_cht_token_t; 17 | 18 | // globals 19 | static ngx_command_t ngx_http_secure_token_cht_cmds[] = { 20 | { ngx_string("acl"), 21 | NGX_CONF_TAKE1, 22 | ngx_http_set_complex_value_slot, 23 | 0, 24 | offsetof(ngx_secure_token_cht_token_t, acl), 25 | NULL }, 26 | 27 | { ngx_string("key"), 28 | NGX_CONF_TAKE1, 29 | ngx_conf_set_str_slot, 30 | 0, 31 | offsetof(ngx_secure_token_cht_token_t, key), 32 | NULL }, 33 | 34 | { ngx_string("end"), 35 | NGX_CONF_TAKE1, 36 | ngx_http_secure_token_conf_set_time_slot, 37 | 0, 38 | offsetof(ngx_secure_token_cht_token_t, end), 39 | NULL }, 40 | }; 41 | 42 | static ngx_int_t 43 | ngx_secure_token_cht_get_var( 44 | ngx_http_request_t *r, 45 | ngx_http_variable_value_t *v, 46 | uintptr_t data) 47 | { 48 | ngx_secure_token_cht_token_t* token = (void*)data; 49 | ngx_str_t expires_str; 50 | ngx_str_t md5hash_str; 51 | ngx_str_t token_str; 52 | ngx_str_t acl; 53 | ngx_md5_t md5; 54 | u_char end_time_buf[NGX_INT32_LEN]; 55 | u_char md5hash_buf[MD5_DIGEST_LENGTH]; 56 | u_char token_buf[ngx_base64_encoded_length(MD5_DIGEST_LENGTH)]; 57 | time_t end_time; 58 | size_t result_size; 59 | u_char* p; 60 | ngx_int_t rc; 61 | 62 | // get the acl 63 | rc = ngx_http_secure_token_get_acl(r, token->acl, &acl); 64 | if (rc != NGX_OK) 65 | { 66 | return rc; 67 | } 68 | 69 | // get the end time 70 | end_time = token->end.val; 71 | if (token->end.type == NGX_HTTP_SECURE_TOKEN_TIME_RELATIVE) 72 | { 73 | end_time += ngx_time(); 74 | } 75 | expires_str.data = end_time_buf; 76 | expires_str.len = ngx_sprintf(end_time_buf, "%uD", (uint32_t)end_time) - end_time_buf; 77 | 78 | // calculate the signature 79 | ngx_md5_init(&md5); 80 | ngx_md5_update(&md5, acl.data, acl.len); 81 | ngx_md5_update(&md5, token->key.data, token->key.len); 82 | ngx_md5_update(&md5, expires_str.data, expires_str.len); 83 | ngx_md5_final(md5hash_buf, &md5); 84 | 85 | md5hash_str.data = md5hash_buf; 86 | md5hash_str.len = sizeof(md5hash_buf); 87 | 88 | token_str.data = token_buf; 89 | ngx_encode_base64url(&token_str, &md5hash_str); 90 | 91 | // get the result size 92 | result_size = sizeof(TOKEN_PART1) + token_str.len + sizeof(TOKEN_PART2) + expires_str.len; 93 | 94 | // allocate the result 95 | p = ngx_pnalloc(r->pool, result_size); 96 | if (p == NULL) 97 | { 98 | return NGX_ERROR; 99 | } 100 | 101 | v->data = p; 102 | 103 | // build the result 104 | p = ngx_copy(p, TOKEN_PART1, sizeof(TOKEN_PART1) - 1); 105 | p = ngx_copy(p, token_str.data, token_str.len); 106 | p = ngx_copy(p, TOKEN_PART2, sizeof(TOKEN_PART2) - 1); 107 | p = ngx_copy(p, expires_str.data, expires_str.len); 108 | *p = '\0'; 109 | 110 | v->len = p - v->data; 111 | v->valid = 1; 112 | v->no_cacheable = 0; 113 | v->not_found = 0; 114 | 115 | return NGX_OK; 116 | } 117 | 118 | char * 119 | ngx_secure_token_cht_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 120 | { 121 | ngx_secure_token_cht_token_t* token; 122 | char* rv; 123 | 124 | // init config 125 | token = ngx_pcalloc(cf->pool, sizeof(*token)); 126 | if (token == NULL) 127 | { 128 | return NGX_CONF_ERROR; 129 | } 130 | 131 | token->end.type = NGX_HTTP_SECURE_TOKEN_TIME_UNSET; 132 | 133 | // parse the block 134 | rv = ngx_http_secure_token_conf_block( 135 | cf, 136 | ngx_http_secure_token_cht_cmds, 137 | token, 138 | ngx_secure_token_cht_get_var); 139 | if (rv != NGX_CONF_OK) 140 | { 141 | return rv; 142 | } 143 | 144 | // validate required params 145 | if (token->key.data == NULL) 146 | { 147 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 148 | "\"key\" is mandatory for cht tokens"); 149 | return NGX_CONF_ERROR; 150 | } 151 | 152 | // populate unset optional params 153 | if (token->end.type == NGX_HTTP_SECURE_TOKEN_TIME_UNSET) 154 | { 155 | token->end.type = NGX_HTTP_SECURE_TOKEN_TIME_RELATIVE; 156 | token->end.val = 86400; 157 | } 158 | 159 | return NGX_CONF_OK; 160 | } 161 | -------------------------------------------------------------------------------- /ngx_http_secure_token_xml.c: -------------------------------------------------------------------------------- 1 | #include "ngx_http_secure_token_xml.h" 2 | 3 | enum { 4 | STATE_TAG_NAME = STATE_URL_LAST, 5 | STATE_CLOSING_TAG_NAME, 6 | STATE_ATTR_NAME, 7 | STATE_ATTR_VALUE, 8 | STATE_ATTR_VALUE_END, 9 | STATE_ATTR_QUOTED_VALUE, 10 | }; 11 | 12 | static ngx_flag_t 13 | ngx_http_secure_token_xml_is_relevant_attr( 14 | ngx_http_secure_token_xml_ctx_t* ctx, 15 | ngx_http_secure_token_xml_node_attrs_t* nodes, 16 | size_t attr_name_len) 17 | { 18 | ngx_http_secure_token_xml_node_attrs_t* cur_node; 19 | ngx_str_t* cur_attr; 20 | 21 | for (cur_node = nodes; cur_node->tag_name.len != 0; cur_node++) 22 | { 23 | if (ctx->tag_name_len != cur_node->tag_name.len || 24 | ngx_strncasecmp(ctx->tag_name, cur_node->tag_name.data, cur_node->tag_name.len) != 0) 25 | { 26 | continue; 27 | } 28 | 29 | if (cur_node->attr_names == NULL) 30 | { 31 | if (attr_name_len != 0) 32 | { 33 | continue; 34 | } 35 | 36 | return 1; 37 | } 38 | 39 | if (attr_name_len == 0) 40 | { 41 | continue; 42 | } 43 | 44 | for (cur_attr = cur_node->attr_names; cur_attr->len != 0; cur_attr++) 45 | { 46 | if (attr_name_len != cur_attr->len || 47 | ngx_strncasecmp(ctx->attr_name, cur_attr->data, cur_attr->len) != 0) 48 | { 49 | continue; 50 | } 51 | 52 | return 1; 53 | } 54 | } 55 | 56 | return 0; 57 | } 58 | 59 | ngx_int_t 60 | ngx_http_secure_token_xml_processor( 61 | ngx_http_request_t* r, 62 | ngx_http_secure_token_processor_conf_t* conf, 63 | ngx_http_secure_token_xml_node_attrs_t* nodes, 64 | u_char** pos, 65 | u_char* buffer_end, 66 | ngx_http_secure_token_xml_ctx_t* ctx, 67 | ngx_http_secure_token_processor_output_t* output) 68 | { 69 | u_char* cur_pos = *pos; 70 | u_char ch; 71 | 72 | for (cur_pos = *pos; cur_pos < buffer_end; cur_pos++) 73 | { 74 | ch = *cur_pos; 75 | 76 | switch (ctx->base.state) 77 | { 78 | case STATE_INITIAL: 79 | if (ch == '<') 80 | { 81 | ctx->base.state = STATE_TAG_NAME; 82 | ctx->tag_name_len = 0; 83 | } 84 | break; 85 | 86 | case STATE_TAG_NAME: 87 | case STATE_CLOSING_TAG_NAME: 88 | if (isspace(ch)) 89 | { 90 | if (ctx->tag_name_len == 0) 91 | { 92 | break; 93 | } 94 | ctx->base.state = STATE_ATTR_NAME; 95 | ctx->attr_name_len = 0; 96 | } 97 | else if (ch == '>') 98 | { 99 | if (ctx->base.state == STATE_TAG_NAME && 100 | ngx_http_secure_token_xml_is_relevant_attr(ctx, nodes, 0)) 101 | { 102 | ngx_http_secure_token_url_state_machine_init( 103 | &ctx->base, 104 | 1, 105 | STATE_INITIAL, 106 | '<'); 107 | break; 108 | } 109 | 110 | ctx->base.state = STATE_INITIAL; 111 | } 112 | else if (ch == '/' && ctx->tag_name_len == 0) 113 | { 114 | ctx->base.state = STATE_CLOSING_TAG_NAME; 115 | } 116 | else if (ctx->tag_name_len < XML_MAX_TAG_NAME_LEN) 117 | { 118 | ctx->tag_name[ctx->tag_name_len] = ch; 119 | ctx->tag_name_len++; 120 | } 121 | break; 122 | 123 | case STATE_ATTR_VALUE_END: // ignore the " char, and move back to attribute name state 124 | ctx->base.state = STATE_ATTR_NAME; 125 | break; 126 | 127 | case STATE_ATTR_NAME: 128 | if (isspace(ch)) 129 | { 130 | break; 131 | } 132 | if (ch == '=') 133 | { 134 | ctx->base.state = STATE_ATTR_VALUE; 135 | } 136 | else if (ch == '>') 137 | { 138 | if (ngx_http_secure_token_xml_is_relevant_attr(ctx, nodes, 0)) 139 | { 140 | ngx_http_secure_token_url_state_machine_init( 141 | &ctx->base, 142 | 1, 143 | STATE_INITIAL, 144 | '<'); 145 | break; 146 | } 147 | 148 | ctx->base.state = STATE_INITIAL; 149 | } 150 | else if (ctx->attr_name_len < XML_MAX_ATTR_NAME_LEN) 151 | { 152 | ctx->attr_name[ctx->attr_name_len] = ch; 153 | ctx->attr_name_len++; 154 | } 155 | break; 156 | 157 | case STATE_ATTR_VALUE: 158 | if (ch == '"') 159 | { 160 | if (ngx_http_secure_token_xml_is_relevant_attr(ctx, nodes, ctx->attr_name_len)) 161 | { 162 | ngx_http_secure_token_url_state_machine_init( 163 | &ctx->base, 164 | 1, 165 | STATE_ATTR_VALUE_END, 166 | '"'); 167 | ctx->attr_name_len = 0; 168 | break; 169 | } 170 | 171 | ctx->base.state = STATE_ATTR_QUOTED_VALUE; 172 | } 173 | break; 174 | 175 | case STATE_ATTR_QUOTED_VALUE: 176 | if (ch != '"') 177 | { 178 | break; 179 | } 180 | 181 | ctx->base.state = STATE_ATTR_NAME; 182 | ctx->attr_name_len = 0; 183 | break; 184 | 185 | default: 186 | *pos = cur_pos; 187 | return ngx_http_secure_token_url_state_machine( 188 | r, 189 | conf, 190 | &ctx->base, 191 | pos, 192 | buffer_end, 193 | output); 194 | } 195 | } 196 | 197 | *pos = cur_pos; 198 | return NGX_OK; 199 | } 200 | -------------------------------------------------------------------------------- /ngx_http_secure_token_m3u8.c: -------------------------------------------------------------------------------- 1 | #include "ngx_http_secure_token_encrypt_uri.h" 2 | #include "ngx_http_secure_token_m3u8.h" 3 | 4 | static ngx_str_t uri_tags[] = { 5 | ngx_string("EXT-X-MAP"), 6 | ngx_string("EXT-X-KEY"), 7 | ngx_string("EXT-X-PART"), 8 | ngx_string("EXT-X-MEDIA"), 9 | ngx_string("EXT-X-SESSION-KEY"), 10 | ngx_string("EXT-X-PRELOAD-HINT"), 11 | ngx_string("EXT-X-I-FRAME-STREAM-INF"), 12 | ngx_null_string, 13 | }; 14 | 15 | static u_char uri_attr_name[] = "URI"; 16 | 17 | enum { 18 | STATE_TAG_NAME = STATE_URL_LAST, 19 | STATE_ATTR_NAME, 20 | STATE_ATTR_VALUE, 21 | STATE_ATTR_QUOTED_VALUE, 22 | STATE_ATTR_WAIT_DELIM, 23 | STATE_WAIT_NEWLINE, 24 | }; 25 | 26 | static ngx_flag_t 27 | ngx_http_secure_token_m3u8_is_string_in_array( 28 | ngx_str_t* array, 29 | u_char* data, 30 | size_t len) 31 | { 32 | ngx_str_t* cur_str; 33 | 34 | for (cur_str = array; cur_str->len != 0; cur_str++) 35 | { 36 | if (len == cur_str->len && 37 | ngx_memcmp(data, cur_str->data, cur_str->len) == 0) 38 | { 39 | return 1; 40 | } 41 | } 42 | 43 | return 0; 44 | } 45 | 46 | // The example below shows the trasitions between the different states (numbers represent the state value): 47 | // #EXT-X-KEY:METHOD=AES-128,URI="encryption.key" 48 | // 1 2 36 2 34 6 49 | 50 | ngx_int_t 51 | ngx_http_secure_token_m3u8_processor( 52 | ngx_http_request_t* r, 53 | ngx_http_secure_token_processor_conf_t* conf, 54 | void* params, 55 | u_char** pos, 56 | u_char* buffer_end, 57 | ngx_http_secure_token_m3u8_ctx_t* ctx, 58 | ngx_http_secure_token_processor_output_t* output) 59 | { 60 | ngx_int_t rc; 61 | u_char* cur_pos; 62 | u_char ch; 63 | 64 | for (cur_pos = *pos; cur_pos < buffer_end; cur_pos++) 65 | { 66 | ch = *cur_pos; 67 | 68 | switch (ctx->base.state) 69 | { 70 | case STATE_INITIAL: 71 | if (ch == '#') 72 | { 73 | ctx->base.state = STATE_TAG_NAME; 74 | ctx->tag_name_len = 0; 75 | } 76 | else if (!isspace(ch)) 77 | { 78 | if (conf->tokenize_segments || conf->encrypt_uri) 79 | { 80 | ngx_http_secure_token_url_state_machine_init( 81 | &ctx->base, 82 | conf->tokenize_segments, 83 | STATE_WAIT_NEWLINE, 84 | 0); 85 | 86 | cur_pos--; // push the current char to the url state machine 87 | } 88 | else 89 | { 90 | ctx->base.state = STATE_WAIT_NEWLINE; 91 | } 92 | } 93 | break; 94 | 95 | case STATE_TAG_NAME: 96 | if (ch == ':') 97 | { 98 | ctx->base.state = STATE_ATTR_NAME; 99 | ctx->attr_name_len = 0; 100 | } 101 | else if (ch == '\n') 102 | { 103 | ctx->base.state = STATE_INITIAL; 104 | } 105 | else if (ctx->tag_name_len < M3U8_MAX_TAG_NAME_LEN) 106 | { 107 | ctx->tag_name[ctx->tag_name_len] = ch; 108 | ctx->tag_name_len++; 109 | } 110 | break; 111 | 112 | case STATE_ATTR_NAME: 113 | if (ch == '=') 114 | { 115 | ctx->base.state = STATE_ATTR_VALUE; 116 | } 117 | else if (ch == '\n') 118 | { 119 | ctx->base.state = STATE_INITIAL; 120 | } 121 | else if (ctx->attr_name_len < M3U8_MAX_ATTR_NAME_LEN) 122 | { 123 | ctx->attr_name[ctx->attr_name_len] = ch; 124 | ctx->attr_name_len++; 125 | } 126 | break; 127 | 128 | case STATE_ATTR_VALUE: 129 | if (ch == '"') 130 | { 131 | if (ctx->attr_name_len == sizeof(uri_attr_name) - 1 && 132 | ngx_memcmp(ctx->attr_name, uri_attr_name, sizeof(uri_attr_name) - 1) == 0) 133 | { 134 | if (ngx_http_secure_token_m3u8_is_string_in_array( 135 | uri_tags, 136 | ctx->tag_name, 137 | ctx->tag_name_len)) 138 | { 139 | ngx_http_secure_token_url_state_machine_init( 140 | &ctx->base, 141 | 1, 142 | STATE_ATTR_WAIT_DELIM, 143 | '"'); 144 | break; 145 | } 146 | } 147 | ctx->base.state = STATE_ATTR_QUOTED_VALUE; 148 | } 149 | else if (ch == ',') 150 | { 151 | ctx->base.state = STATE_ATTR_NAME; 152 | ctx->attr_name_len = 0; 153 | } 154 | else if (ch == '\n') 155 | { 156 | ctx->base.state = STATE_INITIAL; 157 | } 158 | else 159 | { 160 | // dont care about unquoted attribute values 161 | ctx->base.state = STATE_ATTR_WAIT_DELIM; 162 | } 163 | break; 164 | 165 | case STATE_ATTR_QUOTED_VALUE: 166 | if (ch == '"') 167 | { 168 | ctx->base.state = STATE_ATTR_WAIT_DELIM; 169 | } 170 | else if (ch == '\n') 171 | { 172 | ctx->base.state = STATE_INITIAL; 173 | } 174 | break; 175 | 176 | case STATE_ATTR_WAIT_DELIM: 177 | if (ch == ',') 178 | { 179 | ctx->base.state = STATE_ATTR_NAME; 180 | ctx->attr_name_len = 0; 181 | } 182 | else if (ch == '\n') 183 | { 184 | ctx->base.state = STATE_INITIAL; 185 | } 186 | break; 187 | 188 | case STATE_WAIT_NEWLINE: 189 | if (ch == '\n') 190 | { 191 | ctx->base.state = STATE_INITIAL; 192 | } 193 | break; 194 | 195 | default: 196 | *pos = cur_pos; 197 | rc = ngx_http_secure_token_url_state_machine( 198 | r, 199 | conf, 200 | &ctx->base, 201 | pos, 202 | buffer_end, 203 | output); 204 | 205 | if (ctx->base.last_url_char == '\n') 206 | { 207 | ctx->base.state = STATE_INITIAL; 208 | } 209 | 210 | return rc; 211 | } 212 | } 213 | 214 | *pos = cur_pos; 215 | return NGX_OK; 216 | } 217 | -------------------------------------------------------------------------------- /cdnvideo/ngx_http_secure_token_cdnvideo.c: -------------------------------------------------------------------------------- 1 | #include "ngx_http_secure_token_cdnvideo.h" 2 | #include "../ngx_http_secure_token_filter_module.h" 3 | #include "../ngx_http_secure_token_utils.h" 4 | 5 | #include 6 | 7 | // typedefs 8 | typedef struct { 9 | ngx_http_complex_value_t *acl; 10 | ngx_str_t key; 11 | ngx_str_t md5_param_name; 12 | ngx_str_t exp_param_name; 13 | ngx_http_complex_value_t *ip_address; 14 | ngx_secure_token_time_t end; 15 | } ngx_secure_token_cdnvideo_token_t; 16 | 17 | // globals 18 | static ngx_command_t ngx_http_secure_token_cdnvideo_cmds[] = { 19 | { ngx_string("acl"), 20 | NGX_CONF_TAKE1, 21 | ngx_http_set_complex_value_slot, 22 | 0, 23 | offsetof(ngx_secure_token_cdnvideo_token_t, acl), 24 | NULL }, 25 | 26 | { ngx_string("key"), 27 | NGX_CONF_TAKE1, 28 | ngx_conf_set_str_slot, 29 | 0, 30 | offsetof(ngx_secure_token_cdnvideo_token_t, key), 31 | NULL }, 32 | 33 | { ngx_string("md5_param_name"), 34 | NGX_CONF_TAKE1, 35 | ngx_conf_set_str_slot, 36 | 0, 37 | offsetof(ngx_secure_token_cdnvideo_token_t, md5_param_name), 38 | NULL }, 39 | 40 | { ngx_string("exp_param_name"), 41 | NGX_CONF_TAKE1, 42 | ngx_conf_set_str_slot, 43 | 0, 44 | offsetof(ngx_secure_token_cdnvideo_token_t, exp_param_name), 45 | NULL }, 46 | 47 | { ngx_string("ip_address"), 48 | NGX_CONF_TAKE1, 49 | ngx_http_set_complex_value_slot, 50 | 0, 51 | offsetof(ngx_secure_token_cdnvideo_token_t, ip_address), 52 | NULL }, 53 | 54 | { ngx_string("end"), 55 | NGX_CONF_TAKE1, 56 | ngx_http_secure_token_conf_set_time_slot, 57 | 0, 58 | offsetof(ngx_secure_token_cdnvideo_token_t, end), 59 | NULL }, 60 | }; 61 | 62 | static ngx_int_t 63 | ngx_secure_token_cdnvideo_get_var( 64 | ngx_http_request_t *r, 65 | ngx_http_variable_value_t *v, 66 | uintptr_t data) 67 | { 68 | ngx_secure_token_cdnvideo_token_t* token = (void*)data; 69 | ngx_str_t end_time_str; 70 | ngx_str_t ip_address; 71 | ngx_str_t md5hash; 72 | ngx_str_t base64; 73 | ngx_str_t acl; 74 | ngx_md5_t md5; 75 | ngx_int_t rc; 76 | size_t result_size; 77 | time_t end_time; 78 | u_char* p; 79 | u_char end_time_str_buf[NGX_INT32_LEN]; 80 | u_char md5hash_buf[MD5_DIGEST_LENGTH]; 81 | 82 | ngx_md5_init(&md5); 83 | 84 | // key 85 | ngx_md5_update(&md5, token->key.data, token->key.len); 86 | ngx_md5_update(&md5, ":", 1); 87 | 88 | // end time 89 | end_time = token->end.val; 90 | if (token->end.type == NGX_HTTP_SECURE_TOKEN_TIME_RELATIVE) 91 | { 92 | end_time += ngx_time(); 93 | } 94 | 95 | end_time_str.data = end_time_str_buf; 96 | end_time_str.len = ngx_sprintf(end_time_str_buf, "%uD", end_time) - end_time_str_buf; 97 | 98 | ngx_md5_update(&md5, end_time_str.data, end_time_str.len); 99 | ngx_md5_update(&md5, ":", 1); 100 | 101 | // ip address 102 | if (token->ip_address != NULL) 103 | { 104 | if (ngx_http_complex_value( 105 | r, 106 | token->ip_address, 107 | &ip_address) != NGX_OK) 108 | { 109 | return NGX_ERROR; 110 | } 111 | 112 | ngx_md5_update(&md5, ip_address.data, ip_address.len); 113 | ngx_md5_update(&md5, ":", 1); 114 | } 115 | 116 | // acl 117 | rc = ngx_http_secure_token_get_acl(r, token->acl, &acl); 118 | if (rc != NGX_OK) 119 | { 120 | return rc; 121 | } 122 | ngx_md5_update(&md5, acl.data, acl.len); 123 | 124 | ngx_md5_final(md5hash_buf, &md5); 125 | md5hash.data = md5hash_buf; 126 | md5hash.len = sizeof(md5hash_buf); 127 | 128 | // allocate the result 129 | result_size = token->md5_param_name.len + ngx_base64_encoded_length(MD5_DIGEST_LENGTH) + 130 | token->exp_param_name.len + end_time_str.len + sizeof("=&="); 131 | 132 | p = ngx_pnalloc(r->pool, result_size); 133 | if (p == NULL) 134 | { 135 | return NGX_ERROR; 136 | } 137 | 138 | v->data = p; 139 | 140 | // build the result 141 | p = ngx_copy(p, token->md5_param_name.data, token->md5_param_name.len); 142 | *p++ = '='; 143 | 144 | base64.data = p; 145 | ngx_encode_base64url(&base64, &md5hash); 146 | p += base64.len; 147 | 148 | *p++ = '&'; 149 | p = ngx_copy(p, token->exp_param_name.data, token->exp_param_name.len); 150 | *p++ = '='; 151 | p = ngx_copy(p, end_time_str.data, end_time_str.len); 152 | 153 | *p = '\0'; 154 | 155 | v->len = p - v->data; 156 | v->valid = 1; 157 | v->no_cacheable = 0; 158 | v->not_found = 0; 159 | 160 | return NGX_OK; 161 | } 162 | 163 | char * 164 | ngx_secure_token_cdnvideo_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 165 | { 166 | ngx_secure_token_cdnvideo_token_t* token; 167 | char* rv; 168 | 169 | // init config 170 | token = ngx_pcalloc(cf->pool, sizeof(*token)); 171 | if (token == NULL) 172 | { 173 | return NGX_CONF_ERROR; 174 | } 175 | 176 | token->end.type = NGX_HTTP_SECURE_TOKEN_TIME_UNSET; 177 | 178 | // parse the block 179 | rv = ngx_http_secure_token_conf_block( 180 | cf, 181 | ngx_http_secure_token_cdnvideo_cmds, 182 | token, 183 | ngx_secure_token_cdnvideo_get_var); 184 | if (rv != NGX_CONF_OK) 185 | { 186 | return rv; 187 | } 188 | 189 | // validate required params 190 | if (token->key.data == NULL) 191 | { 192 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 193 | "\"key\" is mandatory for cdnvideo tokens"); 194 | return NGX_CONF_ERROR; 195 | } 196 | 197 | // populate unset optional params 198 | if (token->md5_param_name.data == NULL) 199 | { 200 | ngx_str_set(&token->md5_param_name, "md5"); 201 | } 202 | 203 | if (token->exp_param_name.data == NULL) 204 | { 205 | ngx_str_set(&token->exp_param_name, "e"); 206 | } 207 | 208 | if (token->end.type == NGX_HTTP_SECURE_TOKEN_TIME_UNSET) 209 | { 210 | token->end.type = NGX_HTTP_SECURE_TOKEN_TIME_RELATIVE; 211 | token->end.val = 86400; 212 | } 213 | 214 | return NGX_CONF_OK; 215 | } 216 | -------------------------------------------------------------------------------- /akamai/ngx_http_secure_token_akamai.c: -------------------------------------------------------------------------------- 1 | #include "ngx_http_secure_token_akamai.h" 2 | #include "../ngx_http_secure_token_filter_module.h" 3 | #include "../ngx_http_secure_token_utils.h" 4 | 5 | #include 6 | #include 7 | 8 | // constants 9 | #define TOKEN_FORMAT "st=%uD~exp=%uD~acl=%V" 10 | #define IP_TOKEN_PARAM "ip=%V~" 11 | #define HMAC_PARAM "~hmac=" 12 | 13 | // typedefs 14 | typedef struct { 15 | ngx_http_complex_value_t *acl; 16 | ngx_str_t key; 17 | ngx_str_t param_name; 18 | ngx_http_complex_value_t *ip_address; 19 | ngx_secure_token_time_t start; 20 | ngx_secure_token_time_t end; 21 | } ngx_secure_token_akamai_token_t; 22 | 23 | // globals 24 | static ngx_command_t ngx_http_secure_token_akamai_cmds[] = { 25 | { ngx_string("acl"), 26 | NGX_CONF_TAKE1, 27 | ngx_http_set_complex_value_slot, 28 | 0, 29 | offsetof(ngx_secure_token_akamai_token_t, acl), 30 | NULL }, 31 | 32 | { ngx_string("key"), 33 | NGX_CONF_TAKE1, 34 | ngx_http_secure_token_conf_set_hex_str_slot, 35 | 0, 36 | offsetof(ngx_secure_token_akamai_token_t, key), 37 | NULL }, 38 | 39 | { ngx_string("param_name"), 40 | NGX_CONF_TAKE1, 41 | ngx_conf_set_str_slot, 42 | 0, 43 | offsetof(ngx_secure_token_akamai_token_t, param_name), 44 | NULL }, 45 | 46 | { ngx_string("ip_address"), 47 | NGX_CONF_TAKE1, 48 | ngx_http_set_complex_value_slot, 49 | 0, 50 | offsetof(ngx_secure_token_akamai_token_t, ip_address), 51 | NULL }, 52 | 53 | { ngx_string("start"), 54 | NGX_CONF_TAKE1, 55 | ngx_http_secure_token_conf_set_time_slot, 56 | 0, 57 | offsetof(ngx_secure_token_akamai_token_t, start), 58 | NULL }, 59 | 60 | { ngx_string("end"), 61 | NGX_CONF_TAKE1, 62 | ngx_http_secure_token_conf_set_time_slot, 63 | 0, 64 | offsetof(ngx_secure_token_akamai_token_t, end), 65 | NULL }, 66 | }; 67 | 68 | static ngx_int_t 69 | ngx_secure_token_akamai_get_var( 70 | ngx_http_request_t *r, 71 | ngx_http_variable_value_t *v, 72 | uintptr_t data) 73 | { 74 | ngx_secure_token_akamai_token_t* token = (void*)data; 75 | time_t start_time; 76 | time_t end_time; 77 | u_char hash[EVP_MAX_MD_SIZE]; 78 | unsigned hash_len; 79 | ngx_str_t signed_part; 80 | ngx_str_t ip_address; 81 | ngx_str_t acl; 82 | size_t result_size; 83 | u_char* p; 84 | ngx_int_t rc; 85 | 86 | // get the acl 87 | rc = ngx_http_secure_token_get_acl(r, token->acl, &acl); 88 | if (rc != NGX_OK) 89 | { 90 | return rc; 91 | } 92 | 93 | // get the result size 94 | result_size = token->param_name.len + 1 + sizeof(TOKEN_FORMAT) + 95 | 2 * NGX_INT32_LEN + acl.len + sizeof(HMAC_PARAM) - 1 + EVP_MAX_MD_SIZE * 2 + 1; 96 | 97 | // get the ip address 98 | if (token->ip_address != NULL) 99 | { 100 | if (ngx_http_complex_value( 101 | r, 102 | token->ip_address, 103 | &ip_address) != NGX_OK) 104 | { 105 | return NGX_ERROR; 106 | } 107 | 108 | result_size += sizeof(IP_TOKEN_PARAM) + ip_address.len; 109 | } 110 | 111 | // allocate the result 112 | p = ngx_pnalloc(r->pool, result_size); 113 | if (p == NULL) 114 | { 115 | return NGX_ERROR; 116 | } 117 | 118 | v->data = p; 119 | 120 | // get the start / end time (mandatory fields) 121 | start_time = token->start.val; 122 | if (token->start.type == NGX_HTTP_SECURE_TOKEN_TIME_RELATIVE) 123 | { 124 | start_time += ngx_time(); 125 | } 126 | 127 | end_time = token->end.val; 128 | if (token->end.type == NGX_HTTP_SECURE_TOKEN_TIME_RELATIVE) 129 | { 130 | end_time += ngx_time(); 131 | } 132 | 133 | // build the result 134 | p = ngx_copy(p, token->param_name.data, token->param_name.len); 135 | *p++ = '='; 136 | 137 | signed_part.data = p; 138 | if (token->ip_address != NULL) 139 | { 140 | p = ngx_sprintf(p, IP_TOKEN_PARAM, &ip_address); 141 | } 142 | p = ngx_sprintf(p, TOKEN_FORMAT, start_time, end_time, &acl); 143 | signed_part.len = p - signed_part.data; 144 | 145 | if (HMAC(EVP_sha256(), token->key.data, token->key.len, signed_part.data, signed_part.len, hash, &hash_len) == NULL) 146 | { 147 | return NGX_ERROR; 148 | } 149 | 150 | p = ngx_copy(p, HMAC_PARAM, sizeof(HMAC_PARAM) - 1); 151 | p = ngx_hex_dump(p, hash, hash_len); 152 | *p = '\0'; 153 | 154 | v->len = p - v->data; 155 | v->valid = 1; 156 | v->no_cacheable = 0; 157 | v->not_found = 0; 158 | 159 | return NGX_OK; 160 | } 161 | 162 | char * 163 | ngx_secure_token_akamai_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 164 | { 165 | ngx_secure_token_akamai_token_t* token; 166 | char* rv; 167 | 168 | // init config 169 | token = ngx_pcalloc(cf->pool, sizeof(*token)); 170 | if (token == NULL) 171 | { 172 | return NGX_CONF_ERROR; 173 | } 174 | 175 | token->start.type = NGX_HTTP_SECURE_TOKEN_TIME_UNSET; 176 | token->end.type = NGX_HTTP_SECURE_TOKEN_TIME_UNSET; 177 | 178 | // parse the block 179 | rv = ngx_http_secure_token_conf_block( 180 | cf, 181 | ngx_http_secure_token_akamai_cmds, 182 | token, 183 | ngx_secure_token_akamai_get_var); 184 | if (rv != NGX_CONF_OK) 185 | { 186 | return rv; 187 | } 188 | 189 | // validate required params 190 | if (token->key.data == NULL) 191 | { 192 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 193 | "\"key\" is mandatory for akamai tokens"); 194 | return NGX_CONF_ERROR; 195 | } 196 | 197 | // populate unset optional params 198 | if (token->param_name.data == NULL) 199 | { 200 | ngx_str_set(&token->param_name, "__hdnea__"); 201 | } 202 | 203 | if (token->start.type == NGX_HTTP_SECURE_TOKEN_TIME_UNSET) 204 | { 205 | token->start.type = NGX_HTTP_SECURE_TOKEN_TIME_RELATIVE; 206 | } 207 | 208 | if (token->end.type == NGX_HTTP_SECURE_TOKEN_TIME_UNSET) 209 | { 210 | token->end.type = NGX_HTTP_SECURE_TOKEN_TIME_RELATIVE; 211 | token->end.val = 86400; 212 | } 213 | 214 | return NGX_CONF_OK; 215 | } 216 | -------------------------------------------------------------------------------- /cloudfront/ngx_http_secure_token_cloudfront.c: -------------------------------------------------------------------------------- 1 | #include "ngx_http_secure_token_cloudfront.h" 2 | #include "../ngx_http_secure_token_filter_module.h" 3 | #include "../ngx_http_secure_token_utils.h" 4 | #include 5 | 6 | // constants 7 | #define POLICY_HEADER "{\"Statement\":[{\"Resource\":\"%V\",\"Condition\":{\"DateLessThan\":{\"AWS:EpochTime\":%uD}" // DateLessThan is required 8 | #define POLICY_CONDITION_IPADDRESS ",\"IpAddress\":{\"AWS:SourceIp\":\"%V\"}" 9 | #define POLICY_FOOTER "}}]}" 10 | 11 | #define POLICY_PARAM "Policy=" 12 | #define SIGNATURE_PARAM "&Signature=" 13 | #define KEY_PAIR_ID_PARAM "&Key-Pair-Id=" 14 | 15 | // typedefs 16 | typedef struct { 17 | ngx_http_complex_value_t *acl; 18 | ngx_str_t key_pair_id; 19 | EVP_PKEY *private_key; 20 | ngx_http_complex_value_t *ip_address; 21 | ngx_secure_token_time_t end; 22 | } ngx_secure_token_cloudfront_token_t; 23 | 24 | // globals 25 | static ngx_command_t ngx_http_secure_token_cloudfront_cmds[] = { 26 | { ngx_string("acl"), 27 | NGX_CONF_TAKE1, 28 | ngx_http_set_complex_value_slot, 29 | 0, 30 | offsetof(ngx_secure_token_cloudfront_token_t, acl), 31 | NULL }, 32 | 33 | { ngx_string("key_pair_id"), 34 | NGX_CONF_TAKE1, 35 | ngx_conf_set_str_slot, 36 | 0, 37 | offsetof(ngx_secure_token_cloudfront_token_t, key_pair_id), 38 | NULL }, 39 | 40 | { ngx_string("private_key_file"), 41 | NGX_CONF_TAKE1, 42 | ngx_http_secure_token_conf_set_private_key_slot, 43 | 0, 44 | offsetof(ngx_secure_token_cloudfront_token_t, private_key), 45 | NULL }, 46 | 47 | { ngx_string("ip_address"), 48 | NGX_CONF_TAKE1, 49 | ngx_http_set_complex_value_slot, 50 | 0, 51 | offsetof(ngx_secure_token_cloudfront_token_t, ip_address), 52 | NULL }, 53 | 54 | { ngx_string("end"), 55 | NGX_CONF_TAKE1, 56 | ngx_http_secure_token_conf_set_time_slot, 57 | 0, 58 | offsetof(ngx_secure_token_cloudfront_token_t, end), 59 | NULL }, 60 | }; 61 | 62 | // copied from ngx_string, changed the charset: + => -, / => ~, = => _ 63 | static u_char* 64 | ngx_encode_base64_cloudfront(u_char *d, ngx_str_t *src) 65 | { 66 | static u_char basis64[] = 67 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-~"; 68 | 69 | return ngx_http_secure_token_encode_base64_internal(d, src, basis64, '_'); 70 | } 71 | 72 | static ngx_int_t 73 | ngx_secure_token_cloudfront_get_var( 74 | ngx_http_request_t *r, 75 | ngx_http_variable_value_t *v, 76 | uintptr_t data) 77 | { 78 | ngx_secure_token_cloudfront_token_t* token = (void*)data; 79 | ngx_str_t ip_address; 80 | ngx_str_t signature; 81 | ngx_str_t policy; 82 | ngx_str_t acl; 83 | ngx_int_t rc; 84 | size_t policy_size; 85 | time_t end_time; 86 | u_char* p; 87 | 88 | // get the acl 89 | rc = ngx_http_secure_token_get_acl(r, token->acl, &acl); 90 | if (rc != NGX_OK) 91 | { 92 | return rc; 93 | } 94 | 95 | // get the size of the policy json 96 | policy_size = sizeof(POLICY_HEADER) + sizeof(POLICY_FOOTER) + acl.len + NGX_INT32_LEN; 97 | if (token->ip_address != NULL) 98 | { 99 | if (ngx_http_complex_value( 100 | r, 101 | token->ip_address, 102 | &ip_address) != NGX_OK) 103 | { 104 | return NGX_ERROR; 105 | } 106 | 107 | policy_size += sizeof(POLICY_CONDITION_IPADDRESS) + ip_address.len; 108 | } 109 | 110 | // build the policy json 111 | policy.data = ngx_pnalloc(r->pool, policy_size); 112 | if (policy.data == NULL) 113 | { 114 | return NGX_ERROR; 115 | } 116 | 117 | // get the end time 118 | end_time = token->end.val; 119 | if (token->end.type == NGX_HTTP_SECURE_TOKEN_TIME_RELATIVE) 120 | { 121 | end_time += ngx_time(); 122 | } 123 | 124 | p = ngx_sprintf(policy.data, POLICY_HEADER, &acl, end_time); 125 | if (token->ip_address != NULL) 126 | { 127 | p = ngx_sprintf(p, POLICY_CONDITION_IPADDRESS, &ip_address); 128 | } 129 | p = ngx_copy(p, POLICY_FOOTER, sizeof(POLICY_FOOTER) - 1); 130 | 131 | policy.len = p - policy.data; 132 | 133 | // sign the policy 134 | rc = ngx_http_secure_token_sign(r, token->private_key, &policy, &signature); 135 | if (rc != NGX_OK) 136 | { 137 | return rc; 138 | } 139 | 140 | // build the token 141 | p = ngx_pnalloc( 142 | r->pool, 143 | sizeof(POLICY_PARAM) - 1 + 144 | ngx_base64_encoded_length(policy.len) + 145 | sizeof(SIGNATURE_PARAM) - 1 + 146 | ngx_base64_encoded_length(signature.len) + 147 | sizeof(KEY_PAIR_ID_PARAM) - 1 + 148 | token->key_pair_id.len + 1); 149 | if (p == NULL) 150 | { 151 | return NGX_ERROR; 152 | } 153 | 154 | v->data = p; 155 | 156 | p = ngx_copy(p, POLICY_PARAM, sizeof(POLICY_PARAM) - 1); 157 | p = ngx_encode_base64_cloudfront(p, &policy); 158 | p = ngx_copy(p, SIGNATURE_PARAM, sizeof(SIGNATURE_PARAM) - 1); 159 | p = ngx_encode_base64_cloudfront(p, &signature); 160 | p = ngx_copy(p, KEY_PAIR_ID_PARAM, sizeof(KEY_PAIR_ID_PARAM) - 1); 161 | p = ngx_copy(p, token->key_pair_id.data, token->key_pair_id.len); 162 | *p = '\0'; 163 | 164 | v->len = p - v->data; 165 | v->valid = 1; 166 | v->no_cacheable = 0; 167 | v->not_found = 0; 168 | 169 | return NGX_OK; 170 | } 171 | 172 | char * 173 | ngx_secure_token_cloudfront_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 174 | { 175 | ngx_secure_token_cloudfront_token_t* token; 176 | char* rv; 177 | 178 | // init config 179 | token = ngx_pcalloc(cf->pool, sizeof(*token)); 180 | if (token == NULL) 181 | { 182 | return NGX_CONF_ERROR; 183 | } 184 | 185 | token->end.type = NGX_HTTP_SECURE_TOKEN_TIME_UNSET; 186 | 187 | // parse the block 188 | rv = ngx_http_secure_token_conf_block( 189 | cf, 190 | ngx_http_secure_token_cloudfront_cmds, 191 | token, 192 | ngx_secure_token_cloudfront_get_var); 193 | if (rv != NGX_CONF_OK) 194 | { 195 | return rv; 196 | } 197 | 198 | // validate required params 199 | if (token->key_pair_id.data == NULL) 200 | { 201 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 202 | "\"key_pair_id\" is mandatory for cloudfront tokens"); 203 | return NGX_CONF_ERROR; 204 | } 205 | 206 | if (token->private_key == NULL) 207 | { 208 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 209 | "\"private_key\" is mandatory for cloudfront tokens"); 210 | return NGX_CONF_ERROR; 211 | } 212 | 213 | // populate unset optional params 214 | if (token->end.type == NGX_HTTP_SECURE_TOKEN_TIME_UNSET) 215 | { 216 | token->end.type = NGX_HTTP_SECURE_TOKEN_TIME_RELATIVE; 217 | token->end.val = 86400; 218 | } 219 | 220 | return NGX_CONF_OK; 221 | } 222 | -------------------------------------------------------------------------------- /chinacache/ngx_http_secure_token_chinacache.c: -------------------------------------------------------------------------------- 1 | #include "ngx_http_secure_token_chinacache.h" 2 | #include "../ngx_http_secure_token_filter_module.h" 3 | #include "../ngx_http_secure_token_utils.h" 4 | 5 | #include 6 | #include 7 | 8 | // constants 9 | #define TOKEN_PART1 "ACL=" 10 | #define TOKEN_PART2 "&P1=%V&P2=%V&P3=%uD&P4=" 11 | 12 | // typedefs 13 | typedef struct { 14 | ngx_http_complex_value_t *acl; 15 | ngx_str_t key; 16 | ngx_str_t key_id; 17 | ngx_uint_t algorithm; 18 | ngx_secure_token_time_t end; 19 | } ngx_secure_token_chinacache_token_t; 20 | 21 | enum { 22 | ALGO_HMACSHA1 = 1, 23 | ALGO_HMACSHA256 = 2 24 | }; 25 | 26 | // constants 27 | static ngx_conf_enum_t algorithms[] = { 28 | { ngx_string("hmacsha1"), ALGO_HMACSHA1 }, 29 | { ngx_string("hmacsha256"), ALGO_HMACSHA256 }, 30 | { ngx_null_string, 0 } 31 | }; 32 | 33 | // globals 34 | static ngx_command_t ngx_http_secure_token_chinacache_cmds[] = { 35 | { ngx_string("acl"), 36 | NGX_CONF_TAKE1, 37 | ngx_http_set_complex_value_slot, 38 | 0, 39 | offsetof(ngx_secure_token_chinacache_token_t, acl), 40 | NULL }, 41 | 42 | { ngx_string("key"), 43 | NGX_CONF_TAKE1, 44 | ngx_conf_set_str_slot, 45 | 0, 46 | offsetof(ngx_secure_token_chinacache_token_t, key), 47 | NULL }, 48 | 49 | { ngx_string("key_id"), 50 | NGX_CONF_TAKE1, 51 | ngx_conf_set_str_slot, 52 | 0, 53 | offsetof(ngx_secure_token_chinacache_token_t, key_id), 54 | NULL }, 55 | 56 | { ngx_string("algorithm"), 57 | NGX_CONF_TAKE1, 58 | ngx_conf_set_enum_slot, 59 | 0, 60 | offsetof(ngx_secure_token_chinacache_token_t, algorithm), 61 | algorithms }, 62 | 63 | { ngx_string("end"), 64 | NGX_CONF_TAKE1, 65 | ngx_http_secure_token_conf_set_time_slot, 66 | 0, 67 | offsetof(ngx_secure_token_chinacache_token_t, end), 68 | NULL }, 69 | }; 70 | 71 | static ngx_int_t 72 | ngx_secure_token_chinacache_get_var( 73 | ngx_http_request_t *r, 74 | ngx_http_variable_value_t *v, 75 | uintptr_t data) 76 | { 77 | ngx_secure_token_chinacache_token_t* token = (void*)data; 78 | const EVP_MD* digest; 79 | ngx_str_t hash_base64_str; 80 | unsigned hash_len; 81 | ngx_str_t signed_part; 82 | ngx_str_t expiry_str; 83 | ngx_str_t hash_str; 84 | ngx_str_t acl; 85 | uintptr_t hash_escape; 86 | uintptr_t acl_escape; 87 | u_char hash_base64_buf[ngx_base64_encoded_length(EVP_MAX_MD_SIZE)]; 88 | u_char expiry_buf[NGX_TIME_T_LEN]; 89 | u_char hash_buf[EVP_MAX_MD_SIZE]; 90 | size_t result_size; 91 | time_t end_time; 92 | u_char* p; 93 | ngx_int_t rc; 94 | 95 | // get the acl 96 | rc = ngx_http_secure_token_get_acl(r, token->acl, &acl); 97 | if (rc != NGX_OK) 98 | { 99 | return rc; 100 | } 101 | 102 | // get the end time 103 | end_time = token->end.val; 104 | if (token->end.type == NGX_HTTP_SECURE_TOKEN_TIME_RELATIVE) 105 | { 106 | end_time += ngx_time(); 107 | } 108 | 109 | expiry_str.data = expiry_buf; 110 | expiry_str.len = ngx_sprintf(expiry_buf, "%T", end_time) - expiry_buf; 111 | 112 | // calc the signature 113 | switch (token->algorithm) 114 | { 115 | case ALGO_HMACSHA1: 116 | digest = EVP_sha1(); 117 | break; 118 | 119 | case ALGO_HMACSHA256: 120 | digest = EVP_sha256(); 121 | break; 122 | 123 | default: 124 | return NGX_ERROR; 125 | } 126 | 127 | p = ngx_pnalloc(r->pool, acl.len + expiry_str.len); 128 | if (p == NULL) 129 | { 130 | return NGX_ERROR; 131 | } 132 | 133 | signed_part.data = p; 134 | p = ngx_copy(p, acl.data, acl.len); 135 | p = ngx_copy(p, expiry_str.data, expiry_str.len); 136 | signed_part.len = p - signed_part.data; 137 | 138 | if (HMAC(digest, token->key.data, token->key.len, signed_part.data, signed_part.len, hash_buf, &hash_len) == NULL) 139 | { 140 | return NGX_ERROR; 141 | } 142 | 143 | // base64 encode 144 | hash_str.data = hash_buf; 145 | hash_str.len = hash_len; 146 | hash_base64_str.data = hash_base64_buf; 147 | ngx_encode_base64(&hash_base64_str, &hash_str); 148 | 149 | hash_escape = 2 * ngx_escape_uri(NULL, hash_base64_str.data, hash_base64_str.len, NGX_ESCAPE_URI_COMPONENT); 150 | acl_escape = 2 * ngx_escape_uri(NULL, acl.data, acl.len, NGX_ESCAPE_URI_COMPONENT); 151 | 152 | // get the result size 153 | result_size = sizeof(TOKEN_PART1) + sizeof(TOKEN_PART2) + acl.len + acl_escape + 154 | expiry_str.len + token->key_id.len + NGX_INT32_LEN + 155 | hash_base64_str.len + hash_escape; 156 | 157 | // allocate the result 158 | p = ngx_pnalloc(r->pool, result_size); 159 | if (p == NULL) 160 | { 161 | return NGX_ERROR; 162 | } 163 | 164 | v->data = p; 165 | 166 | // build the result 167 | p = ngx_copy(p, TOKEN_PART1, sizeof(TOKEN_PART1) - 1); 168 | 169 | p = (u_char*)ngx_escape_uri(p, acl.data, acl.len, NGX_ESCAPE_URI_COMPONENT); 170 | 171 | p = ngx_sprintf(p, TOKEN_PART2, &expiry_str, &token->key_id, (uint32_t)token->algorithm); 172 | if (hash_escape) 173 | { 174 | p = (u_char*)ngx_escape_uri(p, hash_base64_str.data, hash_base64_str.len, NGX_ESCAPE_URI_COMPONENT); 175 | } 176 | else 177 | { 178 | p = ngx_copy(p, hash_base64_str.data, hash_base64_str.len); 179 | } 180 | 181 | *p = '\0'; 182 | 183 | v->len = p - v->data; 184 | v->valid = 1; 185 | v->no_cacheable = 0; 186 | v->not_found = 0; 187 | 188 | return NGX_OK; 189 | } 190 | 191 | char * 192 | ngx_secure_token_chinacache_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 193 | { 194 | ngx_secure_token_chinacache_token_t* token; 195 | char* rv; 196 | 197 | // init config 198 | token = ngx_pcalloc(cf->pool, sizeof(*token)); 199 | if (token == NULL) 200 | { 201 | return NGX_CONF_ERROR; 202 | } 203 | 204 | token->algorithm = NGX_CONF_UNSET_UINT; 205 | token->end.type = NGX_HTTP_SECURE_TOKEN_TIME_UNSET; 206 | 207 | // parse the block 208 | rv = ngx_http_secure_token_conf_block( 209 | cf, 210 | ngx_http_secure_token_chinacache_cmds, 211 | token, 212 | ngx_secure_token_chinacache_get_var); 213 | if (rv != NGX_CONF_OK) 214 | { 215 | return rv; 216 | } 217 | 218 | // validate required params 219 | if (token->key.data == NULL) 220 | { 221 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 222 | "\"key\" is mandatory for chinacache tokens"); 223 | return NGX_CONF_ERROR; 224 | } 225 | 226 | if (token->key_id.data == NULL) 227 | { 228 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 229 | "\"key_id\" is mandatory for chinacache tokens"); 230 | return NGX_CONF_ERROR; 231 | } 232 | 233 | // populate unset optional params 234 | if (token->algorithm == NGX_CONF_UNSET_UINT) 235 | { 236 | token->algorithm = ALGO_HMACSHA256; 237 | } 238 | 239 | if (token->end.type == NGX_HTTP_SECURE_TOKEN_TIME_UNSET) 240 | { 241 | token->end.type = NGX_HTTP_SECURE_TOKEN_TIME_RELATIVE; 242 | token->end.val = 86400; 243 | } 244 | 245 | return NGX_CONF_OK; 246 | } 247 | -------------------------------------------------------------------------------- /iijpta/ngx_http_secure_token_iijpta.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "ngx_http_secure_token_iijpta.h" 4 | #include "../ngx_http_secure_token_filter_module.h" 5 | #include "../ngx_http_secure_token_utils.h" 6 | 7 | #include 8 | 9 | // macros 10 | #define set_be32(p, dw) \ 11 | { \ 12 | ((u_char*)p)[0] = ((dw) >> 24) & 0xFF; \ 13 | ((u_char*)p)[1] = ((dw) >> 16) & 0xFF; \ 14 | ((u_char*)p)[2] = ((dw) >> 8) & 0xFF; \ 15 | ((u_char*)p)[3] = (dw)& 0xFF; \ 16 | } 17 | 18 | #define set_be64(p, qw) \ 19 | { \ 20 | set_be32(p, (qw) >> 32); \ 21 | set_be32(p + 4, (qw)); \ 22 | } 23 | 24 | // constants 25 | #define CRC32_SIZE 4 26 | #define EXPIRY_SIZE 8 27 | #define PATH_LIMIT 1024 28 | #define COOKIE_ATTR_SIZE (sizeof("; Expires=Thu, 31-Dec-2019 23:59:59 GMT; Max-Age=") - 1 + NGX_TIME_T_LEN) 29 | 30 | // typedefs 31 | typedef struct { 32 | ngx_str_t key; 33 | ngx_str_t iv; 34 | ngx_http_complex_value_t *acl; 35 | ngx_secure_token_time_t end; 36 | } ngx_secure_token_iijpta_token_t; 37 | 38 | typedef struct { 39 | u_char crc[CRC32_SIZE]; 40 | u_char expiry[EXPIRY_SIZE]; 41 | } ngx_http_secure_token_iijpta_header_t; 42 | 43 | static ngx_conf_num_bounds_t ngx_http_secure_token_iijpta_key_bounds = { 44 | ngx_conf_check_str_len_bounds, 16, 16 45 | }; 46 | 47 | static ngx_conf_num_bounds_t ngx_http_secure_token_iijpta_iv_bounds = { 48 | ngx_conf_check_str_len_bounds, 16, 16 49 | }; 50 | 51 | // globals 52 | static ngx_command_t ngx_http_secure_token_iijpta_cmds[] = { 53 | { ngx_string("key"), 54 | NGX_CONF_TAKE1, 55 | ngx_http_secure_token_conf_set_hex_str_slot, 56 | 0, 57 | offsetof(ngx_secure_token_iijpta_token_t, key), 58 | &ngx_http_secure_token_iijpta_key_bounds }, 59 | 60 | { ngx_string("iv"), 61 | NGX_CONF_TAKE1, 62 | ngx_http_secure_token_conf_set_hex_str_slot, 63 | 0, 64 | offsetof(ngx_secure_token_iijpta_token_t, iv), 65 | &ngx_http_secure_token_iijpta_iv_bounds }, 66 | 67 | { ngx_string("acl"), 68 | NGX_CONF_TAKE1, 69 | ngx_http_set_complex_value_slot, 70 | 0, 71 | offsetof(ngx_secure_token_iijpta_token_t, acl), 72 | NULL }, 73 | 74 | { ngx_string("end"), 75 | NGX_CONF_TAKE1, 76 | ngx_http_secure_token_conf_set_time_slot, 77 | 0, 78 | offsetof(ngx_secure_token_iijpta_token_t, end), 79 | NULL }, 80 | }; 81 | 82 | static ngx_int_t 83 | ngx_http_secure_token_get_acl_iijpta(ngx_http_request_t *r, ngx_http_complex_value_t *acl_conf, ngx_str_t* acl) 84 | { 85 | // get the acl 86 | if (acl_conf != NULL) 87 | { 88 | if (ngx_http_complex_value(r, acl_conf, acl) != NGX_OK) 89 | { 90 | return NGX_ERROR; 91 | } 92 | } 93 | else 94 | { 95 | // the default is '/*' 96 | ngx_str_set(acl, "/*"); 97 | } 98 | 99 | return NGX_OK; 100 | } 101 | 102 | static ngx_int_t 103 | ngx_secure_token_iijpta_get_var( 104 | ngx_http_request_t *r, 105 | ngx_http_variable_value_t *v, 106 | uintptr_t data) 107 | { 108 | EVP_CIPHER_CTX *ctx = NULL; 109 | uint32_t crc; 110 | ngx_secure_token_iijpta_token_t* token = (void*)data; 111 | ngx_http_secure_token_iijpta_header_t hdr; 112 | ngx_http_secure_token_loc_conf_t *conf; 113 | size_t in_len; 114 | size_t size; 115 | u_char *p; 116 | u_char *out; 117 | int out_len; 118 | u_char *outp; 119 | uint64_t end; 120 | ngx_str_t acl; 121 | ngx_int_t rc; 122 | 123 | conf = ngx_http_get_module_loc_conf(r, ngx_http_secure_token_filter_module); 124 | 125 | rc = ngx_http_secure_token_get_acl_iijpta(r, token->acl, &acl); 126 | if (rc != NGX_OK) 127 | { 128 | return rc; 129 | } 130 | 131 | if (acl.len > PATH_LIMIT) 132 | { 133 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 134 | "ngx_secure_token_iijpta_get_var: acl is too long for the iijpta token"); 135 | return NGX_ERROR; 136 | } 137 | 138 | end = token->end.val; 139 | if (token->end.type == NGX_HTTP_SECURE_TOKEN_TIME_RELATIVE) 140 | { 141 | end += ngx_time(); 142 | } 143 | set_be64(hdr.expiry, end); 144 | 145 | ngx_crc32_init(crc); 146 | ngx_crc32_update(&crc, hdr.expiry, EXPIRY_SIZE); 147 | ngx_crc32_update(&crc, acl.data, acl.len); 148 | ngx_crc32_final(crc); 149 | set_be32(hdr.crc, crc); 150 | 151 | ctx = EVP_CIPHER_CTX_new(); 152 | if (ctx == NULL) 153 | { 154 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 155 | "ngx_secure_token_iijpta_get_var: EVP_CIPHER_CTX_new failed"); 156 | return NGX_ERROR; 157 | } 158 | 159 | if (!EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, token->key.data, token->iv.data)) 160 | { 161 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 162 | "ngx_secure_token_iijpta_get_var: EVP_EncryptInit_ex failed"); 163 | goto error; 164 | } 165 | 166 | in_len = sizeof(ngx_http_secure_token_iijpta_header_t) + acl.len; 167 | // in_len rounded up to block + one block for padding 168 | out = ngx_pnalloc(r->pool, (in_len & ~0xf) + 0x10); 169 | if (out == NULL) 170 | { 171 | goto error; 172 | } 173 | 174 | outp = out; 175 | if (!EVP_EncryptUpdate(ctx, outp, &out_len, (void *)&hdr, sizeof(hdr))) 176 | { 177 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 178 | "ngx_secure_token_iijpta_get_var: EVP_EncryptUpdate failed (1)"); 179 | goto error; 180 | } 181 | outp += out_len; 182 | 183 | if (!EVP_EncryptUpdate(ctx, outp, &out_len, acl.data, acl.len)) 184 | { 185 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 186 | "ngx_secure_token_iijpta_get_var: EVP_EncryptUpdate failed (2)"); 187 | goto error; 188 | } 189 | outp += out_len; 190 | 191 | if (!EVP_EncryptFinal_ex(ctx, outp, &out_len)) 192 | { 193 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 194 | "ngx_secure_token_iijpta_get_var: EVP_EncryptFinal_ex failed"); 195 | goto error; 196 | } 197 | outp += out_len; 198 | out_len = outp - out; 199 | 200 | size = sizeof("pta=") + (out_len * 2); 201 | if (conf->avoid_cookies == 0) 202 | { 203 | size += COOKIE_ATTR_SIZE; 204 | } 205 | 206 | p = ngx_pnalloc(r->pool, size); 207 | if (p == NULL) 208 | { 209 | goto error; 210 | } 211 | v->data = p; 212 | p = ngx_copy(p, "pta=", sizeof("pta=") - 1); 213 | p = ngx_hex_dump(p, out, out_len); 214 | 215 | if (conf->avoid_cookies == 0) 216 | { 217 | p = ngx_copy(p, "; Expires=", sizeof("; Expires=") - 1); 218 | p = ngx_http_cookie_time(p, end); 219 | p = ngx_sprintf(p, "; Max-Age=%T", end - ngx_time()); 220 | } 221 | 222 | *p = '\0'; 223 | 224 | v->len = p - v->data; 225 | v->valid = 1; 226 | v->no_cacheable = 0; 227 | v->not_found = 0; 228 | 229 | EVP_CIPHER_CTX_free(ctx); 230 | 231 | return NGX_OK; 232 | 233 | error: 234 | EVP_CIPHER_CTX_free(ctx); 235 | return NGX_ERROR; 236 | } 237 | 238 | char * 239 | ngx_secure_token_iijpta_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 240 | { 241 | ngx_secure_token_iijpta_token_t* token; 242 | char* rv; 243 | 244 | // init config 245 | token = ngx_pcalloc(cf->pool, sizeof(*token)); 246 | if (token == NULL) 247 | { 248 | return NGX_CONF_ERROR; 249 | } 250 | 251 | token->end.type = NGX_HTTP_SECURE_TOKEN_TIME_UNSET; 252 | 253 | // parse the block 254 | rv = ngx_http_secure_token_conf_block( 255 | cf, 256 | ngx_http_secure_token_iijpta_cmds, 257 | token, 258 | ngx_secure_token_iijpta_get_var); 259 | if (rv != NGX_CONF_OK) 260 | { 261 | return rv; 262 | } 263 | 264 | if (token->end.type == NGX_HTTP_SECURE_TOKEN_TIME_UNSET) 265 | { 266 | token->end.type = NGX_HTTP_SECURE_TOKEN_TIME_RELATIVE; 267 | token->end.val = 86400; 268 | } 269 | 270 | return NGX_CONF_OK; 271 | } 272 | -------------------------------------------------------------------------------- /broadpeak/ngx_http_secure_token_broadpeak.c: -------------------------------------------------------------------------------- 1 | #include "ngx_http_secure_token_broadpeak.h" 2 | #include "../ngx_http_secure_token_filter_module.h" 3 | #include "../ngx_http_secure_token_utils.h" 4 | #include 5 | 6 | 7 | // typedefs 8 | typedef struct { 9 | ngx_http_complex_value_t* acl; 10 | ngx_http_complex_value_t* key; 11 | ngx_str_t param_name; 12 | ngx_secure_token_time_t start; 13 | ngx_secure_token_time_t end; 14 | ngx_http_complex_value_t* session_start; 15 | ngx_http_complex_value_t* session_end; 16 | ngx_http_complex_value_t* additional_querylist; 17 | } ngx_secure_token_broadpeak_token_t; 18 | 19 | 20 | // globals 21 | static ngx_command_t ngx_http_secure_token_broadpeak_cmds[] = { 22 | { ngx_string("acl"), 23 | NGX_CONF_TAKE1, 24 | ngx_http_set_complex_value_slot, 25 | 0, 26 | offsetof(ngx_secure_token_broadpeak_token_t, acl), 27 | NULL }, 28 | 29 | { ngx_string("key"), 30 | NGX_CONF_TAKE1, 31 | ngx_http_set_complex_value_slot, 32 | 0, 33 | offsetof(ngx_secure_token_broadpeak_token_t, key), 34 | NULL }, 35 | 36 | { ngx_string("param_name"), 37 | NGX_CONF_TAKE1, 38 | ngx_conf_set_str_slot, 39 | 0, 40 | offsetof(ngx_secure_token_broadpeak_token_t, param_name), 41 | NULL }, 42 | 43 | { ngx_string("start"), 44 | NGX_CONF_TAKE1, 45 | ngx_http_secure_token_conf_set_time_slot, 46 | 0, 47 | offsetof(ngx_secure_token_broadpeak_token_t, start), 48 | NULL }, 49 | 50 | { ngx_string("end"), 51 | NGX_CONF_TAKE1, 52 | ngx_http_secure_token_conf_set_time_slot, 53 | 0, 54 | offsetof(ngx_secure_token_broadpeak_token_t, end), 55 | NULL }, 56 | 57 | { ngx_string("session_start"), 58 | NGX_CONF_TAKE1, 59 | ngx_http_set_complex_value_slot, 60 | 0, 61 | offsetof(ngx_secure_token_broadpeak_token_t, session_start), 62 | NULL }, 63 | 64 | { ngx_string("session_end"), 65 | NGX_CONF_TAKE1, 66 | ngx_http_set_complex_value_slot, 67 | 0, 68 | offsetof(ngx_secure_token_broadpeak_token_t, session_end), 69 | NULL }, 70 | 71 | { ngx_string("additional_querylist"), 72 | NGX_CONF_TAKE1, 73 | ngx_http_set_complex_value_slot, 74 | 0, 75 | offsetof(ngx_secure_token_broadpeak_token_t, additional_querylist), 76 | NULL }, 77 | }; 78 | 79 | 80 | static uint64_t 81 | ngx_secure_token_broadpeak_scramble(uint64_t se) 82 | { 83 | return 84 | (((se & 0x0800000000000000) >> 53) | 85 | ((se & 0x2400000000000000) >> 50) | 86 | ((se & 0x0000600000000000) >> 44) | 87 | ((se & 0x0000800000000000) >> 43) | 88 | ((se & 0x0020000000000000) >> 41) | 89 | ((se & 0x0000100000000000) >> 30) | 90 | ((se & 0x0000004000000000) >> 28) | 91 | ((se & 0x0042000000000000) >> 26) | 92 | ((se & 0x0000000002000000) >> 25) | 93 | ((se & 0x0000000020000000) >> 24) | 94 | ((se & 0x0004009000000000) >> 23) | 95 | ((se & 0x1000000000000000) >> 21) | 96 | ((se & 0x0010000000000000) >> 20) | 97 | ((se & 0x0100000000000000) >> 18) | 98 | ((se & 0x0080000400000000) >> 14) | 99 | ((se & 0x8000000000000000) >> 13) | 100 | ((se & 0x0000000000002000) >> 10) | 101 | ((se & 0x0000000010000000) >> 7) | 102 | ((se & 0x0000010000000000) >> 6) | 103 | ((se & 0x4000002000000000) >> 4) | 104 | ((se & 0x0000000000001000) >> 3) | 105 | ((se & 0x0200000100000200) >> 2) | 106 | (se & 0x0000000080020000) | 107 | ((se & 0x0001000000000000) << 1) | 108 | ((se & 0x0000000008000000) << 2) | 109 | ((se & 0x0000000000010000) << 3) | 110 | ((se & 0x0000080000100000) << 5) | 111 | ((se & 0x0008040000000000) << 10) | 112 | ((se & 0x0000000001008000) << 11) | 113 | ((se & 0x0000000000000008) << 12) | 114 | ((se & 0x0000000000400000) << 15) | 115 | ((se & 0x0000000040000000) << 16) | 116 | ((se & 0x0000000000000002) << 17) | 117 | ((se & 0x0000000000000050) << 18) | 118 | ((se & 0x0000020200000000) << 21) | 119 | ((se & 0x0000000000080000) << 23) | 120 | ((se & 0x0000000000040000) << 25) | 121 | ((se & 0x0000000000200000) << 26) | 122 | ((se & 0x0000000800000000) << 28) | 123 | ((se & 0x0000000000000080) << 29) | 124 | ((se & 0x0000000000004000) << 30) | 125 | ((se & 0x0000000004000000) << 33) | 126 | ((se & 0x0000000000800800) << 34) | 127 | ((se & 0x0000000000000001) << 40) | 128 | ((se & 0x0000000000000100) << 43) | 129 | ((se & 0x0000000000000400) << 50) | 130 | ((se & 0x0000000000000024) << 51)); 131 | } 132 | 133 | 134 | int64_t 135 | ngx_atoll(u_char* line, size_t n) 136 | { 137 | int64_t value, cutoff, cutlim; 138 | 139 | if (n == 0) { 140 | return NGX_ERROR; 141 | } 142 | 143 | cutoff = LLONG_MAX / 10; 144 | cutlim = LLONG_MAX % 10; 145 | 146 | for (value = 0; n--; line++) { 147 | if (*line < '0' || *line > '9') { 148 | return NGX_ERROR; 149 | } 150 | 151 | if (value >= cutoff && (value > cutoff || *line - '0' > cutlim)) { 152 | return NGX_ERROR; 153 | } 154 | 155 | value = value * 10 + (*line - '0'); 156 | } 157 | 158 | return value; 159 | } 160 | 161 | 162 | static time_t 163 | ngx_secure_token_broadpeak_parse_time(ngx_pool_t* pool, ngx_str_t* ts) 164 | { 165 | ngx_tm_t tm; 166 | int64_t n; 167 | char* fmt; 168 | char* s; 169 | 170 | switch (ts->len) 171 | { 172 | case 10: /* unixtime */ 173 | return ngx_atotm(ts->data, ts->len); 174 | 175 | case 13: /* unixtime millis */ 176 | n = ngx_atoll(ts->data, ts->len); 177 | if (n == NGX_ERROR) { 178 | return NGX_ERROR; 179 | } 180 | 181 | return n / 1000; 182 | 183 | case 15: 184 | fmt = "%Y%m%dT%H%M%S"; 185 | break; 186 | 187 | case 16: 188 | if (ts->data[15] != 'Z') { 189 | /* unixtime micros */ 190 | n = ngx_atoll(ts->data, ts->len); 191 | if (n == NGX_ERROR) { 192 | return NGX_ERROR; 193 | } 194 | 195 | return n / 1000000; 196 | } 197 | 198 | fmt = "%Y%m%dT%H%M%SZ"; 199 | break; 200 | 201 | case 19: 202 | fmt = "%Y-%m-%dT%H:%M:%S"; 203 | break; 204 | 205 | case 20: 206 | fmt = "%Y-%m-%dT%H:%M:%SZ"; 207 | break; 208 | 209 | default: 210 | return NGX_ERROR; 211 | } 212 | 213 | s = ngx_pnalloc(pool, ts->len + 1); 214 | if (s == NULL) { 215 | return NGX_ERROR; 216 | } 217 | 218 | ngx_memcpy(s, ts->data, ts->len); 219 | s[ts->len] = '\0'; 220 | 221 | if (strptime(s, fmt, &tm) != s + ts->len) { 222 | return NGX_ERROR; 223 | } 224 | 225 | tm.ngx_tm_isdst = -1; 226 | 227 | return timegm(&tm); 228 | } 229 | 230 | 231 | static time_t 232 | ngx_secure_token_broadpeak_get_time(ngx_http_request_t* r, ngx_http_complex_value_t* val) 233 | { 234 | ngx_str_t str; 235 | time_t res; 236 | 237 | if (val == NULL) { 238 | return 0; 239 | } 240 | 241 | if (ngx_http_complex_value(r, val, &str) != NGX_OK) { 242 | return NGX_ERROR; 243 | } 244 | 245 | if (str.len == 0) { 246 | return 0; 247 | } 248 | 249 | res = ngx_secure_token_broadpeak_parse_time(r->pool, &str); 250 | if (res == NGX_ERROR) { 251 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 252 | "ngx_secure_token_broadpeak_get_time: failed to parse \"%V\"", &str); 253 | return NGX_ERROR; 254 | } 255 | 256 | return res; 257 | } 258 | 259 | 260 | static ngx_int_t 261 | ngx_secure_token_broadpeak_get_var( 262 | ngx_http_request_t* r, 263 | ngx_http_variable_value_t* v, 264 | uintptr_t data) 265 | { 266 | ngx_secure_token_broadpeak_token_t* token = (void*)data; 267 | ngx_str_t additional_querylist; 268 | ngx_str_t acl; 269 | ngx_str_t key; 270 | ngx_md5_t md5; 271 | ngx_int_t rc; 272 | uint64_t start_end; 273 | size_t result_size; 274 | time_t session_start; 275 | time_t session_end; 276 | time_t start_time; 277 | time_t end_time; 278 | u_char md5hash_buf[MD5_DIGEST_LENGTH]; 279 | u_char temp_buf[sizeof(uint64_t) * 2]; 280 | u_char* p; 281 | 282 | // get the acl + key 283 | rc = ngx_http_secure_token_get_acl(r, token->acl, &acl); 284 | if (rc != NGX_OK) 285 | { 286 | return rc; 287 | } 288 | 289 | if (ngx_http_complex_value(r, token->key, &key) != NGX_OK) 290 | { 291 | return NGX_ERROR; 292 | } 293 | 294 | session_start = ngx_secure_token_broadpeak_get_time(r, token->session_start); 295 | if (session_start == NGX_ERROR) 296 | { 297 | return NGX_ERROR; 298 | } 299 | 300 | session_end = ngx_secure_token_broadpeak_get_time(r, token->session_end); 301 | if (session_end == NGX_ERROR) 302 | { 303 | return NGX_ERROR; 304 | } 305 | 306 | if (token->additional_querylist != NULL) 307 | { 308 | if (ngx_http_complex_value(r, token->additional_querylist, &additional_querylist) != NGX_OK) 309 | { 310 | return NGX_ERROR; 311 | } 312 | } 313 | else 314 | { 315 | additional_querylist.data = NULL; 316 | additional_querylist.len = 0; 317 | } 318 | 319 | // allocate the result 320 | result_size = token->param_name.len + sizeof(uint64_t) * 2 + sizeof(md5hash_buf) * 2 + sizeof("=_"); 321 | 322 | p = ngx_pnalloc(r->pool, result_size); 323 | if (p == NULL) 324 | { 325 | return NGX_ERROR; 326 | } 327 | 328 | v->data = p; 329 | 330 | // get the start / end time (mandatory fields) 331 | start_time = token->start.val; 332 | if (token->start.type == NGX_HTTP_SECURE_TOKEN_TIME_RELATIVE) 333 | { 334 | start_time += ngx_time(); 335 | } 336 | 337 | end_time = token->end.val; 338 | if (token->end.type == NGX_HTTP_SECURE_TOKEN_TIME_RELATIVE) 339 | { 340 | end_time += ngx_time(); 341 | } 342 | 343 | start_end = ((uint64_t)start_time << 32) | (uint64_t)end_time; 344 | 345 | // calculate the signature 346 | ngx_md5_init(&md5); 347 | ngx_md5_update(&md5, key.data, key.len); 348 | ngx_md5_update(&md5, acl.data, acl.len); 349 | 350 | ngx_sprintf(temp_buf, "%016uxL", start_end); 351 | ngx_md5_update(&md5, temp_buf, sizeof(uint64_t) * 2); 352 | 353 | if (session_start != 0) 354 | { 355 | ngx_sprintf(temp_buf, "%08uxD", (uint32_t) session_start); 356 | ngx_md5_update(&md5, temp_buf, sizeof(uint32_t) * 2); 357 | } 358 | 359 | if (session_end != 0) 360 | { 361 | ngx_sprintf(temp_buf, "%08uxD", (uint32_t) session_end); 362 | ngx_md5_update(&md5, temp_buf, sizeof(uint32_t) * 2); 363 | } 364 | 365 | ngx_md5_update(&md5, additional_querylist.data, additional_querylist.len); 366 | 367 | ngx_md5_final(md5hash_buf, &md5); 368 | 369 | // build the result 370 | p = ngx_copy(p, token->param_name.data, token->param_name.len); 371 | *p++ = '='; 372 | p = ngx_sprintf(p, "%016uxL", ngx_secure_token_broadpeak_scramble(start_end)); 373 | *p++ = '_'; 374 | p = ngx_hex_dump(p, md5hash_buf, sizeof(md5hash_buf)); 375 | *p = '\0'; 376 | 377 | v->len = p - v->data; 378 | v->valid = 1; 379 | v->no_cacheable = 0; 380 | v->not_found = 0; 381 | 382 | return NGX_OK; 383 | } 384 | 385 | char * 386 | ngx_secure_token_broadpeak_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 387 | { 388 | ngx_secure_token_broadpeak_token_t* token; 389 | char* rv; 390 | 391 | // init config 392 | token = ngx_pcalloc(cf->pool, sizeof(*token)); 393 | if (token == NULL) 394 | { 395 | return NGX_CONF_ERROR; 396 | } 397 | 398 | token->start.type = NGX_HTTP_SECURE_TOKEN_TIME_UNSET; 399 | token->end.type = NGX_HTTP_SECURE_TOKEN_TIME_UNSET; 400 | 401 | // parse the block 402 | rv = ngx_http_secure_token_conf_block( 403 | cf, 404 | ngx_http_secure_token_broadpeak_cmds, 405 | token, 406 | ngx_secure_token_broadpeak_get_var); 407 | if (rv != NGX_CONF_OK) 408 | { 409 | return rv; 410 | } 411 | 412 | // validate required params 413 | if (token->key == NULL) 414 | { 415 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 416 | "\"key\" is mandatory for broadpeak tokens"); 417 | return NGX_CONF_ERROR; 418 | } 419 | 420 | if (token->acl == NULL) 421 | { 422 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 423 | "\"acl\" is mandatory for broadpeak tokens"); 424 | return NGX_CONF_ERROR; 425 | } 426 | 427 | // populate unset optional params 428 | if (token->param_name.data == NULL) 429 | { 430 | ngx_str_set(&token->param_name, "token"); 431 | } 432 | 433 | if (token->start.type == NGX_HTTP_SECURE_TOKEN_TIME_UNSET) 434 | { 435 | token->start.type = NGX_HTTP_SECURE_TOKEN_TIME_RELATIVE; 436 | } 437 | 438 | if (token->end.type == NGX_HTTP_SECURE_TOKEN_TIME_UNSET) 439 | { 440 | token->end.type = NGX_HTTP_SECURE_TOKEN_TIME_RELATIVE; 441 | token->end.val = 86400; 442 | } 443 | 444 | return NGX_CONF_OK; 445 | } 446 | -------------------------------------------------------------------------------- /ngx_http_secure_token_encrypt_uri.c: -------------------------------------------------------------------------------- 1 | #include "ngx_http_secure_token_filter_module.h" 2 | #include "ngx_http_secure_token_encrypt_uri.h" 3 | #include "ngx_http_secure_token_conf.h" 4 | #include "ngx_http_secure_token_utils.h" 5 | #include 6 | #include 7 | 8 | 9 | static ngx_str_t ngx_http_secure_token_original_uri = ngx_string("secure_token_original_uri"); 10 | static ngx_uint_t ngx_http_secure_token_original_uri_index; 11 | 12 | 13 | // Note: a modified version of ngx_strstrn that gets ngx_str_t's 14 | static u_char * 15 | ngx_http_secure_token_strstr(ngx_str_t* haystack, ngx_str_t* needle) 16 | { 17 | u_char c1, c2; 18 | u_char* s1 = haystack->data; 19 | u_char* s1_end = haystack->data + haystack->len - needle->len; 20 | u_char* s2 = needle->data + 1; 21 | size_t s2_len = needle->len - 1; 22 | 23 | c2 = needle->data[0]; 24 | 25 | do { 26 | do { 27 | if (s1 > s1_end) { 28 | return NULL; 29 | } 30 | 31 | c1 = *s1++; 32 | 33 | } while (c1 != c2); 34 | 35 | } while (ngx_memcmp(s1, s2, s2_len) != 0); 36 | 37 | return --s1; 38 | } 39 | 40 | static ngx_int_t 41 | ngx_http_secure_token_get_encryted_part( 42 | ngx_http_request_t *r, 43 | ngx_str_t* uri, 44 | ngx_flag_t execute, 45 | ngx_str_t* encrypt_uri_part, 46 | size_t* uri_prefix_len, 47 | size_t* uri_suffix_len) 48 | { 49 | ngx_http_secure_token_loc_conf_t *conf; 50 | ngx_http_core_loc_conf_t *clcf; 51 | u_char* encrypt_uri_pos; 52 | ngx_int_t rc; 53 | 54 | clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); 55 | 56 | // simple (non regex) location 57 | if (clcf->regex == NULL) 58 | { 59 | if (execute) 60 | { 61 | if (uri->len < clcf->name.len || 62 | ngx_memcmp(clcf->name.data, uri->data, clcf->name.len) != 0) 63 | { 64 | encrypt_uri_part->len = 0; 65 | return NGX_OK; 66 | } 67 | } 68 | 69 | *uri_prefix_len = clcf->name.len; 70 | *uri_suffix_len = 0; 71 | encrypt_uri_part->data = uri->data + *uri_prefix_len; 72 | encrypt_uri_part->len = uri->len - *uri_prefix_len; 73 | 74 | return NGX_OK; 75 | } 76 | 77 | // regex location 78 | if (execute) 79 | { 80 | // execute the location regex on the current url so that $1,$2 etc. will evaluate correctly 81 | rc = ngx_http_regex_exec(r, clcf->regex, uri); 82 | if (rc != NGX_OK) 83 | { 84 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 85 | "ngx_http_secure_token_get_encryted_part: ngx_http_regex_exec failed"); 86 | return NGX_ERROR; 87 | } 88 | } 89 | 90 | // evaluate encrypted part 91 | conf = ngx_http_get_module_loc_conf(r, ngx_http_secure_token_filter_module); 92 | 93 | if (conf->encrypt_uri_part == NULL) 94 | { 95 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 96 | "ngx_http_secure_token_get_encryted_part: encrypt_uri_part was not set"); 97 | return NGX_ERROR; 98 | } 99 | 100 | if (ngx_http_complex_value( 101 | r, 102 | conf->encrypt_uri_part, 103 | encrypt_uri_part) != NGX_OK) 104 | { 105 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 106 | "ngx_http_secure_token_get_encryted_part: ngx_http_complex_value failed"); 107 | return NGX_ERROR; 108 | } 109 | 110 | if (encrypt_uri_part->len == 0) 111 | { 112 | return NGX_OK; 113 | } 114 | 115 | // find the encrypted part on the uri 116 | encrypt_uri_pos = ngx_http_secure_token_strstr(uri, encrypt_uri_part); 117 | if (encrypt_uri_pos == NULL) 118 | { 119 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 120 | "ngx_http_secure_token_get_encryted_part: failed to find the encrypted uri part"); 121 | return NGX_ERROR; 122 | } 123 | *uri_prefix_len = encrypt_uri_pos - uri->data; 124 | *uri_suffix_len = uri->len - *uri_prefix_len - encrypt_uri_part->len; 125 | 126 | return NGX_OK; 127 | } 128 | 129 | static ngx_int_t 130 | ngx_http_secure_token_crypt( 131 | ngx_str_t* dest, 132 | ngx_http_request_t* r, 133 | u_char* key, 134 | u_char* iv, 135 | ngx_str_t* buffer1, 136 | ngx_str_t* buffer2, 137 | ngx_flag_t encrypt) 138 | { 139 | EVP_CIPHER_CTX* ctx; 140 | u_char* p; 141 | int output_len; 142 | 143 | ctx = EVP_CIPHER_CTX_new(); 144 | if (ctx == NULL) 145 | { 146 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 147 | "ngx_http_secure_token_crypt: EVP_CIPHER_CTX_new failed"); 148 | return NGX_ERROR; 149 | } 150 | 151 | if (!EVP_CipherInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv, encrypt)) 152 | { 153 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 154 | "ngx_http_secure_token_crypt: EVP_CipherInit_ex failed"); 155 | goto error; 156 | } 157 | 158 | p = dest->data; 159 | 160 | if (!EVP_CipherUpdate(ctx, p, &output_len, buffer1->data, buffer1->len)) 161 | { 162 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 163 | "ngx_http_secure_token_crypt: EVP_CipherUpdate failed (1)"); 164 | goto error; 165 | } 166 | p += output_len; 167 | 168 | if (buffer2 != NULL) 169 | { 170 | if (!EVP_CipherUpdate(ctx, p, &output_len, buffer2->data, buffer2->len)) 171 | { 172 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 173 | "ngx_http_secure_token_crypt: EVP_CipherUpdate failed (2)"); 174 | goto error; 175 | } 176 | p += output_len; 177 | } 178 | 179 | if (!EVP_CipherFinal_ex(ctx, p, &output_len)) 180 | { 181 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 182 | "ngx_http_secure_token_crypt: EVP_CipherFinal_ex failed"); 183 | goto error; 184 | } 185 | p += output_len; 186 | 187 | EVP_CIPHER_CTX_free(ctx); 188 | 189 | dest->len = p - dest->data; 190 | 191 | return NGX_OK; 192 | 193 | error: 194 | 195 | EVP_CIPHER_CTX_free(ctx); 196 | return NGX_ERROR; 197 | } 198 | 199 | ngx_int_t 200 | ngx_http_secure_token_decrypt_uri(ngx_http_request_t *r) 201 | { 202 | ngx_http_secure_token_loc_conf_t *conf; 203 | ngx_http_variable_value_t *vv; 204 | ngx_str_t encrypt_uri_part; 205 | ngx_str_t base64_decoded; 206 | ngx_str_t decrypted; 207 | ngx_str_t new_uri; 208 | ngx_int_t rc; 209 | ngx_md5_t md5; 210 | u_char md5hash[MD5_DIGEST_LENGTH]; 211 | size_t uri_prefix_len; 212 | size_t uri_suffix_len; 213 | u_char* p; 214 | 215 | conf = ngx_http_get_module_loc_conf(r, ngx_http_secure_token_filter_module); 216 | 217 | if (!conf->processor_conf.encrypt_uri) 218 | { 219 | return NGX_DECLINED; 220 | } 221 | 222 | // get the encrypted part 223 | rc = ngx_http_secure_token_get_encryted_part(r, &r->uri, 0, &encrypt_uri_part, &uri_prefix_len, &uri_suffix_len); 224 | if (rc != NGX_OK) 225 | { 226 | return rc; 227 | } 228 | 229 | if (encrypt_uri_part.len == 0) 230 | { 231 | return NGX_DECLINED; 232 | } 233 | 234 | // allocate buffers 235 | base64_decoded.len = ngx_base64_decoded_length(encrypt_uri_part.len); 236 | decrypted.len = base64_decoded.len; 237 | new_uri.len = uri_prefix_len + decrypted.len + uri_suffix_len; 238 | 239 | new_uri.data = ngx_pnalloc(r->pool, new_uri.len + 1); 240 | if (new_uri.data == NULL) 241 | { 242 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 243 | "ngx_http_secure_token_decrypt_uri: ngx_pnalloc failed (1)"); 244 | return NGX_ERROR; 245 | } 246 | 247 | decrypted.data = ngx_pnalloc(r->pool, decrypted.len + base64_decoded.len); 248 | if (decrypted.data == NULL) 249 | { 250 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 251 | "ngx_http_secure_token_decrypt_uri: ngx_pnalloc failed (2)"); 252 | return NGX_ERROR; 253 | } 254 | 255 | base64_decoded.data = decrypted.data + decrypted.len; 256 | 257 | // base64url decode 258 | rc = ngx_decode_base64url(&base64_decoded, &encrypt_uri_part); 259 | if (rc != NGX_OK) 260 | { 261 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 262 | "ngx_http_secure_token_decrypt_uri: ngx_decode_base64url failed %i", rc); 263 | return NGX_HTTP_BAD_REQUEST; 264 | } 265 | 266 | // decrypt 267 | if (base64_decoded.len % AES_BLOCK_SIZE != 0) 268 | { 269 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 270 | "ngx_http_secure_token_decrypt_uri: base64 decoded string length %uz is not a multiple of block size", 271 | base64_decoded.len); 272 | return NGX_HTTP_BAD_REQUEST; 273 | } 274 | 275 | rc = ngx_http_secure_token_crypt( 276 | &decrypted, 277 | r, 278 | conf->encrypt_uri_key.data, 279 | conf->encrypt_uri_iv.data, 280 | &base64_decoded, 281 | NULL, 282 | 0); 283 | if (rc != NGX_OK) 284 | { 285 | return rc; 286 | } 287 | 288 | // validate signature 289 | if (decrypted.len < conf->encrypt_uri_hash_size) 290 | { 291 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 292 | "ngx_http_secure_token_decrypt_uri: decrypted length %uz smaller than hash size", decrypted.len); 293 | return NGX_HTTP_BAD_REQUEST; 294 | } 295 | 296 | ngx_md5_init(&md5); 297 | ngx_md5_update(&md5, decrypted.data + conf->encrypt_uri_hash_size, decrypted.len - conf->encrypt_uri_hash_size); 298 | ngx_md5_final(md5hash, &md5); 299 | 300 | if (ngx_memcmp(md5hash, decrypted.data, conf->encrypt_uri_hash_size) != 0) 301 | { 302 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 303 | "ngx_http_secure_token_decrypt_uri: invalid hash"); 304 | return NGX_HTTP_FORBIDDEN; 305 | } 306 | 307 | // save the original uri in var 308 | vv = &r->variables[ngx_http_secure_token_original_uri_index]; 309 | 310 | vv->valid = 1; 311 | vv->not_found = 0; 312 | vv->no_cacheable = 0; 313 | 314 | vv->data = r->uri.data; 315 | vv->len = r->uri.len; 316 | 317 | // update the uri 318 | p = ngx_copy(new_uri.data, r->uri.data, uri_prefix_len); 319 | p = ngx_copy(p, decrypted.data + conf->encrypt_uri_hash_size, decrypted.len - conf->encrypt_uri_hash_size); 320 | p = ngx_copy(p, r->uri.data + r->uri.len - uri_suffix_len, uri_suffix_len); 321 | *p = 0; 322 | 323 | new_uri.len = p - new_uri.data; 324 | 325 | r->uri = new_uri; 326 | r->unparsed_uri = new_uri; // TODO: fix this 327 | 328 | // free temporary buffer 329 | ngx_pfree(r->pool, decrypted.data); 330 | 331 | return NGX_DECLINED; 332 | } 333 | 334 | ngx_int_t 335 | ngx_http_secure_token_encrypt_uri(ngx_http_request_t* r, ngx_str_t* src, ngx_str_t* dest) 336 | { 337 | ngx_http_secure_token_loc_conf_t *conf; 338 | ngx_str_t encrypt_uri_part; 339 | ngx_str_t base64_encoded; 340 | ngx_str_t encrypted; 341 | ngx_str_t hash; 342 | ngx_str_t new_uri; 343 | ngx_int_t rc; 344 | ngx_md5_t md5; 345 | u_char md5hash[MD5_DIGEST_LENGTH]; 346 | size_t uri_prefix_len; 347 | size_t uri_suffix_len; 348 | u_char* p; 349 | 350 | conf = ngx_http_get_module_loc_conf(r, ngx_http_secure_token_filter_module); 351 | 352 | // get the encrypted part 353 | rc = ngx_http_secure_token_get_encryted_part(r, src, 1, &encrypt_uri_part, &uri_prefix_len, &uri_suffix_len); 354 | if (rc != NGX_OK) 355 | { 356 | return rc; 357 | } 358 | 359 | if (encrypt_uri_part.len == 0) 360 | { 361 | dest->data = ngx_pstrdup(r->pool, src); 362 | if (dest->data == NULL) 363 | { 364 | return NGX_ERROR; 365 | } 366 | dest->len = src->len; 367 | return NGX_OK; 368 | } 369 | 370 | // TODO: consider undoing ngx_http_regex_exec (r->captures, r->variables etc.) 371 | 372 | // allocate buffers 373 | encrypted.len = conf->encrypt_uri_hash_size + encrypt_uri_part.len + AES_BLOCK_SIZE; 374 | new_uri.len = uri_prefix_len + ngx_base64_encoded_length(encrypted.len) + uri_suffix_len; 375 | 376 | new_uri.data = ngx_pnalloc(r->pool, new_uri.len + 1); 377 | if (new_uri.data == NULL) 378 | { 379 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 380 | "ngx_http_secure_token_encrypt_uri: ngx_pnalloc failed (1)"); 381 | return NGX_ERROR; 382 | } 383 | 384 | encrypted.data = ngx_pnalloc(r->pool, encrypted.len); 385 | if (encrypted.data == NULL) 386 | { 387 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 388 | "ngx_http_secure_token_encrypt_uri: ngx_pnalloc failed (2)"); 389 | return NGX_ERROR; 390 | } 391 | 392 | // sign 393 | ngx_md5_init(&md5); 394 | ngx_md5_update(&md5, encrypt_uri_part.data, encrypt_uri_part.len); 395 | ngx_md5_final(md5hash, &md5); 396 | 397 | hash.data = md5hash; 398 | hash.len = conf->encrypt_uri_hash_size; 399 | 400 | // encrypt 401 | rc = ngx_http_secure_token_crypt( 402 | &encrypted, 403 | r, 404 | conf->encrypt_uri_key.data, 405 | conf->encrypt_uri_iv.data, 406 | &hash, 407 | &encrypt_uri_part, 408 | 1); 409 | if (rc != NGX_OK) 410 | { 411 | return rc; 412 | } 413 | 414 | // update the uri 415 | p = ngx_copy(new_uri.data, src->data, uri_prefix_len); 416 | 417 | base64_encoded.data = p; 418 | ngx_encode_base64url(&base64_encoded, &encrypted); 419 | p += base64_encoded.len; 420 | 421 | p = ngx_copy(p, src->data + src->len - uri_suffix_len, uri_suffix_len); 422 | *p = 0; 423 | 424 | new_uri.len = p - new_uri.data; 425 | 426 | *dest = new_uri; 427 | 428 | // free temporary buffer 429 | ngx_pfree(r->pool, encrypted.data); 430 | 431 | return NGX_OK; 432 | } 433 | 434 | static ngx_int_t 435 | ngx_http_secure_token_encrypt_uri_variable(ngx_http_request_t *r, 436 | ngx_http_variable_value_t *v, uintptr_t data) 437 | { 438 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 439 | "encrypt uri variable"); 440 | 441 | v->not_found = 1; 442 | 443 | return NGX_OK; 444 | } 445 | 446 | ngx_int_t 447 | ngx_http_secure_token_encrypt_uri_add_variables(ngx_conf_t *cf) 448 | { 449 | ngx_int_t n; 450 | ngx_http_variable_t *v; 451 | 452 | v = ngx_http_add_variable(cf, &ngx_http_secure_token_original_uri, 0); 453 | if (v == NULL) { 454 | return NGX_ERROR; 455 | } 456 | 457 | n = ngx_http_get_variable_index(cf, &ngx_http_secure_token_original_uri); 458 | if (n == NGX_ERROR) { 459 | return NGX_ERROR; 460 | } 461 | 462 | if (v->get_handler == NULL) { 463 | v->get_handler = ngx_http_secure_token_encrypt_uri_variable; 464 | } 465 | 466 | ngx_http_secure_token_original_uri_index = n; 467 | 468 | return NGX_OK; 469 | } 470 | -------------------------------------------------------------------------------- /ngx_http_secure_token_utils.c: -------------------------------------------------------------------------------- 1 | #include "ngx_http_secure_token_utils.h" 2 | #include 3 | 4 | // typedefs 5 | typedef struct { 6 | ngx_conf_t *cf; 7 | ngx_command_t *cmds; 8 | } ngx_secure_token_conf_ctx_t; 9 | 10 | // constants 11 | static ngx_uint_t argument_number[] = { 12 | NGX_CONF_NOARGS, 13 | NGX_CONF_TAKE1, 14 | NGX_CONF_TAKE2, 15 | NGX_CONF_TAKE3, 16 | NGX_CONF_TAKE4, 17 | NGX_CONF_TAKE5, 18 | NGX_CONF_TAKE6, 19 | NGX_CONF_TAKE7 20 | }; 21 | 22 | static int 23 | ngx_http_secure_token_get_hex_char_value(int ch) 24 | { 25 | if (ch >= '0' && ch <= '9') { 26 | return (ch - '0'); 27 | } 28 | 29 | ch = (ch | 0x20); // lower case 30 | 31 | if (ch >= 'a' && ch <= 'f') { 32 | return (ch - 'a' + 10); 33 | } 34 | 35 | return -1; 36 | } 37 | 38 | static ngx_int_t 39 | ngx_http_secure_token_decode_hex(ngx_pool_t* pool, ngx_str_t* src, ngx_str_t* dest) 40 | { 41 | u_char* cur_pos; 42 | u_char* end_pos; 43 | u_char* p; 44 | int digit1; 45 | int digit2; 46 | 47 | if (src->len & 0x1) 48 | { 49 | return NGX_ERROR; 50 | } 51 | 52 | dest->data = ngx_palloc(pool, src->len >> 1); 53 | if (dest->data == NULL) 54 | { 55 | return NGX_ERROR; 56 | } 57 | p = dest->data; 58 | 59 | end_pos = src->data + src->len; 60 | for (cur_pos = src->data; cur_pos < end_pos; cur_pos += 2) 61 | { 62 | digit1 = ngx_http_secure_token_get_hex_char_value(cur_pos[0]); 63 | digit2 = ngx_http_secure_token_get_hex_char_value(cur_pos[1]); 64 | if (digit1 < 0 || digit2 < 0) 65 | { 66 | return NGX_ERROR; 67 | } 68 | 69 | *p++ = (digit1 << 4) | digit2; 70 | } 71 | dest->len = p - dest->data; 72 | 73 | return NGX_OK; 74 | } 75 | 76 | char * 77 | ngx_http_secure_token_conf_set_hex_str_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 78 | { 79 | ngx_conf_post_t *post; 80 | ngx_str_t *field; 81 | ngx_str_t *value; 82 | ngx_int_t rc; 83 | 84 | field = (ngx_str_t *)((u_char*)conf + cmd->offset); 85 | 86 | if (field->data) 87 | { 88 | return "is duplicate"; 89 | } 90 | 91 | value = cf->args->elts; 92 | 93 | rc = ngx_http_secure_token_decode_hex( 94 | cf->pool, 95 | &value[1], 96 | field); 97 | if (rc != NGX_OK) 98 | { 99 | return "invalid hex string"; 100 | } 101 | 102 | if (cmd->post) { 103 | post = cmd->post; 104 | return post->post_handler(cf, post, field); 105 | } 106 | 107 | return NGX_CONF_OK; 108 | } 109 | 110 | char * 111 | ngx_http_secure_token_conf_set_time_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 112 | { 113 | ngx_secure_token_time_t* result; 114 | ngx_conf_post_t *post; 115 | ngx_uint_t minus; 116 | ngx_str_t *value; 117 | 118 | result = (void *)((u_char*)conf + cmd->offset); 119 | 120 | if (result->type != NGX_HTTP_SECURE_TOKEN_TIME_UNSET) 121 | { 122 | return "is duplicate"; 123 | } 124 | 125 | value = &((ngx_str_t*)cf->args->elts)[1]; 126 | 127 | if (value->len <= 0) 128 | { 129 | return "is empty"; 130 | } 131 | 132 | if (value->len == 5 && ngx_strncmp(value->data, "epoch", 5) == 0) 133 | { 134 | result->type = NGX_HTTP_SECURE_TOKEN_TIME_ABSOLUTE; 135 | result->val = 0; 136 | goto done; 137 | } 138 | 139 | if (value->len == 3 && ngx_strncmp(value->data, "max", 3) == 0) 140 | { 141 | result->type = NGX_HTTP_SECURE_TOKEN_TIME_ABSOLUTE; 142 | result->val = INT_MAX; 143 | goto done; 144 | } 145 | 146 | switch (value->data[0]) 147 | { 148 | case '@': 149 | value->data++; 150 | value->len--; 151 | minus = 0; 152 | result->type = NGX_HTTP_SECURE_TOKEN_TIME_ABSOLUTE; 153 | break; 154 | 155 | case '-': 156 | value->data++; 157 | value->len--; 158 | minus = 1; 159 | result->type = NGX_HTTP_SECURE_TOKEN_TIME_RELATIVE; 160 | break; 161 | 162 | case '+': 163 | value->data++; 164 | value->len--; 165 | // fallthrough 166 | 167 | default: 168 | minus = 0; 169 | result->type = NGX_HTTP_SECURE_TOKEN_TIME_RELATIVE; 170 | break; 171 | } 172 | 173 | result->val = ngx_parse_time(value, 1); 174 | 175 | if (result->val == (time_t)NGX_ERROR) 176 | { 177 | return "invalid value"; 178 | } 179 | 180 | if (minus) 181 | { 182 | result->val = -result->val; 183 | } 184 | 185 | done: 186 | 187 | if (cmd->post) { 188 | post = cmd->post; 189 | return post->post_handler(cf, post, result); 190 | } 191 | 192 | return NGX_CONF_OK; 193 | } 194 | 195 | char * 196 | ngx_http_secure_token_conf_set_private_key_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 197 | { 198 | ngx_pool_cleanup_t* cln; 199 | ngx_conf_post_t *post; 200 | ngx_str_t *value; 201 | EVP_PKEY** result; 202 | BIO *in; 203 | 204 | result = (void *)((u_char*)conf + cmd->offset); 205 | 206 | if (*result != NULL) 207 | { 208 | return "is duplicate"; 209 | } 210 | 211 | value = cf->args->elts; 212 | 213 | cln = ngx_pool_cleanup_add(cf->pool, 0); 214 | if (cln == NULL) 215 | { 216 | return NGX_CONF_ERROR; 217 | } 218 | 219 | in = BIO_new_file((char *)value[1].data, "r"); 220 | if (in == NULL) 221 | { 222 | return "cannot be opened"; 223 | } 224 | 225 | *result = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL); 226 | 227 | BIO_free(in); 228 | 229 | if (*result == NULL) 230 | { 231 | return "cannot be loaded"; 232 | } 233 | 234 | cln->handler = (ngx_pool_cleanup_pt)EVP_PKEY_free; 235 | cln->data = *result; 236 | 237 | if (cmd->post) { 238 | post = cmd->post; 239 | return post->post_handler(cf, post, *result); 240 | } 241 | 242 | return NGX_CONF_OK; 243 | } 244 | 245 | // copied from ngx_conf_handler, removed support for modules 246 | static char * 247 | ngx_http_secure_token_command_handler(ngx_conf_t *cf, ngx_command_t *dummy, void *conf) 248 | { 249 | ngx_secure_token_conf_ctx_t* ctx; 250 | ngx_command_t *cmd; 251 | ngx_str_t *name; 252 | char *rv; 253 | 254 | ctx = cf->ctx; 255 | cmd = ctx->cmds; 256 | 257 | name = cf->args->elts; 258 | 259 | for ( /* void */; cmd->name.len; cmd++) { 260 | 261 | if (name->len != cmd->name.len) { 262 | continue; 263 | } 264 | 265 | if (ngx_strcmp(name->data, cmd->name.data) != 0) { 266 | continue; 267 | } 268 | 269 | /* is the directive's argument count right ? */ 270 | 271 | if (!(cmd->type & NGX_CONF_ANY)) { 272 | 273 | if (cmd->type & NGX_CONF_FLAG) { 274 | 275 | if (cf->args->nelts != 2) { 276 | goto invalid; 277 | } 278 | 279 | } 280 | else if (cmd->type & NGX_CONF_1MORE) { 281 | 282 | if (cf->args->nelts < 2) { 283 | goto invalid; 284 | } 285 | 286 | } 287 | else if (cmd->type & NGX_CONF_2MORE) { 288 | 289 | if (cf->args->nelts < 3) { 290 | goto invalid; 291 | } 292 | 293 | } 294 | else if (cf->args->nelts > NGX_CONF_MAX_ARGS) { 295 | 296 | goto invalid; 297 | 298 | } 299 | else if (!(cmd->type & argument_number[cf->args->nelts - 1])) 300 | { 301 | goto invalid; 302 | } 303 | } 304 | 305 | rv = cmd->set(ctx->cf, cmd, conf); 306 | 307 | if (rv == NGX_CONF_OK) { 308 | return NGX_CONF_OK; 309 | } 310 | 311 | if (rv == NGX_CONF_ERROR) { 312 | return NGX_CONF_ERROR; 313 | } 314 | 315 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 316 | "\"%s\" directive %s", name->data, rv); 317 | 318 | return NGX_CONF_ERROR; 319 | } 320 | 321 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 322 | "unknown directive \"%s\"", name->data); 323 | 324 | return NGX_CONF_ERROR; 325 | 326 | invalid: 327 | 328 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 329 | "invalid number of arguments in \"%s\" directive", 330 | name->data); 331 | 332 | return NGX_CONF_ERROR; 333 | } 334 | 335 | char * 336 | ngx_http_secure_token_conf_block( 337 | ngx_conf_t *cf, 338 | ngx_command_t *cmds, 339 | void *conf, 340 | ngx_http_get_variable_pt get_handler) 341 | { 342 | ngx_secure_token_conf_ctx_t ctx; 343 | ngx_http_variable_t *var; 344 | ngx_conf_t save; 345 | ngx_str_t *value; 346 | ngx_str_t name; 347 | char *rv; 348 | 349 | value = cf->args->elts; 350 | 351 | name = value[1]; 352 | 353 | if (name.data[0] != '$') 354 | { 355 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 356 | "invalid variable name \"%V\"", &name); 357 | return NGX_CONF_ERROR; 358 | } 359 | 360 | name.len--; 361 | name.data++; 362 | 363 | var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE); 364 | if (var == NULL) 365 | { 366 | return NGX_CONF_ERROR; 367 | } 368 | 369 | var->get_handler = get_handler; 370 | var->data = (uintptr_t)conf; 371 | 372 | ctx.cmds = cmds; 373 | ctx.cf = &save; 374 | 375 | save = *cf; 376 | cf->ctx = &ctx; 377 | cf->handler = ngx_http_secure_token_command_handler; 378 | cf->handler_conf = conf; 379 | 380 | rv = ngx_conf_parse(cf, NULL); 381 | 382 | *cf = save; 383 | 384 | return rv; 385 | } 386 | 387 | // copied from ngx_string, changed the interface: 388 | // 1. get a u_char* dest pointer and return the write end position 389 | // 2. get the padding char as param 390 | 391 | u_char* 392 | ngx_http_secure_token_encode_base64_internal( 393 | u_char *d, 394 | ngx_str_t *src, 395 | const u_char *basis, 396 | u_char padding) 397 | { 398 | u_char *s; 399 | size_t len; 400 | 401 | len = src->len; 402 | s = src->data; 403 | 404 | while (len > 2) { 405 | *d++ = basis[(s[0] >> 2) & 0x3f]; 406 | *d++ = basis[((s[0] & 3) << 4) | (s[1] >> 4)]; 407 | *d++ = basis[((s[1] & 0x0f) << 2) | (s[2] >> 6)]; 408 | *d++ = basis[s[2] & 0x3f]; 409 | 410 | s += 3; 411 | len -= 3; 412 | } 413 | 414 | if (len) { 415 | *d++ = basis[(s[0] >> 2) & 0x3f]; 416 | 417 | if (len == 1) { 418 | *d++ = basis[(s[0] & 3) << 4]; 419 | if (padding) { 420 | *d++ = padding; 421 | } 422 | 423 | } else { 424 | *d++ = basis[((s[0] & 3) << 4) | (s[1] >> 4)]; 425 | *d++ = basis[(s[1] & 0x0f) << 2]; 426 | } 427 | 428 | if (padding) { 429 | *d++ = padding; 430 | } 431 | } 432 | 433 | return d; 434 | } 435 | 436 | ngx_int_t 437 | ngx_http_secure_token_sign( 438 | ngx_http_request_t* r, 439 | EVP_PKEY* private_key, 440 | ngx_str_t* message, 441 | ngx_str_t* signature) 442 | { 443 | #if (OPENSSL_VERSION_NUMBER < 0x10100000L) 444 | EVP_MD_CTX md_ctx_buf; 445 | #endif 446 | EVP_MD_CTX* md_ctx; 447 | unsigned int siglen; 448 | 449 | signature->data = ngx_pnalloc(r->pool, EVP_PKEY_size(private_key) + 1); 450 | if (signature->data == NULL) 451 | { 452 | return NGX_ERROR; 453 | } 454 | 455 | #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) 456 | md_ctx = EVP_MD_CTX_new(); 457 | if (md_ctx == NULL) 458 | { 459 | return NGX_ERROR; 460 | } 461 | #else 462 | md_ctx = &md_ctx_buf; 463 | EVP_MD_CTX_init(md_ctx); 464 | #endif 465 | 466 | if (!EVP_SignInit_ex(md_ctx, EVP_sha1(), NULL)) 467 | { 468 | goto error; 469 | } 470 | 471 | if (!EVP_SignUpdate(md_ctx, message->data, message->len)) 472 | { 473 | goto error; 474 | } 475 | 476 | if (!EVP_SignFinal(md_ctx, signature->data, &siglen, private_key)) 477 | { 478 | goto error; 479 | } 480 | 481 | #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) 482 | EVP_MD_CTX_free(md_ctx); 483 | #else 484 | EVP_MD_CTX_cleanup(md_ctx); 485 | #endif 486 | 487 | signature->len = siglen; 488 | return NGX_OK; 489 | 490 | error: 491 | 492 | #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) 493 | EVP_MD_CTX_free(md_ctx); 494 | #else 495 | EVP_MD_CTX_cleanup(md_ctx); 496 | #endif 497 | return NGX_ERROR; 498 | } 499 | 500 | // code copied from ngx_escape_html, added apos 501 | static uintptr_t 502 | ngx_escape_xml(u_char *dst, u_char *src, size_t size) 503 | { 504 | u_char ch; 505 | ngx_uint_t len; 506 | 507 | if (dst == NULL) { 508 | 509 | len = 0; 510 | 511 | while (size) { 512 | switch (*src++) { 513 | 514 | case '<': 515 | len += sizeof("<") - 2; 516 | break; 517 | 518 | case '>': 519 | len += sizeof(">") - 2; 520 | break; 521 | 522 | case '&': 523 | len += sizeof("&") - 2; 524 | break; 525 | 526 | case '"': 527 | len += sizeof(""") - 2; 528 | break; 529 | 530 | case '\'': 531 | len += sizeof("'") - 2; 532 | break; 533 | 534 | default: 535 | break; 536 | } 537 | size--; 538 | } 539 | 540 | return (uintptr_t) len; 541 | } 542 | 543 | while (size) { 544 | ch = *src++; 545 | 546 | switch (ch) { 547 | 548 | case '<': 549 | *dst++ = '&'; *dst++ = 'l'; *dst++ = 't'; *dst++ = ';'; 550 | break; 551 | 552 | case '>': 553 | *dst++ = '&'; *dst++ = 'g'; *dst++ = 't'; *dst++ = ';'; 554 | break; 555 | 556 | case '&': 557 | *dst++ = '&'; *dst++ = 'a'; *dst++ = 'm'; *dst++ = 'p'; 558 | *dst++ = ';'; 559 | break; 560 | 561 | case '"': 562 | *dst++ = '&'; *dst++ = 'q'; *dst++ = 'u'; *dst++ = 'o'; 563 | *dst++ = 't'; *dst++ = ';'; 564 | break; 565 | 566 | case '\'': 567 | *dst++ = '&'; *dst++ = 'a'; *dst++ = 'p'; *dst++ = 'o'; 568 | *dst++ = 's'; *dst++ = ';'; 569 | break; 570 | 571 | default: 572 | *dst++ = ch; 573 | break; 574 | } 575 | size--; 576 | } 577 | 578 | return (uintptr_t) dst; 579 | } 580 | 581 | ngx_int_t 582 | ngx_http_secure_token_escape_xml( 583 | ngx_pool_t* pool, 584 | ngx_str_t* src, 585 | ngx_str_t* dst) 586 | { 587 | uintptr_t escape_xml; 588 | 589 | escape_xml = ngx_escape_xml(NULL, src->data, src->len); 590 | if (escape_xml == 0) 591 | { 592 | *dst = *src; 593 | return NGX_OK; 594 | } 595 | 596 | dst->len = src->len + escape_xml; 597 | dst->data = ngx_pnalloc(pool, dst->len + 1); 598 | if (dst->data == NULL) 599 | { 600 | return NGX_ERROR; 601 | } 602 | 603 | ngx_escape_xml(dst->data, src->data, src->len); 604 | dst->data[dst->len] = '\0'; 605 | 606 | return NGX_OK; 607 | } 608 | 609 | // Note: copy of ngx_conf_check_num_bounds adjusted for string length validation 610 | char * 611 | ngx_conf_check_str_len_bounds(ngx_conf_t *cf, void *post, void *data) 612 | { 613 | ngx_conf_num_bounds_t *bounds = post; 614 | ngx_str_t *sp = data; 615 | 616 | if (bounds->high == -1) { 617 | if (sp->len >= (size_t)bounds->low) { 618 | return NGX_CONF_OK; 619 | } 620 | 621 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 622 | "value must be equal to or greater than %i", 623 | bounds->low); 624 | 625 | return NGX_CONF_ERROR; 626 | } 627 | 628 | if (sp->len >= (size_t)bounds->low && sp->len <= (size_t)bounds->high) { 629 | return NGX_CONF_OK; 630 | } 631 | 632 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 633 | "value must be between %i and %i", 634 | bounds->low, bounds->high); 635 | 636 | return NGX_CONF_ERROR; 637 | } 638 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Secure token module for Nginx [![Build Status](https://travis-ci.org/kaltura/nginx-secure-token-module.svg?branch=master)](https://travis-ci.org/kaltura/nginx-secure-token-module) 2 | 3 | Generates CDN tokens, either as a cookie or as a query string parameter (m3u8,mpd,f4m only). 4 | Currently supports Akamai v2 tokens, and Amazon CloudFront tokens. 5 | In addition, the module supports the encryption of URIs with a configured key. 6 | 7 | ## Build 8 | 9 | To link statically against nginx, cd to nginx source directory and execute: 10 | 11 | ./configure --add-module=/path/to/nginx-secure-token-module 12 | 13 | To compile as a dynamic module (nginx 1.9.11+), use: 14 | 15 | ./configure --add-dynamic-module=/path/to/nginx-secure-token-module 16 | 17 | In this case, the `load_module` directive should be used in nginx.conf to load the module. 18 | 19 | Requires OpenSSL. 20 | 21 | ## Configuration 22 | 23 | ### Generic token parameters 24 | 25 | #### secure_token 26 | * **syntax**: `secure_token value` 27 | * **default**: `none` 28 | * **context**: `http`, `server`, `location` 29 | 30 | Sets the value of the token that should be embedded in the manifest/returned as a cookie. 31 | The parameter value can contain variables, and often points to variables set by this module 32 | (using `secure_token_akamai` / `secure_token_cloudfront` blocks) 33 | 34 | #### secure_token_avoid_cookies 35 | * **syntax**: `secure_token_avoid_cookies on/off` 36 | * **default**: `on` 37 | * **context**: `http`, `server`, `location` 38 | 39 | When enabled the module prefers to use a query string token instead of a cookie token. 40 | A query string token is currently supported only for the following mime types (other mime types return a cookie token): 41 | * application/vnd.apple.mpegurl 42 | * application/dash+xml 43 | * video/f4m 44 | 45 | #### secure_token_types 46 | * **syntax**: `secure_token_types mime_type ...` 47 | * **default**: `none` 48 | * **context**: `http`, `server`, `location` 49 | 50 | Defines a set of mime types that should return a token 51 | 52 | #### secure_token_uri_filename_prefix 53 | * **syntax**: `secure_token_uri_filename_prefix prefix` 54 | * **default**: `none` 55 | * **context**: `http`, `server`, `location` 56 | 57 | Defines a set of prefixes that will be matched against the URI file name, only URIs whose file name 58 | starts with one of the defined prefixes will return a token 59 | 60 | #### secure_token_expires_time 61 | * **syntax**: `secure_token_expires_time time` 62 | * **default**: `none` 63 | * **context**: `http`, `server`, `location` 64 | 65 | Sets the expiration time of responses that are not tokenized 66 | (determines the values of the Cache-Control and Expires HTTP headers) 67 | 68 | #### secure_token_cookie_token_expires_time 69 | * **syntax**: `secure_token_cookie_token_expires_time time` 70 | * **default**: `none` 71 | * **context**: `http`, `server`, `location` 72 | 73 | Sets the expiration time of responses that are tokenized with a cookie token 74 | (determines the values of the Cache-Control and Expires HTTP headers) 75 | 76 | #### secure_token_query_token_expires_time 77 | * **syntax**: `secure_token_query_token_expires_time time` 78 | * **default**: `none` 79 | * **context**: `http`, `server`, `location` 80 | 81 | Sets the expiration time of responses that are tokenized with a query string token 82 | (determines the values of the Cache-Control and Expires HTTP headers) 83 | 84 | #### secure_token_cache_scope 85 | * **syntax**: `secure_token_cache_scope scope` 86 | * **default**: `public` 87 | * **context**: `http`, `server`, `location` 88 | 89 | Sets the cache scope (public/private) of responses that are not tokenized 90 | 91 | #### secure_token_token_cache_scope 92 | * **syntax**: `secure_token_token_cache_scope scope` 93 | * **default**: `private` 94 | * **context**: `http`, `server`, `location` 95 | 96 | Sets the cache scope (public/private) of responses that are tokenized (query / cookie) 97 | 98 | #### secure_token_last_modified 99 | * **syntax**: `secure_token_last_modified time` 100 | * **default**: `Sun, 19 Nov 2000 08:52:00 GMT` 101 | * **context**: `http`, `server`, `location` 102 | 103 | Sets the value of the last-modified header of responses that are not tokenized. 104 | An empty string leaves the value of last-modified unaltered, while the string "now" sets the header to the server current time. 105 | 106 | #### secure_token_token_last_modified 107 | * **syntax**: `secure_token_token_last_modified time` 108 | * **default**: `now` 109 | * **context**: `http`, `server`, `location` 110 | 111 | Sets the value of the last-modified header of responses that are tokenized (query / cookie) 112 | An empty string leaves the value of last-modified unaltered, while the string "now" sets the header to the server current time. 113 | 114 | #### secure_token_content_type_m3u8 115 | * **syntax**: `secure_token_content_type_m3u8 type` 116 | * **default**: `application/vnd.apple.mpegurl` 117 | * **context**: `http`, `server`, `location` 118 | 119 | Sets the content type that should be parsed as m3u8 for token insertion 120 | 121 | #### secure_token_content_type_mpd 122 | * **syntax**: `secure_token_content_type_mpd type` 123 | * **default**: `application/dash+xml` 124 | * **context**: `http`, `server`, `location` 125 | 126 | Sets the content type that should be parsed as mpd for token insertion 127 | 128 | #### secure_token_content_type_f4m 129 | * **syntax**: `secure_token_content_type_f4m type` 130 | * **default**: `video/f4m` 131 | * **context**: `http`, `server`, `location` 132 | 133 | Sets the content type that should be parsed as f4m for token insertion 134 | 135 | ### Akamai token parameters 136 | 137 | #### secure_token_akamai 138 | * **syntax**: `secure_token_akamai $variable { ... }` 139 | * **context**: `http` 140 | 141 | Creates a new variable whose value is an Akamai token, created according to the 142 | parameters specified within the block. 143 | 144 | The block supports the following parameters: 145 | 146 | #### key 147 | * **syntax**: `key key_hex` 148 | * **default**: `N/A (mandatory)` 149 | 150 | Sets the secret key. 151 | 152 | #### param_name 153 | * **syntax**: `param_name name` 154 | * **default**: `__hdnea__` 155 | 156 | Sets the token parameter name (either the name of the cookie or the query string parameter) 157 | 158 | #### acl 159 | * **syntax**: `acl acl` 160 | * **default**: `$secure_token_baseuri_comma` 161 | 162 | Sets the signed part of the URL (ACL). The parameter value can contain variables. 163 | 164 | #### start 165 | * **syntax**: `start time` 166 | * **default**: `0` 167 | 168 | Sets the start time of the token (see `Time format` below) 169 | 170 | #### end 171 | * **syntax**: `end time` 172 | * **default**: `86400` 173 | 174 | Sets the end time of the token (see `Time format` below) 175 | 176 | #### ip_address 177 | * **syntax**: `ip_address address` 178 | * **default**: `none` 179 | 180 | Sets the IP address that should be embedded in the token. 181 | The parameter value can contain variables, e.g. $remote_addr. 182 | 183 | ### CloudFront token parameters 184 | 185 | #### secure_token_cloudfront 186 | * **syntax**: `secure_token_cloudfront $variable { ... }` 187 | * **context**: `http` 188 | 189 | Creates a new variable whose value is a CloudFront token, created according to the 190 | parameters specified within the block. 191 | 192 | The block supports the following parameters: 193 | 194 | #### private_key_file 195 | * **syntax**: `private_key_file filename` 196 | * **default**: `N/A (mandatory)` 197 | 198 | Sets the file name of the private key (PEM file) 199 | 200 | #### key_pair_id 201 | * **syntax**: `key_pair_id id` 202 | * **default**: `N/A (mandatory)` 203 | 204 | Sets the key pair id 205 | 206 | #### acl 207 | * **syntax**: `acl acl` 208 | * **default**: `$secure_token_baseuri_comma` 209 | 210 | Sets the signed part of the URL (ACL). The parameter value can contain variables. 211 | 212 | #### end 213 | * **syntax**: `end time` 214 | * **default**: `86400` 215 | 216 | Sets the end time of the token (see `Time format` below) 217 | 218 | #### ip_address 219 | * **syntax**: `ip_address address` 220 | * **default**: `none` 221 | 222 | Sets the IP address that should be embedded in the token. 223 | The parameter value can contain variables, e.g. $remote_addr/32 can be used to limit the token to the specific IP of the client. 224 | 225 | ### Broadpeak token parameters 226 | 227 | #### secure_token_broadpeak 228 | * **syntax**: `secure_token_broadpeak $variable { ... }` 229 | * **context**: `http` 230 | 231 | Creates a new variable whose value is a Broadpeak token, created according to the 232 | parameters specified within the block. 233 | 234 | The block supports the following parameters: 235 | 236 | #### key 237 | * **syntax**: `key key` 238 | * **default**: `N/A (mandatory)` 239 | 240 | Sets the secret key. The parameter value can contain variables. 241 | 242 | #### param_name 243 | * **syntax**: `param_name name` 244 | * **default**: `token` 245 | 246 | Sets the token parameter name (either the name of the cookie or the query string parameter) 247 | 248 | #### acl 249 | * **syntax**: `acl acl` 250 | * **default**: `$secure_token_baseuri_comma` 251 | 252 | Sets the signed part of the URL (ACL). The parameter value can contain variables. 253 | 254 | #### start 255 | * **syntax**: `start time` 256 | * **default**: `0` 257 | 258 | Sets the start time of the token (see `Time format` below) 259 | 260 | #### end 261 | * **syntax**: `end time` 262 | * **default**: `86400` 263 | 264 | Sets the end time of the token (see `Time format` below) 265 | 266 | #### session_start 267 | * **syntax**: `session_start time` 268 | * **default**: `N/A` 269 | 270 | Sets the start time of the session, required for catchup. The parameter value can contain variables. 271 | 272 | #### session_end 273 | * **syntax**: `session_end time` 274 | * **default**: `N/A` 275 | 276 | Sets the end time of the session, required for catchup. The parameter value can contain variables. 277 | 278 | #### additional_querylist 279 | * **syntax**: `additional_querylist expr` 280 | * **default**: `N/A` 281 | 282 | Sets the primary token value, the value needs to be a list of name=value pairs without any separator. 283 | For example, "ip=${arg_ip}account=${arg_account}device=${arg_device}". 284 | The parameter value can contain variables. 285 | 286 | ### URI encryption parameters 287 | 288 | #### secure_token_encrypt_uri 289 | * **syntax**: `secure_token_encrypt_uri on/off` 290 | * **default**: `off` 291 | * **context**: `http`, `server`, `location` 292 | 293 | Enables/disables uri encryption 294 | 295 | #### secure_token_encrypt_uri_key 296 | * **syntax**: `secure_token_encrypt_uri_key key_hex` 297 | * **default**: `none` 298 | * **context**: `http`, `server`, `location` 299 | 300 | Sets the encryption key, the key has to be 256 bits (64 hex characters) 301 | 302 | #### secure_token_encrypt_uri_iv 303 | * **syntax**: `secure_token_encrypt_uri_iv iv_hex` 304 | * **default**: `none` 305 | * **context**: `http`, `server`, `location` 306 | 307 | Sets the encryption iv, the iv has to be 128 bits (32 hex characters) 308 | 309 | #### secure_token_encrypt_uri_part 310 | * **syntax**: `secure_token_encrypt_uri_part expression` 311 | * **default**: `none` 312 | * **context**: `http`, `server`, `location` 313 | 314 | An expression that calculates the part of the URL that should be encrypted in regular expression locations. 315 | For non-regular expression locations, the encrypted part is everything following the path defined on the location block. 316 | 317 | Example 1: 318 | ``` 319 | location /secret_param/([^/]+)/some_other_param/.* { 320 | secure_token_encrypt_uri_part $1; 321 | ... 322 | } 323 | ``` 324 | In this configuration, only the value of secret_param will be encrypted/decrypted. 325 | 326 | Example 2: 327 | ``` 328 | location /base/ { 329 | ... 330 | } 331 | ``` 332 | In this configuration, everything following /base/ will be encrypted/decrypted. 333 | 334 | #### secure_token_encrypt_uri_hash_size 335 | * **syntax**: `secure_token_encrypt_uri_hash_size size` 336 | * **default**: `8` 337 | * **context**: `http`, `server`, `location` 338 | 339 | The size in bytes of hash used to validate the uri after decryption, the value has to be between 0 and 16. 340 | 341 | ### Time format 342 | 343 | Some of the configuration parameters mentioned above, support both absolute timestamps, 344 | and timestamps relative to `now`. 345 | These parameters can be set in the configuration using one of the following formats: 346 | * `epoch` - unix timestamp 0 (01/01/1970) 347 | * `max` - unix timestamp 2147483647 (18/01/2038) 348 | * `@1481230000` - unix timestamp 1481230000 (8/12/2016) 349 | * `10d` / `+10d` - `now` + 10 days 350 | * `-5m` - `now` - 5 minutes 351 | 352 | ## Sample configurations 353 | 354 | ### HLS packaging with Akamai tokens 355 | ``` 356 | secure_token_akamai $token { 357 | key 1234; 358 | acl "$secure_token_baseuri_comma*"; 359 | } 360 | 361 | server { 362 | 363 | location ~ ^/hls/p/\d+/(sp/\d+/)?serveFlavor/ { 364 | vod hls; 365 | 366 | g2o on; 367 | 368 | secure_token $token; 369 | secure_token_types application/vnd.apple.mpegurl; 370 | 371 | secure_token_expires_time 100d; 372 | secure_token_query_token_expires_time 1h; 373 | 374 | more_set_headers 'Access-Control-Allow-Headers: *'; 375 | more_set_headers 'Access-Control-Expose-Headers: Server,range,Content-Length,Content-Range'; 376 | more_set_headers 'Access-Control-Allow-Methods: GET, HEAD, OPTIONS'; 377 | more_set_headers 'Access-Control-Allow-Origin: *'; 378 | } 379 | 380 | } 381 | ``` 382 | 383 | ### HDS packaging with CloudFront tokens 384 | ``` 385 | secure_token_cloudfront $token { 386 | private_key_file /path/to/pem; 387 | key_pair_id ABCDEF; 388 | acl "$scheme://$http_host$secure_token_baseuri_comma*"; 389 | } 390 | 391 | server { 392 | 393 | location ~ ^/hds/p/\d+/(sp/\d+/)?serveFlavor/ { 394 | vod hds; 395 | vod_segment_duration 6000; 396 | vod_align_segments_to_key_frames on; 397 | vod_segment_count_policy last_rounded; 398 | 399 | secure_token $token; 400 | secure_token_types video/f4m; 401 | 402 | secure_token_expires_time 100d; 403 | secure_token_query_token_expires_time 1h; 404 | 405 | more_set_headers 'Access-Control-Allow-Headers: *'; 406 | more_set_headers 'Access-Control-Expose-Headers: Server,range,Content-Length,Content-Range'; 407 | more_set_headers 'Access-Control-Allow-Methods: GET, HEAD, OPTIONS'; 408 | more_set_headers 'Access-Control-Allow-Origin: *'; 409 | } 410 | 411 | } 412 | ``` 413 | 414 | ### Encrypted HLS with token security on the encryption key 415 | 416 | This configuration enables token security while having static URLs for the video segments, 417 | this enables the caching of the segments transparently by proxies. 418 | ``` 419 | secure_token_akamai $token { 420 | key 1234; 421 | acl "$secure_token_baseuri_comma*"; 422 | } 423 | 424 | server { 425 | 426 | location ~ ^/s/hls/enc/p/\d+/(sp/\d+/)?serveFlavor/ { 427 | vod hls; 428 | vod_secret_key "password$vod_filepath"; 429 | 430 | secure_token $token; 431 | secure_token_types application/vnd.apple.mpegurl; 432 | 433 | secure_token_expires_time 100d; 434 | secure_token_query_token_expires_time 1h; 435 | 436 | secure_token_uri_filename_prefix index; 437 | secure_token_tokenize_segments off; 438 | 439 | akamai_token_validate $arg___hdnea__; 440 | akamai_token_validate_key 1234; 441 | akamai_token_validate_uri_filename_prefix encryption; 442 | akamai_token_validate_uri_filename_prefix index; 443 | } 444 | 445 | } 446 | ``` 447 | Note: this configuration requires the module https://github.com/kaltura/nginx-akamai-token-validate-module 448 | in addition to nginx-secure-token-module 449 | 450 | ### Adding token security on top of an existing HDS/HLS live stream 451 | ``` 452 | secure_token_akamai $token { 453 | key 1234; 454 | acl "$secure_token_baseuri_comma*"; 455 | } 456 | 457 | server { 458 | 459 | location /secure-live/ { 460 | proxy_pass http://original.live.domain; 461 | 462 | secure_token $token; 463 | secure_token_types text/xml application/vnd.apple.mpegurl; 464 | secure_token_content_type_f4m text/xml; 465 | 466 | secure_token_expires_time 100d; 467 | secure_token_query_token_expires_time 1h; 468 | 469 | akamai_token_validate $arg___hdnea__; 470 | akamai_token_validate_key 1234; 471 | akamai_token_validate_strip_token __hdnea__; 472 | } 473 | 474 | } 475 | ``` 476 | Note: this configuration requires the module https://github.com/kaltura/nginx-akamai-token-validate-module 477 | in addition to nginx-secure-token-module 478 | 479 | ### URI encryption 480 | ``` 481 | location ~ ^/hls/p/\d+/(sp/\d+/)?serveFlavor/entryId/([^/]+)/(.*) { 482 | vod hls; 483 | vod_secret_key "password$2"; 484 | 485 | secure_token_encrypt_uri on; 486 | secure_token_encrypt_uri_key 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f; 487 | secure_token_encrypt_uri_iv 00000000000000000000000000000000; 488 | secure_token_encrypt_uri_part $3; 489 | secure_token_types application/vnd.apple.mpegurl; 490 | 491 | add_header Last-Modified "Sun, 19 Nov 2000 08:52:00 GMT"; 492 | expires 100d; 493 | } 494 | ``` 495 | 496 | ## Nginx variables 497 | 498 | The module adds the following nginx variables: 499 | * `$secure_token_baseuri` - contains the value of the `$uri` built in variable truncated up to the last slash (/). 500 | For exmaple, if `$uri` is /a/b/c.htm then `$secure_token_baseuri` will be /a/b/. 501 | * `$secure_token_baseuri_comma` - same as `$secure_token_baseuri`, except that if this value contains a comma (,) 502 | the value is truncated up to the comma position. 503 | For exmaple, if `$uri` is /a/b/c.htm then `$secure_token_baseuri_comma` will be /a/b/; 504 | if `$uri` is /a/b,c/d.htm then `$secure_token_baseuri_comma` will be /a/b. 505 | * `$secure_token_original_uri` - contains the original (encrypted) uri when using uri encryption. 506 | Note that the built in `$uri` variable contains the modified (decrypted) uri in this case. 507 | 508 | ## Copyright & License 509 | 510 | All code in this project is released under the [AGPLv3 license](http://www.gnu.org/licenses/agpl-3.0.html) unless a different license for a particular library is specified in the applicable library path. 511 | 512 | Copyright © Kaltura Inc. All rights reserved. 513 | -------------------------------------------------------------------------------- /ngx_http_secure_token_processor_base.c: -------------------------------------------------------------------------------- 1 | #include "ngx_http_secure_token_processor_base.h" 2 | #include "ngx_http_secure_token_filter_module.h" 3 | #include "ngx_http_secure_token_encrypt_uri.h" 4 | #include "ngx_http_secure_token_utils.h" 5 | #include "ngx_http_secure_token_m3u8.h" 6 | #include "ngx_http_secure_token_xml.h" 7 | 8 | // typedefs 9 | enum { 10 | TOKEN_PREFIX_NONE, 11 | TOKEN_PREFIX_QUESTION, 12 | TOKEN_PREFIX_AMPERSAND, 13 | 14 | TOKEN_PREFIX_COUNT 15 | }; 16 | 17 | struct ngx_http_secure_token_ctx_s { 18 | ngx_chain_t* out; 19 | ngx_chain_t** last_out; 20 | ngx_chain_t* busy; 21 | ngx_chain_t* free; 22 | 23 | ngx_str_t prefixed_tokens[TOKEN_PREFIX_COUNT]; 24 | ngx_str_t token; 25 | ngx_http_secure_token_body_processor_t process; 26 | off_t processor_context_offset; 27 | void* processor_params; 28 | union { 29 | ngx_http_secure_token_m3u8_ctx_t m3u8; 30 | ngx_http_secure_token_xml_ctx_t xml; 31 | } u; 32 | }; 33 | 34 | typedef ngx_int_t (*ngx_http_secure_token_escape_t)( 35 | ngx_pool_t* pool, 36 | ngx_str_t* src, 37 | ngx_str_t* dest); 38 | 39 | typedef struct { 40 | off_t content_type_offset; // in ngx_http_secure_token_loc_conf_t 41 | ngx_http_secure_token_body_processor_t process; 42 | off_t processor_context_offset; // in ngx_http_secure_token_ctx_t 43 | void* processor_params; 44 | ngx_http_secure_token_escape_t escape; 45 | } body_processor_t; 46 | 47 | // body processors config constants 48 | static ngx_str_t mpd_segment_template_attrs[] = { 49 | ngx_string("media"), 50 | ngx_string("initialization"), 51 | ngx_null_string 52 | }; 53 | 54 | static ngx_str_t mpd_initialization_attrs[] = { 55 | ngx_string("sourceURL"), 56 | ngx_null_string 57 | }; 58 | 59 | static ngx_str_t mpd_segmenturl_attrs[] = { 60 | ngx_string("media"), 61 | ngx_null_string 62 | }; 63 | 64 | static ngx_http_secure_token_xml_node_attrs_t mpd_nodes[] = { 65 | { ngx_string("SegmentTemplate"), mpd_segment_template_attrs }, 66 | { ngx_string("Initialization"), mpd_initialization_attrs }, 67 | { ngx_string("SegmentURL"), mpd_segmenturl_attrs }, 68 | { ngx_string("BaseURL"), NULL }, 69 | { ngx_null_string, NULL } 70 | }; 71 | 72 | static ngx_str_t f4m_media_attrs[] = { 73 | ngx_string("url"), 74 | ngx_null_string 75 | }; 76 | 77 | static ngx_str_t f4m_bootstrap_info_attrs[] = { 78 | ngx_string("url"), 79 | ngx_null_string 80 | }; 81 | 82 | static ngx_http_secure_token_xml_node_attrs_t f4m_nodes[] = { 83 | { ngx_string("media"), f4m_media_attrs }, 84 | { ngx_string("bootstrapInfo"), f4m_bootstrap_info_attrs }, 85 | { ngx_null_string, NULL } 86 | }; 87 | 88 | static body_processor_t body_processors[] = { 89 | { 90 | offsetof(ngx_http_secure_token_loc_conf_t, content_type_m3u8), 91 | (ngx_http_secure_token_body_processor_t)ngx_http_secure_token_m3u8_processor, 92 | offsetof(ngx_http_secure_token_ctx_t, u.m3u8), 93 | NULL, 94 | NULL, 95 | }, 96 | { 97 | offsetof(ngx_http_secure_token_loc_conf_t, content_type_mpd), 98 | (ngx_http_secure_token_body_processor_t)ngx_http_secure_token_xml_processor, 99 | offsetof(ngx_http_secure_token_ctx_t, u.xml), 100 | &mpd_nodes, 101 | ngx_http_secure_token_escape_xml, 102 | }, 103 | { 104 | offsetof(ngx_http_secure_token_loc_conf_t, content_type_f4m), 105 | (ngx_http_secure_token_body_processor_t)ngx_http_secure_token_xml_processor, 106 | offsetof(ngx_http_secure_token_ctx_t, u.xml), 107 | &f4m_nodes, 108 | ngx_http_secure_token_escape_xml, 109 | }, 110 | }; 111 | 112 | // misc constants 113 | static ngx_str_t token_prefixes[TOKEN_PREFIX_COUNT] = { 114 | ngx_string(""), 115 | ngx_string("?"), 116 | ngx_string("&"), 117 | }; 118 | 119 | static u_char scheme_delimeter[] = "://"; 120 | 121 | // globals 122 | static ngx_http_output_body_filter_pt ngx_http_next_body_filter; 123 | 124 | void 125 | ngx_http_secure_token_url_state_machine_init( 126 | ngx_http_secure_token_base_ctx_t* ctx, 127 | ngx_flag_t tokenize, 128 | int url_end_state, 129 | u_char url_end_char) 130 | { 131 | ctx->state = STATE_URL_SCHEME; 132 | ctx->scheme_pos = 0; 133 | ctx->scheme_delim_pos = 0; 134 | ctx->last_url_char = '\0'; 135 | ctx->tokenize = tokenize; 136 | ctx->url_end_state = url_end_state; 137 | ctx->url_end_char = url_end_char; 138 | } 139 | 140 | ngx_flag_t 141 | ngx_http_secure_token_is_http_scheme( 142 | ngx_http_secure_token_base_ctx_t* ctx) 143 | { 144 | switch (ctx->scheme_pos) 145 | { 146 | case 4: 147 | return memcmp(ctx->scheme, "http", 4) == 0; 148 | 149 | case 5: 150 | return memcmp(ctx->scheme, "https", 5) == 0; 151 | 152 | default: 153 | return 0; 154 | } 155 | } 156 | 157 | ngx_int_t 158 | ngx_http_secure_token_url_state_machine( 159 | ngx_http_request_t* r, 160 | ngx_http_secure_token_processor_conf_t* conf, 161 | ngx_http_secure_token_base_ctx_t* ctx, 162 | u_char** cur_pos, 163 | u_char* buffer_end, 164 | ngx_http_secure_token_processor_output_t* output) 165 | { 166 | ngx_int_t rc; 167 | u_char* new_uri_path; 168 | u_char ch; 169 | 170 | for (; (*cur_pos) < buffer_end; (*cur_pos)++) 171 | { 172 | ch = **cur_pos; 173 | 174 | if (ch == ctx->url_end_char || (isspace(ch) && ctx->url_end_char == 0)) 175 | { 176 | // end of url 177 | if (conf->encrypt_uri && ctx->state == STATE_URL_PATH) 178 | { 179 | output->copy_input = 0; 180 | 181 | rc = ngx_http_secure_token_encrypt_uri(r, &ctx->uri_path, &output->output_buffer); 182 | if (rc != NGX_OK) 183 | { 184 | return NGX_ERROR; 185 | } 186 | } 187 | 188 | if (ctx->tokenize && ctx->state != STATE_URL_NON_HTTP) 189 | { 190 | if (ctx->state == STATE_URL_QUERY) 191 | { 192 | if (ctx->last_url_char == '?' || ctx->last_url_char == '&') 193 | { 194 | output->token_index = TOKEN_PREFIX_NONE; 195 | } 196 | else 197 | { 198 | output->token_index = TOKEN_PREFIX_AMPERSAND; 199 | } 200 | } 201 | else 202 | { 203 | output->token_index = TOKEN_PREFIX_QUESTION; 204 | } 205 | } 206 | 207 | ctx->last_url_char = ch; 208 | ctx->state = ctx->url_end_state; 209 | 210 | return NGX_OK; 211 | } 212 | 213 | switch (ctx->state) 214 | { 215 | case STATE_URL_SCHEME: 216 | if (ch == scheme_delimeter[ctx->scheme_delim_pos]) 217 | { 218 | if (ch == ':' && !ngx_http_secure_token_is_http_scheme(ctx)) 219 | { 220 | ctx->state = STATE_URL_NON_HTTP; 221 | break; 222 | } 223 | 224 | ctx->scheme_delim_pos++; 225 | if (ctx->scheme_delim_pos >= sizeof(scheme_delimeter) - 1) 226 | { 227 | ctx->state = STATE_URL_HOST; 228 | } 229 | } 230 | else 231 | { 232 | if (ctx->scheme_pos < sizeof(ctx->scheme)) 233 | { 234 | ctx->scheme[ctx->scheme_pos++] = ch; 235 | } 236 | ctx->scheme_delim_pos = 0; 237 | } 238 | break; 239 | 240 | case STATE_URL_HOST: 241 | if (ch != '/') 242 | { 243 | break; 244 | } 245 | 246 | ctx->state = STATE_URL_PATH; 247 | ctx->uri_path.len = 0; 248 | ctx->last_url_char = ch; 249 | 250 | if (conf->encrypt_uri) 251 | { 252 | // flush any buffered data before starting to process the encrypted part 253 | return NGX_OK; 254 | } 255 | // fallthrough 256 | 257 | case STATE_URL_PATH: 258 | ctx->last_url_char = ch; 259 | 260 | if (ch != '?') 261 | { 262 | if (conf->encrypt_uri) 263 | { 264 | if (ctx->uri_path.len >= ctx->uri_path_alloc_size) 265 | { 266 | ctx->uri_path_alloc_size = ngx_max(ctx->uri_path_alloc_size * 2, 1024); 267 | 268 | new_uri_path = ngx_pnalloc(r->pool, ctx->uri_path_alloc_size); 269 | if (new_uri_path == NULL) 270 | { 271 | return NGX_ERROR; 272 | } 273 | 274 | ngx_memcpy(new_uri_path, ctx->uri_path.data, ctx->uri_path.len); 275 | ctx->uri_path.data = new_uri_path; 276 | } 277 | 278 | ctx->uri_path.data[ctx->uri_path.len] = ch; 279 | ctx->uri_path.len++; 280 | 281 | output->copy_input = 0; 282 | } 283 | break; 284 | } 285 | 286 | ctx->state = STATE_URL_QUERY; 287 | 288 | if (conf->encrypt_uri) 289 | { 290 | return ngx_http_secure_token_encrypt_uri(r, &ctx->uri_path, &output->output_buffer); 291 | } 292 | break; 293 | 294 | case STATE_URL_QUERY: 295 | ctx->last_url_char = ch; 296 | break; 297 | } 298 | } 299 | 300 | return NGX_OK; 301 | } 302 | 303 | static ngx_int_t 304 | ngx_http_secure_token_get_token_buffer( 305 | ngx_http_request_t *r, 306 | ngx_http_secure_token_ctx_t* ctx, 307 | ngx_buf_t* b, 308 | int token_index) 309 | { 310 | u_char* p; 311 | 312 | if (ctx->prefixed_tokens[token_index].len == 0) 313 | { 314 | ctx->prefixed_tokens[token_index].data = ngx_pnalloc(r->pool, token_prefixes[token_index].len + ctx->token.len); 315 | if (ctx->prefixed_tokens[token_index].data == NULL) 316 | { 317 | return NGX_ERROR; 318 | } 319 | 320 | p = ngx_copy(ctx->prefixed_tokens[token_index].data, token_prefixes[token_index].data, token_prefixes[token_index].len); 321 | p = ngx_copy(p, ctx->token.data, ctx->token.len); 322 | 323 | ctx->prefixed_tokens[token_index].len = p - ctx->prefixed_tokens[token_index].data; 324 | } 325 | 326 | ngx_memzero(b, sizeof(ngx_buf_t)); 327 | 328 | b->memory = 1; 329 | b->pos = ctx->prefixed_tokens[token_index].data; 330 | b->last = ctx->prefixed_tokens[token_index].data + ctx->prefixed_tokens[token_index].len; 331 | 332 | return NGX_OK; 333 | } 334 | 335 | // A slightly simplified version of ngx_http_sub_output 336 | static ngx_int_t 337 | ngx_http_secure_token_output(ngx_http_request_t *r, ngx_http_secure_token_ctx_t *ctx) 338 | { 339 | ngx_int_t rc; 340 | ngx_buf_t *b; 341 | ngx_chain_t *cl; 342 | 343 | #if 1 344 | b = NULL; 345 | for (cl = ctx->out; cl; cl = cl->next) { 346 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 347 | "secure token out: %p %p", cl->buf, cl->buf->pos); 348 | if (cl->buf == b) { 349 | ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, 350 | "the same buf was used in secure token"); 351 | ngx_debug_point(); 352 | return NGX_ERROR; 353 | } 354 | b = cl->buf; 355 | } 356 | #endif 357 | 358 | rc = ngx_http_next_body_filter(r, ctx->out); 359 | 360 | if (ctx->busy == NULL) { 361 | ctx->busy = ctx->out; 362 | 363 | } 364 | else { 365 | for (cl = ctx->busy; cl->next; cl = cl->next) { /* void */ } 366 | cl->next = ctx->out; 367 | } 368 | 369 | ctx->out = NULL; 370 | ctx->last_out = &ctx->out; 371 | 372 | while (ctx->busy) { 373 | 374 | cl = ctx->busy; 375 | b = cl->buf; 376 | 377 | if (ngx_buf_size(b) != 0) { 378 | break; 379 | } 380 | 381 | if (b->shadow) { 382 | b->shadow->pos = b->shadow->last; 383 | } 384 | 385 | ctx->busy = cl->next; 386 | 387 | if (ngx_buf_in_memory(b) || b->in_file) { 388 | /* add data bufs only to the free buf chain */ 389 | 390 | cl->next = ctx->free; 391 | ctx->free = cl; 392 | } 393 | } 394 | 395 | return rc; 396 | } 397 | 398 | // the implementation is based on ngx_http_sub_body_filter 399 | static ngx_int_t 400 | ngx_http_secure_token_body_filter(ngx_http_request_t *r, ngx_chain_t *in) 401 | { 402 | ngx_http_secure_token_processor_output_t output; 403 | ngx_http_secure_token_loc_conf_t *conf; 404 | ngx_http_secure_token_ctx_t* ctx; 405 | ngx_chain_t* in_copy = NULL; 406 | ngx_chain_t* cl; 407 | ngx_buf_t* buf; 408 | ngx_buf_t* b; 409 | ngx_int_t rc; 410 | u_char* copy_start; 411 | u_char* pos = NULL; 412 | 413 | ctx = ngx_http_get_module_ctx(r, ngx_http_secure_token_filter_module); 414 | 415 | if (ctx == NULL) { 416 | return ngx_http_next_body_filter(r, in); 417 | } 418 | 419 | if ((in == NULL 420 | && ctx->busy == NULL)) 421 | { 422 | return ngx_http_next_body_filter(r, in); 423 | } 424 | 425 | conf = ngx_http_get_module_loc_conf(r, ngx_http_secure_token_filter_module); 426 | 427 | /* add the incoming chain to the chain in_copy */ 428 | 429 | if (in) { 430 | if (ngx_chain_add_copy(r->pool, &in_copy, in) != NGX_OK) { 431 | return NGX_ERROR; 432 | } 433 | } 434 | 435 | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 436 | "http secure token filter \"%V\"", &r->uri); 437 | 438 | buf = NULL; 439 | 440 | while (in_copy || buf) { 441 | 442 | if (buf == NULL) { 443 | buf = in_copy->buf; 444 | in_copy = in_copy->next; 445 | pos = buf->pos; 446 | } 447 | 448 | b = NULL; 449 | 450 | while (pos < buf->last) { 451 | 452 | copy_start = pos; 453 | 454 | output.copy_input = 1; 455 | output.output_buffer.len = 0; 456 | output.token_index = -1; 457 | 458 | rc = ctx->process( 459 | r, 460 | &conf->processor_conf, 461 | ctx->processor_params, 462 | &pos, 463 | buf->last, 464 | (u_char*)ctx + ctx->processor_context_offset, 465 | &output); 466 | 467 | ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 468 | "process: %d, %p-%p", 469 | rc, copy_start, pos); 470 | 471 | if (rc == NGX_ERROR) { 472 | return rc; 473 | } 474 | 475 | if (output.copy_input && copy_start != pos) { 476 | 477 | cl = ngx_chain_get_free_buf(r->pool, &ctx->free); 478 | if (cl == NULL) { 479 | return NGX_ERROR; 480 | } 481 | 482 | b = cl->buf; 483 | 484 | ngx_memcpy(b, buf, sizeof(ngx_buf_t)); 485 | 486 | b->pos = copy_start; 487 | b->last = pos; 488 | b->shadow = NULL; 489 | b->last_buf = 0; 490 | b->last_in_chain = 0; 491 | b->recycled = 0; 492 | 493 | if (b->in_file) { 494 | b->file_last = b->file_pos + (b->last - buf->pos); 495 | b->file_pos += b->pos - buf->pos; 496 | } 497 | 498 | *ctx->last_out = cl; 499 | ctx->last_out = &cl->next; 500 | } 501 | 502 | if (output.output_buffer.len != 0) 503 | { 504 | cl = ngx_chain_get_free_buf(r->pool, &ctx->free); 505 | if (cl == NULL) { 506 | return NGX_ERROR; 507 | } 508 | 509 | b = cl->buf; 510 | 511 | ngx_memzero(b, sizeof(ngx_buf_t)); 512 | 513 | b->memory = 1; 514 | b->pos = output.output_buffer.data; 515 | b->last = output.output_buffer.data + output.output_buffer.len; 516 | 517 | *ctx->last_out = cl; 518 | ctx->last_out = &cl->next; 519 | } 520 | 521 | if (output.token_index >= 0 && ctx->token.len != 0) 522 | { 523 | cl = ngx_chain_get_free_buf(r->pool, &ctx->free); 524 | if (cl == NULL) { 525 | return NGX_ERROR; 526 | } 527 | 528 | b = cl->buf; 529 | 530 | rc = ngx_http_secure_token_get_token_buffer(r, ctx, b, output.token_index); 531 | if (rc != NGX_OK) 532 | { 533 | return rc; 534 | } 535 | 536 | *ctx->last_out = cl; 537 | ctx->last_out = &cl->next; 538 | } 539 | 540 | continue; 541 | } 542 | 543 | if (buf->last_buf || buf->flush || buf->sync 544 | || ngx_buf_in_memory(buf)) 545 | { 546 | if (b == NULL) { 547 | cl = ngx_chain_get_free_buf(r->pool, &ctx->free); 548 | if (cl == NULL) { 549 | return NGX_ERROR; 550 | } 551 | 552 | b = cl->buf; 553 | 554 | ngx_memzero(b, sizeof(ngx_buf_t)); 555 | 556 | b->sync = 1; 557 | 558 | *ctx->last_out = cl; 559 | ctx->last_out = &cl->next; 560 | } 561 | 562 | b->last_buf = buf->last_buf; 563 | b->last_in_chain = buf->last_in_chain; 564 | b->flush = buf->flush; 565 | b->shadow = buf; 566 | 567 | b->recycled = buf->recycled; 568 | } 569 | 570 | buf = NULL; 571 | } 572 | 573 | if (ctx->out == NULL && ctx->busy == NULL) { 574 | return NGX_OK; 575 | } 576 | 577 | return ngx_http_secure_token_output(r, ctx); 578 | } 579 | 580 | ngx_int_t 581 | ngx_http_secure_token_init_processors_hash(ngx_conf_t *cf, ngx_http_secure_token_loc_conf_t* conf) 582 | { 583 | ngx_hash_key_t hash_keys[sizeof(body_processors) / sizeof(body_processors[0])]; 584 | ngx_hash_init_t hash; 585 | ngx_str_t* content_type; 586 | ngx_int_t rc; 587 | unsigned i; 588 | 589 | for (i = 0; i < sizeof(hash_keys) / sizeof(hash_keys[0]); i++) 590 | { 591 | content_type = (ngx_str_t*)((u_char*)conf + body_processors[i].content_type_offset); 592 | hash_keys[i].key = *content_type; 593 | hash_keys[i].key_hash = ngx_hash_key_lc(content_type->data, content_type->len); 594 | hash_keys[i].value = &body_processors[i]; 595 | } 596 | 597 | hash.hash = &conf->processors_hash; 598 | hash.key = ngx_hash_key; 599 | hash.max_size = 512; 600 | hash.bucket_size = 64; 601 | hash.name = "processors_hash"; 602 | hash.pool = cf->pool; 603 | hash.temp_pool = NULL; 604 | 605 | rc = ngx_hash_init(&hash, hash_keys, sizeof(hash_keys) / sizeof(hash_keys[0])); 606 | if (rc != NGX_OK) 607 | { 608 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "ngx_hash_init failed %i", rc); 609 | return rc; 610 | } 611 | 612 | return NGX_OK; 613 | } 614 | 615 | ngx_int_t 616 | ngx_http_secure_token_init_body_filter(ngx_http_request_t *r, ngx_str_t* token) 617 | { 618 | ngx_http_secure_token_loc_conf_t *conf; 619 | ngx_http_secure_token_ctx_t* ctx; 620 | body_processor_t* processor; 621 | ngx_int_t rc; 622 | 623 | conf = ngx_http_get_module_loc_conf(r, ngx_http_secure_token_filter_module); 624 | 625 | // Note: content_type_lowcase is already initialized since we called ngx_http_test_content_type 626 | processor = ngx_hash_find(&conf->processors_hash, r->headers_out.content_type_hash, r->headers_out.content_type_lowcase, r->headers_out.content_type_len); 627 | if (processor == NULL) 628 | { 629 | return NGX_DONE; 630 | } 631 | 632 | // add the token to all the URLs in the response 633 | ctx = ngx_pcalloc(r->pool, sizeof(*ctx)); 634 | if (ctx == NULL) 635 | { 636 | return NGX_ERROR; 637 | } 638 | 639 | if (processor->escape != NULL) 640 | { 641 | rc = processor->escape(r->pool, token, &ctx->token); 642 | if (rc != NGX_OK) 643 | { 644 | return rc; 645 | } 646 | } 647 | else 648 | { 649 | ctx->token = *token; 650 | } 651 | 652 | ctx->process = processor->process; 653 | ctx->processor_context_offset = processor->processor_context_offset; 654 | ctx->processor_params = processor->processor_params; 655 | 656 | ngx_http_set_ctx(r, ctx, ngx_http_secure_token_filter_module); 657 | 658 | ctx->last_out = &ctx->out; 659 | 660 | r->filter_need_in_memory = 1; 661 | 662 | ngx_http_clear_content_length(r); 663 | ngx_http_clear_accept_ranges(r); 664 | ngx_http_clear_etag(r); 665 | 666 | return NGX_OK; 667 | } 668 | 669 | void 670 | ngx_http_secure_token_install_body_filter() 671 | { 672 | ngx_http_next_body_filter = ngx_http_top_body_filter; 673 | ngx_http_top_body_filter = ngx_http_secure_token_body_filter; 674 | } 675 | -------------------------------------------------------------------------------- /ngx_http_secure_token_filter_module.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "ngx_http_secure_token_processor_base.h" 7 | #include "ngx_http_secure_token_filter_module.h" 8 | #include "ngx_http_secure_token_encrypt_uri.h" 9 | #include "ngx_http_secure_token_utils.h" 10 | #include "ngx_http_secure_token_conf.h" 11 | #include "ngx_http_secure_token_m3u8.h" 12 | #include "ngx_http_secure_token_xml.h" 13 | 14 | #include "akamai/ngx_http_secure_token_akamai.h" 15 | #include "broadpeak/ngx_http_secure_token_broadpeak.h" 16 | #include "cdnvideo/ngx_http_secure_token_cdnvideo.h" 17 | #include "chinacache/ngx_http_secure_token_chinacache.h" 18 | #include "cht/ngx_http_secure_token_cht.h" 19 | #include "cloudfront/ngx_http_secure_token_cloudfront.h" 20 | #include "iijpta/ngx_http_secure_token_iijpta.h" 21 | 22 | #define CACHE_CONTROL_FORMAT "%V, max-age=%T, max-stale=0" 23 | 24 | static void *ngx_http_secure_token_create_loc_conf(ngx_conf_t *cf); 25 | static char *ngx_http_secure_token_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); 26 | static ngx_int_t ngx_http_secure_token_add_variables(ngx_conf_t *cf); 27 | static ngx_int_t ngx_http_secure_token_filter_init(ngx_conf_t *cf); 28 | static ngx_int_t ngx_http_secure_token_set_baseuri_comma(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); 29 | 30 | static ngx_str_t ngx_http_secure_token_default_types[] = { 31 | ngx_null_string 32 | }; 33 | 34 | static ngx_str_t ngx_http_baseuri = ngx_string("secure_token_baseuri"); 35 | static ngx_str_t ngx_http_baseuri_noslash = ngx_string("secure_token_baseuri_noslash"); 36 | static ngx_str_t ngx_http_baseuri_comma = ngx_string("secure_token_baseuri_comma"); 37 | 38 | static ngx_conf_num_bounds_t ngx_http_secure_token_encrypt_uri_key_bounds = { 39 | ngx_conf_check_str_len_bounds, 32, 32 40 | }; 41 | 42 | static ngx_conf_num_bounds_t ngx_http_secure_token_encrypt_uri_iv_bounds = { 43 | ngx_conf_check_str_len_bounds, 16, 16 44 | }; 45 | 46 | static ngx_conf_num_bounds_t ngx_http_secure_token_encrypt_uri_hash_size_bounds = { 47 | ngx_conf_check_num_bounds, 0, 16 48 | }; 49 | 50 | static ngx_command_t ngx_http_secure_token_commands[] = { 51 | { ngx_string("secure_token"), 52 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 53 | ngx_http_set_complex_value_slot, 54 | NGX_HTTP_LOC_CONF_OFFSET, 55 | offsetof(ngx_http_secure_token_loc_conf_t, token), 56 | NULL }, 57 | 58 | { ngx_string("secure_token_avoid_cookies"), 59 | NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_FLAG, 60 | ngx_conf_set_flag_slot, 61 | NGX_HTTP_LOC_CONF_OFFSET, 62 | offsetof(ngx_http_secure_token_loc_conf_t, avoid_cookies), 63 | NULL }, 64 | 65 | { ngx_string("secure_token_tokenize_segments"), 66 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, 67 | ngx_conf_set_flag_slot, 68 | NGX_HTTP_LOC_CONF_OFFSET, 69 | offsetof(ngx_http_secure_token_loc_conf_t, processor_conf.tokenize_segments), 70 | NULL }, 71 | 72 | { ngx_string("secure_token_types"), 73 | NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_1MORE, 74 | ngx_http_types_slot, 75 | NGX_HTTP_LOC_CONF_OFFSET, 76 | offsetof(ngx_http_secure_token_loc_conf_t, types_keys), 77 | NULL }, 78 | 79 | { ngx_string("secure_token_uri_filename_prefix"), 80 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 81 | ngx_conf_set_str_array_slot, 82 | NGX_HTTP_LOC_CONF_OFFSET, 83 | offsetof(ngx_http_secure_token_loc_conf_t, filename_prefixes), 84 | NULL }, 85 | 86 | { ngx_string("secure_token_expires_time"), 87 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 88 | ngx_conf_set_sec_slot, 89 | NGX_HTTP_LOC_CONF_OFFSET, 90 | offsetof(ngx_http_secure_token_loc_conf_t, expires_time), 91 | NULL }, 92 | 93 | { ngx_string("secure_token_cookie_token_expires_time"), 94 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 95 | ngx_conf_set_sec_slot, 96 | NGX_HTTP_LOC_CONF_OFFSET, 97 | offsetof(ngx_http_secure_token_loc_conf_t, cookie_token_expires_time), 98 | NULL }, 99 | 100 | { ngx_string("secure_token_query_token_expires_time"), 101 | NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, 102 | ngx_conf_set_sec_slot, 103 | NGX_HTTP_LOC_CONF_OFFSET, 104 | offsetof(ngx_http_secure_token_loc_conf_t, query_token_expires_time), 105 | NULL }, 106 | 107 | { ngx_string("secure_token_cache_scope"), 108 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 109 | ngx_conf_set_str_slot, 110 | NGX_HTTP_LOC_CONF_OFFSET, 111 | offsetof(ngx_http_secure_token_loc_conf_t, cache_scope), 112 | NULL }, 113 | 114 | { ngx_string("secure_token_token_cache_scope"), 115 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 116 | ngx_conf_set_str_slot, 117 | NGX_HTTP_LOC_CONF_OFFSET, 118 | offsetof(ngx_http_secure_token_loc_conf_t, token_cache_scope), 119 | NULL }, 120 | 121 | { ngx_string("secure_token_last_modified"), 122 | NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, 123 | ngx_conf_set_str_slot, 124 | NGX_HTTP_LOC_CONF_OFFSET, 125 | offsetof(ngx_http_secure_token_loc_conf_t, last_modified), 126 | NULL }, 127 | 128 | { ngx_string("secure_token_token_last_modified"), 129 | NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, 130 | ngx_conf_set_str_slot, 131 | NGX_HTTP_LOC_CONF_OFFSET, 132 | offsetof(ngx_http_secure_token_loc_conf_t, token_last_modified), 133 | NULL }, 134 | 135 | { ngx_string("secure_token_content_type_m3u8"), 136 | NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, 137 | ngx_conf_set_str_slot, 138 | NGX_HTTP_LOC_CONF_OFFSET, 139 | offsetof(ngx_http_secure_token_loc_conf_t, content_type_m3u8), 140 | NULL }, 141 | 142 | { ngx_string("secure_token_content_type_mpd"), 143 | NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, 144 | ngx_conf_set_str_slot, 145 | NGX_HTTP_LOC_CONF_OFFSET, 146 | offsetof(ngx_http_secure_token_loc_conf_t, content_type_mpd), 147 | NULL }, 148 | 149 | { ngx_string("secure_token_content_type_f4m"), 150 | NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, 151 | ngx_conf_set_str_slot, 152 | NGX_HTTP_LOC_CONF_OFFSET, 153 | offsetof(ngx_http_secure_token_loc_conf_t, content_type_f4m), 154 | NULL }, 155 | 156 | #include "akamai/ngx_http_secure_token_akamai_commands.h" 157 | #include "broadpeak/ngx_http_secure_token_broadpeak_commands.h" 158 | #include "cdnvideo/ngx_http_secure_token_cdnvideo_commands.h" 159 | #include "chinacache/ngx_http_secure_token_chinacache_commands.h" 160 | #include "cht/ngx_http_secure_token_cht_commands.h" 161 | #include "cloudfront/ngx_http_secure_token_cloudfront_commands.h" 162 | #include "iijpta/ngx_http_secure_token_iijpta_commands.h" 163 | 164 | { ngx_string("secure_token_encrypt_uri"), 165 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, 166 | ngx_conf_set_flag_slot, 167 | NGX_HTTP_LOC_CONF_OFFSET, 168 | offsetof(ngx_http_secure_token_loc_conf_t, processor_conf.encrypt_uri), 169 | NULL }, 170 | 171 | { ngx_string("secure_token_encrypt_uri_key"), 172 | NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, 173 | ngx_http_secure_token_conf_set_hex_str_slot, 174 | NGX_HTTP_LOC_CONF_OFFSET, 175 | offsetof(ngx_http_secure_token_loc_conf_t, encrypt_uri_key), 176 | &ngx_http_secure_token_encrypt_uri_key_bounds }, 177 | 178 | { ngx_string("secure_token_encrypt_uri_iv"), 179 | NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, 180 | ngx_http_secure_token_conf_set_hex_str_slot, 181 | NGX_HTTP_LOC_CONF_OFFSET, 182 | offsetof(ngx_http_secure_token_loc_conf_t, encrypt_uri_iv), 183 | &ngx_http_secure_token_encrypt_uri_iv_bounds }, 184 | 185 | { ngx_string("secure_token_encrypt_uri_part"), 186 | NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, 187 | ngx_http_set_complex_value_slot, 188 | NGX_HTTP_LOC_CONF_OFFSET, 189 | offsetof(ngx_http_secure_token_loc_conf_t, encrypt_uri_part), 190 | NULL }, 191 | 192 | { ngx_string("secure_token_encrypt_uri_hash_size"), 193 | NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, 194 | ngx_conf_set_size_slot, 195 | NGX_HTTP_LOC_CONF_OFFSET, 196 | offsetof(ngx_http_secure_token_loc_conf_t, encrypt_uri_hash_size), 197 | &ngx_http_secure_token_encrypt_uri_hash_size_bounds }, 198 | 199 | ngx_null_command 200 | }; 201 | 202 | static ngx_http_module_t ngx_http_secure_token_filter_module_ctx = { 203 | ngx_http_secure_token_add_variables, /* preconfiguration */ 204 | ngx_http_secure_token_filter_init, /* postconfiguration */ 205 | 206 | NULL, /* create main configuration */ 207 | NULL, /* init main configuration */ 208 | 209 | NULL, /* create server configuration */ 210 | NULL, /* merge server configuration */ 211 | 212 | ngx_http_secure_token_create_loc_conf, /* create location configuration */ 213 | ngx_http_secure_token_merge_loc_conf /* merge location configuration */ 214 | }; 215 | 216 | ngx_module_t ngx_http_secure_token_filter_module = { 217 | NGX_MODULE_V1, 218 | &ngx_http_secure_token_filter_module_ctx, /* module context */ 219 | ngx_http_secure_token_commands, /* module directives */ 220 | NGX_HTTP_MODULE, /* module type */ 221 | NULL, /* init master */ 222 | NULL, /* init module */ 223 | NULL, /* init process */ 224 | NULL, /* init thread */ 225 | NULL, /* exit thread */ 226 | NULL, /* exit process */ 227 | NULL, /* exit master */ 228 | NGX_MODULE_V1_PADDING 229 | }; 230 | 231 | static ngx_http_output_header_filter_pt ngx_http_next_header_filter; 232 | 233 | static void * 234 | ngx_http_secure_token_create_loc_conf(ngx_conf_t *cf) 235 | { 236 | ngx_http_secure_token_loc_conf_t *conf; 237 | 238 | conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_secure_token_loc_conf_t)); 239 | if (conf == NULL) { 240 | return NGX_CONF_ERROR; 241 | } 242 | conf->avoid_cookies = NGX_CONF_UNSET; 243 | conf->filename_prefixes = NGX_CONF_UNSET_PTR; 244 | conf->expires_time = NGX_CONF_UNSET; 245 | conf->cookie_token_expires_time = NGX_CONF_UNSET; 246 | conf->query_token_expires_time = NGX_CONF_UNSET; 247 | conf->processor_conf.tokenize_segments = NGX_CONF_UNSET; 248 | conf->processor_conf.encrypt_uri = NGX_CONF_UNSET; 249 | conf->encrypt_uri_hash_size = NGX_CONF_UNSET_SIZE; 250 | 251 | return conf; 252 | } 253 | 254 | static char * 255 | ngx_http_secure_token_init_time(ngx_str_t* str, time_t* time) 256 | { 257 | // now => 0 258 | // empty => NGX_CONF_UNSET 259 | // other => unix timestamp 260 | 261 | if (str->len == sizeof("now") - 1 && 262 | ngx_strncasecmp(str->data, (u_char *)"now", sizeof("now") - 1) == 0) 263 | { 264 | *time = 0; 265 | } 266 | else if (str->len > 0) 267 | { 268 | *time = ngx_http_parse_time(str->data, str->len); 269 | if (*time == NGX_ERROR) 270 | { 271 | return NGX_CONF_ERROR; 272 | } 273 | } 274 | else 275 | { 276 | *time = NGX_CONF_UNSET; 277 | } 278 | 279 | return NGX_CONF_OK; 280 | } 281 | 282 | static char * 283 | ngx_http_secure_token_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) 284 | { 285 | ngx_http_secure_token_loc_conf_t *prev = parent; 286 | ngx_http_secure_token_loc_conf_t *conf = child; 287 | char* err; 288 | 289 | if (conf->token == NULL) 290 | { 291 | conf->token = prev->token; 292 | } 293 | ngx_conf_merge_value(conf->avoid_cookies, prev->avoid_cookies, 1); 294 | 295 | ngx_conf_merge_ptr_value(conf->filename_prefixes, prev->filename_prefixes, NULL); 296 | 297 | ngx_conf_merge_sec_value(conf->expires_time, prev->expires_time, NGX_CONF_UNSET); 298 | ngx_conf_merge_sec_value(conf->cookie_token_expires_time, prev->cookie_token_expires_time, NGX_CONF_UNSET); 299 | ngx_conf_merge_sec_value(conf->query_token_expires_time, prev->query_token_expires_time, NGX_CONF_UNSET); 300 | ngx_conf_merge_str_value(conf->cache_scope, prev->cache_scope, "public"); 301 | ngx_conf_merge_str_value(conf->token_cache_scope, prev->token_cache_scope, "private"); 302 | ngx_conf_merge_str_value(conf->last_modified, prev->last_modified, "Sun, 19 Nov 2000 08:52:00 GMT"); 303 | ngx_conf_merge_str_value(conf->token_last_modified, prev->token_last_modified, "now"); 304 | 305 | ngx_conf_merge_str_value(conf->content_type_m3u8, prev->content_type_m3u8, "application/vnd.apple.mpegurl"); 306 | ngx_conf_merge_str_value(conf->content_type_mpd, prev->content_type_mpd, "application/dash+xml"); 307 | ngx_conf_merge_str_value(conf->content_type_f4m, prev->content_type_f4m, "video/f4m"); 308 | 309 | ngx_conf_merge_value(conf->processor_conf.tokenize_segments, prev->processor_conf.tokenize_segments, 1); 310 | 311 | if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types, 312 | &prev->types_keys, &prev->types, 313 | ngx_http_secure_token_default_types) 314 | != NGX_OK) 315 | { 316 | return NGX_CONF_ERROR; 317 | } 318 | 319 | if (ngx_http_secure_token_init_processors_hash(cf, conf) != NGX_OK) 320 | { 321 | return NGX_CONF_ERROR; 322 | } 323 | 324 | err = ngx_http_secure_token_init_time(&conf->last_modified, &conf->last_modified_time); 325 | if (err != NGX_CONF_OK) 326 | { 327 | return err; 328 | } 329 | 330 | err = ngx_http_secure_token_init_time(&conf->token_last_modified, &conf->token_last_modified_time); 331 | if (err != NGX_CONF_OK) 332 | { 333 | return err; 334 | } 335 | 336 | ngx_conf_merge_value(conf->processor_conf.encrypt_uri, prev->processor_conf.encrypt_uri, 0); 337 | ngx_conf_merge_str_value(conf->encrypt_uri_key, prev->encrypt_uri_key, ""); 338 | ngx_conf_merge_str_value(conf->encrypt_uri_iv, prev->encrypt_uri_iv, ""); 339 | if (conf->encrypt_uri_part == NULL) 340 | { 341 | conf->encrypt_uri_part = prev->encrypt_uri_part; 342 | } 343 | ngx_conf_merge_size_value(conf->encrypt_uri_hash_size, prev->encrypt_uri_hash_size, 8); 344 | 345 | if (conf->processor_conf.encrypt_uri) 346 | { 347 | if (!conf->encrypt_uri_key.len) 348 | { 349 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 350 | "\"secure_token_encrypt_uri_key\" is mandatory when encrypt uri is enabled"); 351 | return NGX_CONF_ERROR; 352 | } 353 | 354 | if (!conf->encrypt_uri_iv.len) 355 | { 356 | ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, 357 | "\"secure_token_encrypt_uri_iv\" is mandatory when encrypt uri is enabled"); 358 | return NGX_CONF_ERROR; 359 | } 360 | } 361 | 362 | return NGX_CONF_OK; 363 | } 364 | 365 | #if (nginx_version >= 1023000) 366 | static ngx_table_elt_t * 367 | ngx_http_secure_token_push_cache_control(ngx_http_request_t *r) 368 | { 369 | ngx_table_elt_t *cc; 370 | 371 | cc = r->headers_out.cache_control; 372 | 373 | if (cc == NULL) { 374 | 375 | cc = ngx_list_push(&r->headers_out.headers); 376 | if (cc == NULL) { 377 | return NULL; 378 | } 379 | 380 | r->headers_out.cache_control = cc; 381 | cc->next = NULL; 382 | 383 | cc->hash = 1; 384 | ngx_str_set(&cc->key, "Cache-Control"); 385 | 386 | } else { 387 | for (cc = cc->next; cc; cc = cc->next) { 388 | cc->hash = 0; 389 | } 390 | 391 | cc = r->headers_out.cache_control; 392 | cc->next = NULL; 393 | } 394 | 395 | return cc; 396 | } 397 | #else 398 | static ngx_table_elt_t * 399 | ngx_http_secure_token_push_cache_control(ngx_http_request_t *r) 400 | { 401 | ngx_uint_t i; 402 | ngx_table_elt_t *cc, **ccp; 403 | 404 | ccp = r->headers_out.cache_control.elts; 405 | 406 | if (ccp == NULL) { 407 | 408 | if (ngx_array_init(&r->headers_out.cache_control, r->pool, 409 | 1, sizeof(ngx_table_elt_t *)) 410 | != NGX_OK) 411 | { 412 | return NULL; 413 | } 414 | 415 | ccp = ngx_array_push(&r->headers_out.cache_control); 416 | if (ccp == NULL) { 417 | return NULL; 418 | } 419 | 420 | cc = ngx_list_push(&r->headers_out.headers); 421 | if (cc == NULL) { 422 | return NULL; 423 | } 424 | 425 | cc->hash = 1; 426 | ngx_str_set(&cc->key, "Cache-Control"); 427 | *ccp = cc; 428 | 429 | } else { 430 | for (i = 1; i < r->headers_out.cache_control.nelts; i++) { 431 | ccp[i]->hash = 0; 432 | } 433 | 434 | cc = ccp[0]; 435 | } 436 | 437 | return cc; 438 | } 439 | #endif 440 | 441 | // a run down version of ngx_http_set_expires with a few changes 442 | // (can't use the existing code since the function is static) 443 | static ngx_int_t 444 | ngx_http_secure_token_set_expires(ngx_http_request_t *r, time_t expires_time, ngx_str_t* cache_scope) 445 | { 446 | size_t len; 447 | time_t max_age; 448 | ngx_table_elt_t *e, *cc; 449 | 450 | e = r->headers_out.expires; 451 | 452 | if (e == NULL) { 453 | 454 | e = ngx_list_push(&r->headers_out.headers); 455 | if (e == NULL) { 456 | return NGX_ERROR; 457 | } 458 | 459 | r->headers_out.expires = e; 460 | #if (nginx_version >= 1023000) 461 | e->next = NULL; 462 | #endif 463 | 464 | e->hash = 1; 465 | ngx_str_set(&e->key, "Expires"); 466 | } 467 | 468 | cc = ngx_http_secure_token_push_cache_control(r); 469 | if (cc == NULL) { 470 | e->hash = 0; 471 | return NGX_ERROR; 472 | } 473 | 474 | if (expires_time == 0) { 475 | ngx_str_set(&e->value, "Sun, 19 Nov 2000 08:52:00 GMT"); 476 | ngx_str_set(&cc->value, "no-store, no-cache, must-revalidate, post-check=0, pre-check=0"); 477 | return NGX_OK; 478 | } 479 | 480 | len = sizeof("Mon, 28 Sep 1970 06:00:00 GMT"); 481 | e->value.len = len - 1; 482 | 483 | e->value.data = ngx_pnalloc(r->pool, len); 484 | if (e->value.data == NULL) { 485 | e->hash = 0; 486 | cc->hash = 0; 487 | return NGX_ERROR; 488 | } 489 | 490 | max_age = expires_time; 491 | expires_time += ngx_time(); 492 | 493 | ngx_http_time(e->value.data, expires_time); 494 | 495 | cc->value.data = ngx_pnalloc(r->pool, 496 | sizeof(CACHE_CONTROL_FORMAT) + cache_scope->len + NGX_TIME_T_LEN + 1); 497 | if (cc->value.data == NULL) { 498 | cc->hash = 0; 499 | return NGX_ERROR; 500 | } 501 | 502 | cc->value.len = ngx_sprintf(cc->value.data, CACHE_CONTROL_FORMAT, cache_scope, max_age) 503 | - cc->value.data; 504 | 505 | return NGX_OK; 506 | } 507 | 508 | static ngx_int_t 509 | ngx_http_secure_token_call_next_filter( 510 | ngx_http_request_t *r, 511 | time_t expires, 512 | ngx_str_t* cache_scope, 513 | time_t last_modified, 514 | ngx_str_t* last_modified_str) 515 | { 516 | ngx_table_elt_t *h; 517 | ngx_int_t rc; 518 | 519 | if (expires != NGX_CONF_UNSET) 520 | { 521 | rc = ngx_http_secure_token_set_expires(r, expires, cache_scope); 522 | if (rc != NGX_OK) 523 | { 524 | return rc; 525 | } 526 | } 527 | 528 | if (last_modified == 0) 529 | { 530 | if (r->headers_out.last_modified != NULL) 531 | { 532 | r->headers_out.last_modified->hash = 0; 533 | r->headers_out.last_modified = NULL; 534 | } 535 | r->headers_out.last_modified_time = ngx_time(); 536 | } 537 | else if (last_modified != NGX_CONF_UNSET) 538 | { 539 | if (r->headers_out.last_modified) 540 | { 541 | h = r->headers_out.last_modified; 542 | } 543 | else 544 | { 545 | h = ngx_list_push(&r->headers_out.headers); 546 | if (h == NULL) 547 | { 548 | return NGX_ERROR; 549 | } 550 | 551 | r->headers_out.last_modified = h; 552 | } 553 | h->hash = 1; 554 | #if (nginx_version >= 1023000) 555 | h->next = NULL; 556 | #endif 557 | h->key.data = (u_char*)"Last-Modified"; 558 | h->key.len = sizeof("Last-Modified") - 1; 559 | h->value = *last_modified_str; 560 | 561 | r->headers_out.last_modified_time = last_modified; 562 | } 563 | 564 | return ngx_http_next_header_filter(r); 565 | } 566 | 567 | ngx_int_t 568 | ngx_http_secure_token_get_acl(ngx_http_request_t *r, ngx_http_complex_value_t *acl_conf, ngx_str_t* acl) 569 | { 570 | ngx_http_variable_value_t var_value; 571 | 572 | // get the acl 573 | if (acl_conf != NULL) 574 | { 575 | if (ngx_http_complex_value(r, acl_conf, acl) != NGX_OK) 576 | { 577 | return NGX_ERROR; 578 | } 579 | } 580 | else 581 | { 582 | // the default is 'baseuri_comma' 583 | if (ngx_http_secure_token_set_baseuri_comma(r, &var_value, 0) != NGX_OK) 584 | { 585 | return NGX_ERROR; 586 | } 587 | 588 | acl->data = var_value.data; 589 | acl->len = var_value.len; 590 | } 591 | 592 | return NGX_OK; 593 | } 594 | 595 | static void * 596 | ngx_http_secure_token_memrchr(const u_char *s, int c, size_t n) 597 | { 598 | const u_char *cp; 599 | 600 | for (cp = s + n; cp > s;) 601 | { 602 | if (*(--cp) == (u_char)c) 603 | return (void*)cp; 604 | } 605 | return NULL; 606 | } 607 | 608 | static ngx_int_t 609 | ngx_http_secure_token_header_filter(ngx_http_request_t *r) 610 | { 611 | ngx_http_secure_token_loc_conf_t *conf; 612 | ngx_table_elt_t *set_cookie; 613 | ngx_flag_t body_filter_inited; 614 | ngx_flag_t prefix_matched; 615 | ngx_uint_t i; 616 | ngx_str_t* cur_prefix; 617 | ngx_str_t token; 618 | ngx_str_t uri_filename; 619 | ngx_int_t rc; 620 | u_char* last_slash_pos; 621 | 622 | conf = ngx_http_get_module_loc_conf(r, ngx_http_secure_token_filter_module); 623 | 624 | // decide whether the token should be added 625 | if ((conf->token == NULL && !conf->processor_conf.encrypt_uri) || 626 | r->headers_out.status != NGX_HTTP_OK || 627 | r != r->main) 628 | { 629 | return ngx_http_next_header_filter(r); 630 | } 631 | 632 | if (ngx_http_test_content_type(r, &conf->types) == NULL) 633 | { 634 | return ngx_http_secure_token_call_next_filter( 635 | r, 636 | conf->expires_time, 637 | &conf->cache_scope, 638 | conf->last_modified_time, 639 | &conf->last_modified); 640 | } 641 | 642 | // check the file name 643 | last_slash_pos = ngx_http_secure_token_memrchr(r->uri.data, '/', r->uri.len); 644 | if (last_slash_pos == NULL) 645 | { 646 | return NGX_ERROR; 647 | } 648 | 649 | if (conf->filename_prefixes != NULL) 650 | { 651 | uri_filename.data = last_slash_pos + 1; 652 | uri_filename.len = r->uri.data + r->uri.len - uri_filename.data; 653 | 654 | prefix_matched = 0; 655 | for (i = 0; i < conf->filename_prefixes->nelts; i++) 656 | { 657 | cur_prefix = &((ngx_str_t*)conf->filename_prefixes->elts)[i]; 658 | if (uri_filename.len >= cur_prefix->len && 659 | ngx_memcmp(uri_filename.data, cur_prefix->data, cur_prefix->len) == 0) 660 | { 661 | prefix_matched = 1; 662 | break; 663 | } 664 | } 665 | 666 | if (!prefix_matched) 667 | { 668 | return ngx_http_secure_token_call_next_filter( 669 | r, 670 | conf->expires_time, 671 | &conf->cache_scope, 672 | conf->last_modified_time, 673 | &conf->last_modified); 674 | } 675 | } 676 | 677 | // build the token 678 | token.len = 0; 679 | token.data = NULL; 680 | 681 | if (conf->token != NULL) 682 | { 683 | if (ngx_http_complex_value( 684 | r, 685 | conf->token, 686 | &token) != NGX_OK) 687 | { 688 | return NGX_ERROR; 689 | } 690 | } 691 | 692 | // init the body filter if needed 693 | body_filter_inited = 0; 694 | if (conf->avoid_cookies || conf->processor_conf.encrypt_uri) 695 | { 696 | // Note: this function returns NGX_DONE when a matching body processor is not found 697 | rc = ngx_http_secure_token_init_body_filter(r, &token); 698 | if (rc == NGX_OK) 699 | { 700 | body_filter_inited = 1; 701 | } 702 | else if (rc != NGX_DONE) 703 | { 704 | return rc; 705 | } 706 | } 707 | 708 | // if no token, we are done 709 | if (token.len == 0) 710 | { 711 | return ngx_http_secure_token_call_next_filter( 712 | r, 713 | conf->expires_time, 714 | &conf->cache_scope, 715 | conf->last_modified_time, 716 | &conf->last_modified); 717 | } 718 | 719 | // if the token will be added to the body we are done 720 | if (body_filter_inited) 721 | { 722 | return ngx_http_secure_token_call_next_filter( 723 | r, 724 | conf->query_token_expires_time, 725 | &conf->token_cache_scope, 726 | conf->token_last_modified_time, 727 | &conf->token_last_modified); 728 | } 729 | 730 | // add a cookie token 731 | set_cookie = ngx_list_push(&r->headers_out.headers); 732 | if (set_cookie == NULL) 733 | { 734 | return NGX_ERROR; 735 | } 736 | 737 | set_cookie->hash = 1; 738 | #if (nginx_version >= 1023000) 739 | set_cookie->next = NULL; 740 | #endif 741 | ngx_str_set(&set_cookie->key, "Set-Cookie"); 742 | set_cookie->value = token; 743 | 744 | return ngx_http_secure_token_call_next_filter( 745 | r, 746 | conf->cookie_token_expires_time, 747 | &conf->token_cache_scope, 748 | conf->token_last_modified_time, 749 | &conf->token_last_modified); 750 | } 751 | 752 | static ngx_int_t 753 | ngx_http_secure_token_filter_init(ngx_conf_t *cf) 754 | { 755 | ngx_http_handler_pt *h; 756 | ngx_http_core_main_conf_t *cmcf; 757 | 758 | // header filter 759 | ngx_http_next_header_filter = ngx_http_top_header_filter; 760 | ngx_http_top_header_filter = ngx_http_secure_token_header_filter; 761 | 762 | // body filter 763 | ngx_http_secure_token_install_body_filter(); 764 | 765 | // access handler 766 | cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); 767 | 768 | h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers); 769 | if (h == NULL) 770 | { 771 | return NGX_ERROR; 772 | } 773 | 774 | *h = ngx_http_secure_token_decrypt_uri; 775 | 776 | return NGX_OK; 777 | } 778 | 779 | static ngx_int_t 780 | ngx_http_secure_token_set_baseuri(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) 781 | { 782 | u_char* last_slash_pos; 783 | 784 | last_slash_pos = ngx_http_secure_token_memrchr(r->uri.data, '/', r->uri.len); 785 | if (last_slash_pos == NULL) 786 | { 787 | return NGX_ERROR; 788 | } 789 | 790 | v->valid = 1; 791 | v->no_cacheable = 0; 792 | v->not_found = 0; 793 | 794 | v->len = last_slash_pos + 1 - r->uri.data; 795 | v->data = r->uri.data; 796 | 797 | return NGX_OK; 798 | } 799 | 800 | static ngx_int_t 801 | ngx_http_secure_token_set_baseuri_noslash(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) 802 | { 803 | u_char* last_slash_pos; 804 | 805 | last_slash_pos = ngx_http_secure_token_memrchr(r->uri.data, '/', r->uri.len); 806 | if (last_slash_pos == NULL) 807 | { 808 | return NGX_ERROR; 809 | } 810 | 811 | v->valid = 1; 812 | v->no_cacheable = 0; 813 | v->not_found = 0; 814 | 815 | v->len = last_slash_pos - r->uri.data; 816 | v->data = r->uri.data; 817 | 818 | return NGX_OK; 819 | } 820 | 821 | static ngx_int_t 822 | ngx_http_secure_token_set_baseuri_comma(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) 823 | { 824 | u_char* last_slash_pos; 825 | u_char* acl_end_pos; 826 | u_char* comma_pos; 827 | 828 | last_slash_pos = ngx_http_secure_token_memrchr(r->uri.data, '/', r->uri.len); 829 | if (last_slash_pos == NULL) 830 | { 831 | return NGX_ERROR; 832 | } 833 | 834 | acl_end_pos = last_slash_pos + 1; 835 | 836 | comma_pos = memchr(r->uri.data, ',', r->uri.len); 837 | if (comma_pos != NULL) 838 | { 839 | acl_end_pos = ngx_min(acl_end_pos, comma_pos); 840 | } 841 | 842 | v->valid = 1; 843 | v->no_cacheable = 0; 844 | v->not_found = 0; 845 | 846 | v->len = acl_end_pos - r->uri.data; 847 | v->data = r->uri.data; 848 | 849 | return NGX_OK; 850 | } 851 | 852 | static ngx_int_t 853 | ngx_http_secure_token_add_variables(ngx_conf_t *cf) 854 | { 855 | ngx_http_variable_t *var; 856 | 857 | var = ngx_http_add_variable(cf, &ngx_http_baseuri, NGX_HTTP_VAR_CHANGEABLE); 858 | if (var == NULL) { 859 | return NGX_ERROR; 860 | } 861 | 862 | var->get_handler = ngx_http_secure_token_set_baseuri; 863 | 864 | var = ngx_http_add_variable(cf, &ngx_http_baseuri_noslash, NGX_HTTP_VAR_CHANGEABLE); 865 | if (var == NULL) { 866 | return NGX_ERROR; 867 | } 868 | 869 | var->get_handler = ngx_http_secure_token_set_baseuri_noslash; 870 | 871 | var = ngx_http_add_variable(cf, &ngx_http_baseuri_comma, NGX_HTTP_VAR_CHANGEABLE); 872 | if (var == NULL) { 873 | return NGX_ERROR; 874 | } 875 | 876 | var->get_handler = ngx_http_secure_token_set_baseuri_comma; 877 | 878 | if (ngx_http_secure_token_encrypt_uri_add_variables(cf) != NGX_OK) { 879 | return NGX_ERROR; 880 | } 881 | 882 | return NGX_OK; 883 | } 884 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published by 637 | the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | --------------------------------------------------------------------------------