/*
* This Block of code added to deal with paths that are not on the root -
* that is, via proxy_pass that are being redirected and the base part of
* the proxy url needs to be taken off the beginning of the URI in order to sign it correctly.
*/
static void chop_leading_prefix( ngx_http_request_t *r, ngx_http_aws_auth_conf_t *aws_conf, u_char **uri )
{
if( ngx_strcmp( aws_conf->chop_prefix.data, "" ) ) {
if( !ngx_strncmp( r->uri.data, aws_conf->chop_prefix.data, aws_conf->chop_prefix.len ) ) {
*uri += aws_conf->chop_prefix.len;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "chop_prefix '%V' chopped from URI", &aws_conf->chop_prefix);
} else {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "chop_prefix '%V' NOT in URI", &aws_conf->chop_prefix);
}
}
}
/* hmac signature for the string provided appended to the end of the string */
static void hmac_sign_string( ngx_http_aws_auth_conf_t *aws_conf, u_char *str_to_sign )
{
unsigned int md_len;
unsigned char md[EVP_MAX_MD_SIZE];
if (evp_md==NULL)
{
evp_md = EVP_sha1();
}
HMAC( evp_md, aws_conf->secret.data, aws_conf->secret.len, str_to_sign, ngx_strlen(str_to_sign), md, &md_len );
BIO* b64 = BIO_new( BIO_f_base64() );
BIO* bmem = BIO_new( BIO_s_mem() );
b64 = BIO_push( b64, bmem );
BIO_write( b64, md, md_len );
(void) BIO_flush( b64 );
BUF_MEM *bptr;
BIO_get_mem_ptr( b64, &bptr );
ngx_memcpy( str_to_sign, bptr->data, bptr->length-1 );
str_to_sign[bptr->length-1] = '\0';
BIO_free_all(b64);
}
/* calculate the signature using the non escaped URI, compatible with cumulus boto,.
returns 401 if incompatible signature or expires > now
*/
static ngx_int_t ngx_http_aws_auth_variable_s3(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data)
{
ngx_http_aws_auth_conf_t *aws_conf;
aws_conf = ngx_http_get_module_loc_conf(r, ngx_http_aws_auth_module);
/* Extract parameters from the GET request
http://trac.nginx.org/nginx/browser/nginx/src/core/ngx_string.h
http://trac.nginx.org/nginx/browser/nginx/src/http/ngx_http_parse.c
*/
ngx_str_t awsid = ngx_string( "AWSAccessKeyId" );
ngx_str_t myarg_awsid;
ngx_int_t awsid_exists = ngx_http_arg( r, awsid.data, awsid.len, &myarg_awsid);
if( awsid_exists == NGX_OK ) {
ngx_log_debug1( NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "AWSAccessKeyId FOUND: %V", &myarg_awsid );
}
/*TODO: check if myarg_awsid matches aws_conf aws_access_key */
/* http://trac.nginx.org/nginx/browser/nginx/src/http/ngx_http_request.h
http://trac.nginx.org/nginx/browser/nginx/src/http/ngx_http_variables.h
http://trac.nginx.org/nginx/browser/nginx/src/http/ngx_http_variables.c
*/
ngx_str_t expires = ngx_string( "Expires" );
ngx_str_t myarg_expires;
ngx_int_t expires_exists = ngx_http_arg( r, expires.data, expires.len, &myarg_expires);
if( expires_exists == NGX_OK ) {
ngx_log_error( NGX_LOG_ERR, r->connection->log, 0, "Expires FOUND: %V", &myarg_expires );
}
ngx_str_t original_signature = ngx_string( "Signature" );
ngx_str_t myarg_original_signature;
ngx_int_t original_signature_exists = ngx_http_arg( r, original_signature.data, original_signature.len, &myarg_original_signature);
if( original_signature_exists == NGX_OK ) {
ngx_log_debug1( NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "Signature FOUND: %V", &myarg_original_signature );
}
/* get the unmodified uri up until a ? (truncating any query parameters) */
ngx_str_t unparsed_uri_only;
unparsed_uri_only.data = ngx_palloc(r->pool, r->unparsed_uri.len);
unparsed_uri_only.len = 0;
ngx_uint_t i;
for( i=0; i < r->unparsed_uri.len; i++ ) {
unparsed_uri_only.data[i] = r->unparsed_uri.data[i];
if( unparsed_uri_only.data[i] == '?' ) {
break;
}
unparsed_uri_only.len++;
}
unparsed_uri_only.data[i] = '\0';
ngx_log_debug1( NGX_LOG_DEBUG_HTTP, r->connection->log, 0,"UNPARSED URI: %V", &unparsed_uri_only );
u_char *uri = unparsed_uri_only.data;
u_char *str_to_sign = ngx_palloc(r->pool,r->uri.len + aws_conf->s3_bucket.len + 200);
/* if an authenticated url do not strip the bucket from the URI, instead include the Expires */
if( awsid_exists == NGX_OK && expires_exists == NGX_OK && original_signature_exists == NGX_OK ) {
ngx_sprintf( str_to_sign, "GET\n\n\n%V\n%s%Z", &myarg_expires, uri );
} else {
chop_leading_prefix( r, aws_conf, &uri );
ngx_sprintf(str_to_sign, "GET\n\n\n\nx-amz-date:%V\n/%V%s%Z", &ngx_cached_http_time, &aws_conf->s3_bucket,uri);
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "CANONICAL STRING TO SIGN: %s", str_to_sign);
/* string to sign becomes replaced by the signature */
hmac_sign_string( aws_conf, str_to_sign );
u_char *signature = ngx_palloc( r->pool, 100 + aws_conf->access_key.len );
ngx_sprintf( signature, "AWS %V:%s%Z", &aws_conf->access_key, str_to_sign );
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "Calculated Signature: %s", signature);
/* if authenticated URL signature matches recalculate the signature so cumulus allows the download */
if( awsid_exists == NGX_OK || expires_exists == NGX_OK || original_signature_exists == NGX_OK ) {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "COMPARING CALCULATED to SENT: %s", myarg_original_signature.data);
u_char *mytemp = ngx_palloc( r->pool, 120 );
/ u_char end_escaped = (u_char) ngx_escape_uri( mytemp, str_to_sign, trlen(str_to_sign) , NGX_ESCAPE_URI); end_escaped = '\0'; */ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "CALCULATED SIGNATURE: %s", signature);
/* compare calculated signature (minus the trailing equals sign) to the GET parameter signature */
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "CALCULATED Signature LEN: %d", ngx_strlen(str_to_sign));
ngx_uint_t k;
ngx_uint_t match = 1;
for( k=0; k < ngx_strlen(str_to_sign) -1 ; k++ ) {
if( str_to_sign[k] != myarg_original_signature.data[k] ) {
match = 0;
}
}
if( match == 1 ) {
chop_leading_prefix( r, aws_conf, &uri );
ngx_sprintf( str_to_sign, "GET\n\n\n\nx-amz-date:%V\n/%V%s%Z", &ngx_cached_http_time, &aws_conf->s3_bucket, uri );
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "RECALCULATED STRING TO SIGN: %s", str_to_sign);
/* string to sign becomes replaced by the signature */
hmac_sign_string( aws_conf, str_to_sign );
ngx_sprintf( signature, "AWS %V:%s%Z", &aws_conf->access_key, str_to_sign );
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "RECALCULATED Signature: %s", signature);
/* checking for expired earlier in the function causes worker process to crash
if( expires_exists == NGX_OK ) {
ngx_int_t expires_int = ngx_atoi( myarg_expires.data, myarg_expires.len );
if( expires_int < ngx_time() ) {
ngx_log_error( NGX_LOG_ERR, r->connection->log, 0, "Expiration is invalid");
ngx_http_finalize_request(r, NGX_HTTP_UNAUTHORIZED);
return NGX_HTTP_UNAUTHORIZED;
}
}
*/
} else {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "GET PARAMETER SIGNATURE: %s", myarg_original_signature.data);
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "CALCULATED SIGNATURE: %s", signature);
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "AUTHENTICATED URL GET PARAMETER SIGNATURE does not match Calculated Signature");
ngx_http_finalize_request(r, NGX_HTTP_UNAUTHORIZED);
return NGX_HTTP_UNAUTHORIZED;
}
}
v->len = ngx_strlen(signature);
v->data = signature;
v->valid = 1;
v->no_cacheable = 0;
v->not_found = 0;
return NGX_OK;
}