diff --git a/src/mod/applications/mod_http_cache/aws.c b/src/mod/applications/mod_http_cache/aws.c
index fbbbf00d98..9c49363e6f 100644
--- a/src/mod/applications/mod_http_cache/aws.c
+++ b/src/mod/applications/mod_http_cache/aws.c
@@ -22,6 +22,7 @@
  *
  * Contributor(s):
  * Chris Rienzo <chris.rienzo@grasshopper.com>
+ * Quoc-Bao Nguyen <baonq5@vng.com.vn>
  *
  * aws.c -- Some Amazon Web Services helper functions
  *
@@ -34,211 +35,378 @@
 #include <openssl/sha.h>
 #endif
 
-/* 160 bits / 8 bits per byte */
-#define SHA1_LENGTH 20
 
-/**
- * Create the string to sign for a AWS signature calculation
- * @param verb (PUT/GET)
- * @param bucket bucket object is stored in
- * @param object to access (filename.ext)
- * @param content_type optional content type
- * @param content_md5 optional content MD5 checksum
- * @param date header
- * @return the string_to_sign (must be freed)
- */
-static char *aws_s3_string_to_sign(const char *verb, const char *bucket, const char *object, const char *content_type, const char *content_md5, const char *date)
-{
-	/*
-	 * String to sign has the following format:
-	 *   <HTTP-VERB>\n<Content-MD5>\n<Content-Type>\n<Expires/Date>\n/bucket/object
-	 */
-	return switch_mprintf("%s\n%s\n%s\n%s\n/%s/%s",
-		verb, content_md5 ? content_md5 : "", content_type ? content_type : "",
-		date, bucket, object);
-}
 
-/**
- * Create the AWS S3 signature
- * @param signature buffer to store the signature
- * @param signature_length length of signature buffer
- * @param string_to_sign
- * @param aws_secret_access_key secret access key
- * @return the signature buffer or NULL if missing input
- */
-static char *aws_s3_signature(char *signature, int signature_length, const char *string_to_sign, const char *aws_secret_access_key)
-{
 #if defined(HAVE_OPENSSL)
-	unsigned int signature_raw_length = SHA1_LENGTH;
-	char signature_raw[SHA1_LENGTH];
-	signature_raw[0] = '\0';
-	if (!signature || signature_length <= 0) {
-		return NULL;
-	}
-	if (zstr(aws_secret_access_key)) {
-		return NULL;
-	}
-	if (!string_to_sign) {
-		string_to_sign = "";
-	}
-	HMAC(EVP_sha1(),
-		 aws_secret_access_key,
-		 strlen(aws_secret_access_key),
-		 (const unsigned char *)string_to_sign,
-		 strlen(string_to_sign),
-		 (unsigned char *)signature_raw,
-		 &signature_raw_length);
-
-	/* convert result to base64 */
-	switch_b64_encode((unsigned char *)signature_raw, signature_raw_length, (unsigned char *)signature, signature_length);
+#include <openssl/hmac.h>
+#include <openssl/sha.h>
 #endif
-	return signature;
+
+#if defined(HAVE_OPENSSL)
+/**
+ * Calculate HMAC-SHA256 hash of a message
+ * @param buffer buffer to store the HMAC-SHA256 version of message as byte array
+ * @param buffer_length length of buffer
+ * @param key buffer that store the key to run HMAC-SHA256
+ * @param key_length length of the key
+ * @param message message that will be hashed
+ * @return byte array, equals to buffer
+ */
+static char *hmac256(char* buffer, unsigned int buffer_length, const char* key, unsigned int key_length, const char* message)
+{
+	if (zstr(key) || zstr(message) || buffer_length < SHA256_DIGEST_LENGTH) {
+		return NULL;
+	}
+
+	HMAC(EVP_sha256(),
+		 key,
+		 (int)key_length,
+		 (unsigned char *)message,
+		 strlen(message),
+		 (unsigned char*)buffer,
+		 &buffer_length);
+
+	return (char*)buffer;
+}
+
+
+/**
+ * Calculate HMAC-SHA256 hash of a message
+ * @param buffer buffer to store the HMAC-SHA256 version of the message as hex string
+ * @param key buffer that store the key to run HMAC-SHA256
+ * @param key_length length of the key
+ * @param message message that will be hashed
+ * @return hex string that store the HMAC-SHA256 version of the message
+ */
+static char *hmac256_hex(char* buffer, const char* key, unsigned int key_length, const char* message)
+{
+	char hmac256_raw[SHA256_DIGEST_LENGTH] = { 0 };
+
+	if (hmac256(hmac256_raw, SHA256_DIGEST_LENGTH, key, key_length, message) == NULL) {
+		return NULL;
+	}
+
+	for (unsigned int i = 0; i < SHA256_DIGEST_LENGTH; i++)
+	{
+		snprintf(buffer + i*2, 3, "%02x", (unsigned char)hmac256_raw[i]);
+	}
+	buffer[SHA256_DIGEST_LENGTH * 2] = '\0';
+
+	return buffer;
+}
+
+
+/**
+ * Calculate SHA256 hash of a message
+ * @param buffer buffer to store the SHA256 version of the message as hex string
+ * @param string string to be hashed
+ * @return hex string that store the SHA256 version of the message
+ */
+static char *sha256_hex(char* buffer, const char* string)
+{
+	unsigned char sha256_raw[SHA256_DIGEST_LENGTH] = { 0 };
+
+	SHA256((unsigned char*)string, strlen(string), sha256_raw);
+
+	for (unsigned int i = 0; i < SHA256_DIGEST_LENGTH; i++)
+	{
+		snprintf(buffer + i*2, 3, "%02x", sha256_raw[i]);
+	}
+	buffer[SHA256_DIGEST_LENGTH * 2] = '\0';
+
+	return buffer;
+}
+
+
+/**
+ * Get current time_stamp. Example: 20190724T110316Z
+ * @param format format of the time in strftime format
+ * @param buffer buffer to store the result
+ * @param buffer_length length of buffer
+ * @return current time stamp
+ */
+static char *get_time(char* format, char* buffer, unsigned int buffer_length)
+{
+	switch_time_exp_t time;
+	switch_size_t size;
+
+	switch_time_exp_gmt(&time, switch_time_now());
+
+	switch_strftime(buffer, &size, buffer_length, format, &time);
+
+	return buffer;
+}
+
+
+/**
+ * Get signature key
+ * @param key_signing buffer to store signature key
+ * @param aws_s3_profile AWS profile
+ * @return key_signing
+ */
+static char* aws_s3_signature_key(char* key_signing, switch_aws_s3_profile* aws_s3_profile) {
+
+	char key_date[SHA256_DIGEST_LENGTH];
+	char key_region[SHA256_DIGEST_LENGTH];
+	char key_service[SHA256_DIGEST_LENGTH];
+	char* aws4_secret_access_key = switch_mprintf("AWS4%s", aws_s3_profile->access_key_secret);
+
+	hmac256(key_date, SHA256_DIGEST_LENGTH, aws4_secret_access_key, strlen(aws4_secret_access_key), aws_s3_profile->date_stamp);
+	hmac256(key_region, SHA256_DIGEST_LENGTH, key_date, SHA256_DIGEST_LENGTH, aws_s3_profile->region);
+	hmac256(key_service, SHA256_DIGEST_LENGTH, key_region, SHA256_DIGEST_LENGTH, "s3");
+	hmac256(key_signing, SHA256_DIGEST_LENGTH, key_service, SHA256_DIGEST_LENGTH, "aws4_request");
+
+	switch_safe_free(aws4_secret_access_key);
+
+	return key_signing;
 }
 
 /**
- * Create a pre-signed URL for AWS S3
- * @param verb (PUT/GET)
- * @param url address (virtual-host-style)
- * @param base_domain (optional - amazon aws assumed if not specified)
- * @param content_type optional content type
- * @param content_md5 optional content MD5 checksum
- * @param aws_access_key_id secret access key identifier
- * @param aws_secret_access_key secret access key
- * @param expires seconds since the epoch
- * @return presigned_url
+ * Get query string that will be put together with the signature
+ * @param aws_s3_profile AWS profile
+ * @return the query string (must be freed)
  */
-SWITCH_MOD_DECLARE(char *) aws_s3_presigned_url_create(const char *verb, const char *url, const char *base_domain, const char *content_type, const char *content_md5, const char *aws_access_key_id, const char *aws_secret_access_key, const char *expires)
+static char* aws_s3_standardized_query_string(switch_aws_s3_profile* aws_s3_profile)
 {
-	char signature[S3_SIGNATURE_LENGTH_MAX];
-	char signature_url_encoded[S3_SIGNATURE_LENGTH_MAX];
-	char *string_to_sign;
-	char *url_dup = strdup(url);
-	char *bucket;
-	char *object;
+	char* credential;
+	char expires[10];
+	char* standardized_query_string;
 
-	/* create URL encoded signature */
-	parse_url(url_dup, base_domain, "s3", &bucket, &object);
-	string_to_sign = aws_s3_string_to_sign(verb, bucket, object, content_type, content_md5, expires);
-	signature[0] = '\0';
-	aws_s3_signature(signature, S3_SIGNATURE_LENGTH_MAX, string_to_sign, aws_secret_access_key);
-	switch_url_encode(signature, signature_url_encoded, S3_SIGNATURE_LENGTH_MAX);
-	free(string_to_sign);
-	free(url_dup);
+	credential = switch_mprintf("%s%%2F%s%%2F%s%%2Fs3%%2Faws4_request", aws_s3_profile->access_key_id, aws_s3_profile->date_stamp, aws_s3_profile->region);
+	switch_snprintf(expires, 9, "%ld", aws_s3_profile->expires);
 
-	/* create the presigned URL */
-	return switch_mprintf("%s?Signature=%s&Expires=%s&AWSAccessKeyId=%s", url, signature_url_encoded, expires, aws_access_key_id);
+	standardized_query_string = switch_mprintf(
+			"X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=%s&X-Amz-Date=%s&X-Amz-Expires=%s&X-Amz-SignedHeaders=host",
+			credential, aws_s3_profile->time_stamp, expires
+	);
+
+	switch_safe_free(credential);
+
+	return standardized_query_string;
 }
 
 /**
- * Create an authentication signature for AWS S3
- * @param authentication buffer to store result
- * @param authentication_length maximum result length
- * @param verb (PUT/GET)
- * @param url address (virtual-host-style)
- * @param base_domain (optional - amazon aws assumed if not specified)
- * @param content_type optional content type
- * @param content_md5 optional content MD5 checksum
- * @param aws_access_key_id secret access key identifier
- * @param aws_secret_access_key secret access key
- * @param date header
- * @return signature for Authorization header
+ * Get request string that is used to build string to sign
+ * @param aws_s3_profile AWS profile
+ * @return the request string (must be freed)
  */
-static char *aws_s3_authentication_create(const char *verb, const char *url, const char *base_domain, const char *content_type, const char *content_md5, const char *aws_access_key_id, const char *aws_secret_access_key, const char *date)
-{
-	char signature[S3_SIGNATURE_LENGTH_MAX];
-	char *string_to_sign;
-	char *url_dup = strdup(url);
-	char *bucket;
-	char *object;
+static char* aws_s3_standardized_request(switch_aws_s3_profile* aws_s3_profile) {
 
-	/* create base64 encoded signature */
-	parse_url(url_dup, base_domain, "s3", &bucket, &object);
-	string_to_sign = aws_s3_string_to_sign(verb, bucket, object, content_type, content_md5, date);
-	signature[0] = '\0';
-	aws_s3_signature(signature, S3_SIGNATURE_LENGTH_MAX, string_to_sign, aws_secret_access_key);
-	free(string_to_sign);
-	free(url_dup);
+	char* standardized_query_string = aws_s3_standardized_query_string(aws_s3_profile);
 
-	return switch_mprintf("AWS %s:%s", aws_access_key_id, signature);
+	char* standardized_request = switch_mprintf(
+		"%s\n/%s\n%s\nhost:%s.%s\n\nhost\nUNSIGNED-PAYLOAD",
+		aws_s3_profile->verb, aws_s3_profile->object, standardized_query_string, aws_s3_profile->bucket, aws_s3_profile->base_domain
+	);
+
+	switch_safe_free(standardized_query_string);
+
+	return standardized_request;
 }
 
+
+/**
+ * Create the string to sign for a AWS signature version 4
+ * @param standardized_request request string that is used to build string to sign
+ * @param aws_s3_profile AWS profile
+ * @return the string to sign (must be freed)
+ */
+static char *aws_s3_string_to_sign(char* standardized_request, switch_aws_s3_profile* aws_s3_profile) {
+
+	char standardized_request_hex[SHA256_DIGEST_LENGTH * 2 + 1] = {'\0'};
+	char* string_to_sign;
+
+	sha256_hex(standardized_request_hex, standardized_request);
+
+	string_to_sign = switch_mprintf(
+		"AWS4-HMAC-SHA256\n%s\n%s/%s/s3/aws4_request\n%s",
+		aws_s3_profile->time_stamp, aws_s3_profile->date_stamp, aws_s3_profile->region, standardized_request_hex
+	);
+
+	return string_to_sign;
+}
+
+/**
+ * Create a full query string that contains signature version 4 for AWS request
+ * @param aws_s3_profile AWS profile
+ * @return full query string that include the signature
+ */
+static char *aws_s3_authentication_create(switch_aws_s3_profile* aws_s3_profile) {
+	char signature[SHA256_DIGEST_LENGTH * 2 + 1];
+	char *string_to_sign;
+
+	char* standardized_query_string;
+	char* standardized_request;
+	char signature_key[SHA256_DIGEST_LENGTH];
+	char* query_param;
+
+	// Get standardized_query_string
+	standardized_query_string = aws_s3_standardized_query_string(aws_s3_profile);
+
+	// Get standardized_request
+	standardized_request = aws_s3_standardized_request(aws_s3_profile);
+
+	// Get string_to_sign
+	string_to_sign = aws_s3_string_to_sign(standardized_request, aws_s3_profile);
+
+	// Get signature_key
+	aws_s3_signature_key(signature_key, aws_s3_profile);
+
+	// Get signature
+	hmac256_hex(signature, signature_key, SHA256_DIGEST_LENGTH, string_to_sign);
+
+	// Build final query string
+	query_param = switch_mprintf("%s&X-Amz-Signature=%s", standardized_query_string, signature);
+
+	switch_safe_free(string_to_sign);
+	switch_safe_free(standardized_query_string);
+	switch_safe_free(standardized_request);
+
+	return query_param;
+}
+#endif
+
+/**
+ * Append Amazon S3 query params to request if necessary
+ * @param headers to add to. AWS signature v4 requires no header to be appended
+ * @param profile with S3 credentials
+ * @param content_type of object (PUT only)
+ * @param verb http methods (GET/PUT)
+ * @param url full url
+ * @param block_num block number, only used by Azure
+ * @param query_string pointer to query param string that will be calculated
+ * @return updated headers
+ */
+SWITCH_MOD_DECLARE(switch_curl_slist_t) *aws_s3_append_headers(
+		http_profile_t *profile,
+		switch_curl_slist_t *headers,
+		const char *verb,
+		unsigned int content_length,
+		const char *content_type,
+		const char *url,
+		const unsigned int block_num,
+		char **query_string
+) {
+#if defined(HAVE_OPENSSL)
+	switch_aws_s3_profile aws_s3_profile;
+	char* url_dup;
+
+	// Get bucket and object name from url
+	switch_strdup(url_dup, url);
+	parse_url(url_dup, profile->base_domain, "s3", &aws_s3_profile.bucket, &aws_s3_profile.object);
+	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "bucket: %s\n", aws_s3_profile.bucket);
+	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "object: %s\n", aws_s3_profile.object);
+
+	// Get date and time
+	get_time("%Y%m%d", aws_s3_profile.date_stamp, DATE_STAMP_LENGTH);
+	get_time("%Y%m%dT%H%M%SZ", aws_s3_profile.time_stamp, TIME_STAMP_LENGTH);
+
+	// Get access key id and secret
+	aws_s3_profile.access_key_id = profile->aws_s3_access_key_id;
+	aws_s3_profile.access_key_secret = profile->secret_access_key;
+
+	// Get base domain
+	aws_s3_profile.base_domain = profile->base_domain;
+	aws_s3_profile.region = profile->region;
+	aws_s3_profile.verb = verb;
+	aws_s3_profile.expires = profile->expires;
+
+	*query_string = aws_s3_authentication_create(&aws_s3_profile);
+
+	switch_safe_free(url_dup);
+#endif
+	return headers;
+}
+
+/**
+ * Get key id, secret and region from env variables or config file
+ * @param xml object that store config file
+ * @param profile pointer that config will be written to
+ * @return status
+ */
 SWITCH_MOD_DECLARE(switch_status_t) aws_s3_config_profile(switch_xml_t xml, http_profile_t *profile)
 {
-	switch_status_t status = SWITCH_STATUS_SUCCESS;
+#if defined(HAVE_OPENSSL)
 	switch_xml_t base_domain_xml = switch_xml_child(xml, "base-domain");
+	switch_xml_t region_xml = switch_xml_child(xml, "region");
+	switch_xml_t expires_xml = switch_xml_child(xml, "expires");
 
+	// Function pointer to be called to append query params to original url
 	profile->append_headers_ptr = aws_s3_append_headers;
 
 	/* check if environment variables set the keys */
 	profile->aws_s3_access_key_id = getenv("AWS_ACCESS_KEY_ID");
 	profile->secret_access_key = getenv("AWS_SECRET_ACCESS_KEY");
 	if (!zstr(profile->aws_s3_access_key_id) && !zstr(profile->secret_access_key)) {
-		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO,
-						  "Using AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables for s3 access on profile \"%s\"\n", profile->name);
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Using AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables for AWS S3 access for profile \"%s\"\n", profile->name);
 		profile->aws_s3_access_key_id = strdup(profile->aws_s3_access_key_id);
 		profile->secret_access_key = strdup(profile->secret_access_key);
 	} else {
 		/* use configuration for keys */
 		switch_xml_t id = switch_xml_child(xml, "access-key-id");
 		switch_xml_t secret = switch_xml_child(xml, "secret-access-key");
+		if (!id || !secret)
+		{
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Missing access-key-id or secret-access-key in http_cache.conf.xml for profile \"%s\"\n", profile->name);
+			return SWITCH_STATUS_FALSE;
+		}
 
-		if (id && secret) {
-			profile->aws_s3_access_key_id = switch_strip_whitespace(switch_xml_txt(id));
-			profile->secret_access_key = switch_strip_whitespace(switch_xml_txt(secret));
-			if (zstr(profile->aws_s3_access_key_id) || zstr(profile->secret_access_key)) {
-				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Missing AWS S3 credentials for profile \"%s\"\n", profile->name);
-				switch_safe_free(profile->aws_s3_access_key_id);
-				profile->aws_s3_access_key_id = NULL;
-				switch_safe_free(profile->secret_access_key);
-				profile->secret_access_key = NULL;
-			}
-		} else {
-			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Missing key id or secret\n");
-			status = SWITCH_STATUS_FALSE;
+		profile->aws_s3_access_key_id = switch_strip_whitespace(switch_xml_txt(id));
+		profile->secret_access_key = switch_strip_whitespace(switch_xml_txt(secret));
+		if (zstr(profile->aws_s3_access_key_id) || zstr(profile->secret_access_key)) {
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Empty access-key-id or secret-access-key in http_cache.conf.xml for profile \"%s\"\n", profile->name);
+			switch_safe_free(profile->aws_s3_access_key_id);
+			switch_safe_free(profile->secret_access_key);
+			return SWITCH_STATUS_FALSE;
 		}
 	}
 
+	// Get region
+	if (!region_xml) {
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Missing region in http_cache.conf.xml for profile \"%s\"\n", profile->name);
+		return SWITCH_STATUS_FALSE;
+	}
+	profile->region = switch_strip_whitespace(switch_xml_txt(region_xml));
+	if (zstr(profile->region)) {
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Empty region in http_cache.conf.xml for profile \"%s\"\n", profile->name);
+		switch_safe_free(profile->region);
+		return SWITCH_STATUS_FALSE;
+	}
+
+	// Get base domain for AWS S3 compatible services. Default base domain is s3.amazonaws.com
 	if (base_domain_xml) {
 		profile->base_domain = switch_strip_whitespace(switch_xml_txt(base_domain_xml));
 		if (zstr(profile->base_domain)) {
 			switch_safe_free(profile->base_domain);
-			profile->base_domain = NULL;
+			profile->base_domain = switch_mprintf(DEFAULT_BASE_DOMAIN, profile->region);
 		}
+	} else
+	{
+		profile->base_domain = switch_mprintf(DEFAULT_BASE_DOMAIN, profile->region);
 	}
-	return status;
+
+	// Get expire time for URL signature
+	if (expires_xml) {
+		char* expires = switch_strip_whitespace(switch_xml_txt(expires_xml));
+		if (!zstr(expires) && switch_is_number(expires))
+		{
+			profile->expires = switch_safe_atoi(expires, DEFAULT_EXPIRATION_TIME);
+		} else
+		{
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Invalid \"expires\" in http_cache.conf.xml for profile \"%s\"\n", profile->name);
+			profile->expires = DEFAULT_EXPIRATION_TIME;
+		}
+		switch_safe_free(expires);
+	} else
+	{
+		profile->expires = DEFAULT_EXPIRATION_TIME;
+	}
+
+#endif
+
+	return SWITCH_STATUS_SUCCESS;
 }
 
-/**
- * Append Amazon S3 headers to request if necessary
- * @param headers to add to.  If NULL, new headers are created.
- * @param profile with S3 credentials
- * @param content_type of object (PUT only)
- * @param verb (GET/PUT)
- * @param url
- * @return updated headers
- */
-SWITCH_MOD_DECLARE(switch_curl_slist_t*) aws_s3_append_headers(http_profile_t *profile, switch_curl_slist_t *headers,
-		const char *verb, unsigned int content_length, const char *content_type, const char *url, const unsigned int block_num, char **query_string)
-{
-	char date[256];
-	char header[1024];
-	char *authenticate;
-
-	/* Date: */
-	switch_rfc822_date(date, switch_time_now());
-	snprintf(header, 1024, "Date: %s", date);
-	headers = switch_curl_slist_append(headers, header);
-
-	/* Authorization: */
-	authenticate = aws_s3_authentication_create(verb, url, profile->base_domain, content_type, "", profile->aws_s3_access_key_id, profile->secret_access_key, date);
-	snprintf(header, 1024, "Authorization: %s", authenticate);
-	free(authenticate);
-	headers = switch_curl_slist_append(headers, header);
-
-	return headers;
-}
-
-
 /* For Emacs:
  * Local Variables:
  * mode:c
diff --git a/src/mod/applications/mod_http_cache/aws.h b/src/mod/applications/mod_http_cache/aws.h
index 3112bfe989..5ae5b933fc 100644
--- a/src/mod/applications/mod_http_cache/aws.h
+++ b/src/mod/applications/mod_http_cache/aws.h
@@ -22,7 +22,8 @@
  *
  * Contributor(s):
  * Chris Rienzo <chris.rienzo@grasshopper.com>
- *
+ * Quoc-Bao Nguyen <baonq5@vng.com.vn>
+ * 
  * aws.h - Some Amazon Web Services helper functions
  *
  */
@@ -33,13 +34,27 @@
 #include <switch_curl.h>
 #include "common.h"
 
-/* (SHA1_LENGTH * 1.37 base64 bytes per byte * 3 url-encoded bytes per byte) */
-#define S3_SIGNATURE_LENGTH_MAX 83
+#define DATE_STAMP_LENGTH 9			// 20190729
+#define TIME_STAMP_LENGTH 17		// 20190729T083832Z
+#define DEFAULT_BASE_DOMAIN "s3.%s.amazonaws.com"
+#define DEFAULT_EXPIRATION_TIME 604800
 
-SWITCH_MOD_DECLARE(switch_curl_slist_t*) aws_s3_append_headers(http_profile_t *profile, switch_curl_slist_t *headers,
-	const char *verb, unsigned int content_length, const char *content_type, const char *url, const unsigned int block_num, char **query_string);
 SWITCH_MOD_DECLARE(switch_status_t) aws_s3_config_profile(switch_xml_t xml, http_profile_t *profile);
-SWITCH_MOD_DECLARE(char *) aws_s3_presigned_url_create(const char *verb, const char *url, const char *base_domain, const char *content_type, const char *content_md5, const char *aws_access_key_id, const char *aws_secret_access_key, const char *expires);
+
+struct aws_s3_profile {
+	const char* base_domain;
+	char* bucket;
+	char* object;
+	char time_stamp[TIME_STAMP_LENGTH];
+	char date_stamp[DATE_STAMP_LENGTH];
+	const char* verb;
+	const char* access_key_id;
+	const char* access_key_secret;
+	const char* region;
+	switch_time_t expires;
+};
+
+typedef struct aws_s3_profile switch_aws_s3_profile;
 
 #endif
 
diff --git a/src/mod/applications/mod_http_cache/common.c b/src/mod/applications/mod_http_cache/common.c
index 5db61fcdf9..04e66c7079 100644
--- a/src/mod/applications/mod_http_cache/common.c
+++ b/src/mod/applications/mod_http_cache/common.c
@@ -22,6 +22,7 @@
  *
  * Contributor(s):
  * Chris Rienzo <chris.rienzo@grasshopper.com>
+ * Quoc-Bao Nguyen <baonq5@vng.com.vn>
  *
  * common.c - Functions common to the store provider
  *
@@ -75,6 +76,7 @@ SWITCH_MOD_DECLARE(void) parse_url(char *url, const char *base_domain, const cha
 	*object = NULL;
 
 	if (zstr(url)) {
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "url is empty\n");
 		return;
 	}
 
@@ -86,6 +88,7 @@ SWITCH_MOD_DECLARE(void) parse_url(char *url, const char *base_domain, const cha
 	}
 
 	if (zstr(bucket_start)) { /* invalid URL */
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "invalid url\n");
 		return;
 	}
 
@@ -96,6 +99,7 @@ SWITCH_MOD_DECLARE(void) parse_url(char *url, const char *base_domain, const cha
 	bucket_end = my_strrstr(bucket_start, base_domain_match);
 
 	if (!bucket_end) { /* invalid URL */
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "invalid url\n");
 		return;
 	}
 
@@ -104,12 +108,14 @@ SWITCH_MOD_DECLARE(void) parse_url(char *url, const char *base_domain, const cha
 	object_start = strchr(bucket_end + 1, '/');
 
 	if (!object_start) { /* invalid URL */
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "invalid url\n");
 		return;
 	}
 
 	object_start++;
 
 	if (zstr(bucket_start) || zstr(object_start)) { /* invalid URL */
+		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "invalid url\n");
 		return;
 	}
 
diff --git a/src/mod/applications/mod_http_cache/common.h b/src/mod/applications/mod_http_cache/common.h
index 612b7fb2ad..9092f2a13b 100644
--- a/src/mod/applications/mod_http_cache/common.h
+++ b/src/mod/applications/mod_http_cache/common.h
@@ -1,10 +1,40 @@
+/*
+ * aws.h for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
+ * Copyright (C) 2013-2014, Grasshopper
+ *
+ * Version: MPL 1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is aws.h for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
+ *
+ * The Initial Developer of the Original Code is Grasshopper
+ * Portions created by the Initial Developer are Copyright (C)
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Chris Rienzo <chris.rienzo@grasshopper.com>
+ * Quoc-Bao Nguyen <baonq5@vng.com.vn>
+ *
+ * common.h - Functions common to the store provider
+ *
+ */
+
 #ifndef COMMON_H
 #define COMMON_H
 
 #include <switch.h>
 
 /**
- * An http profile.  Defines optional credentials
+ * An http profile. Defines optional credentials
  * for access to Amazon S3 and Azure Blob Service
  */
 struct http_profile {
@@ -12,6 +42,8 @@ struct http_profile {
 	char *aws_s3_access_key_id;
 	char *secret_access_key;
 	char *base_domain;
+	char *region;            // AWS region. Used by AWS S3
+	switch_time_t expires;   // Expiration time in seconds for URL signature. Default is 604800 seconds. Used by AWS S3
 	switch_size_t bytes_per_block;
 
 	// function to be called to add the profile specific headers to the GET/PUT requests
diff --git a/src/mod/applications/mod_http_cache/conf/autoload_configs/http_cache.conf.xml b/src/mod/applications/mod_http_cache/conf/autoload_configs/http_cache.conf.xml
index 1408e550ac..bf14470ecb 100644
--- a/src/mod/applications/mod_http_cache/conf/autoload_configs/http_cache.conf.xml
+++ b/src/mod/applications/mod_http_cache/conf/autoload_configs/http_cache.conf.xml
@@ -1,47 +1,74 @@
+<?xml version="1.0"?>
 <configuration name="http_cache.conf" description="HTTP GET cache">
-  <settings>
-    <!-- set to true if you want to enable http:// and https:// formats.  Do not use if mod_httapi is also loaded -->
-    <param name="enable-file-formats" value="false"/>
-    <param name="max-urls" value="10000"/>
-    <param name="location" value="$${base_dir}/http_cache"/>
-    <param name="default-max-age" value="86400"/>
-    <param name="ssl-cacert" value="$${base_dir}/conf/cacert.pem"/>
-    <param name="ssl-verifyhost" value="true"/>
-    <param name="ssl-verifypeer" value="true"/>
-    <!-- default is 300 seconds, override here -->
-    <!--param name="connect-timeout" value="300"/-->
-    <!-- default is 300 seconds, override here -->
-    <!--param name="download-timeout" value="300"/-->
-  </settings>
+    <settings>
+        <!-- set to true if you want to enable http:// and https:// formats.  Do not use if mod_httapi is also loaded -->
+        <param name="enable-file-formats" value="false"/>
+        <param name="max-urls" value="10000"/>
+        <param name="location" value="$${base_dir}/http_cache"/>
+        <param name="default-max-age" value="86400"/>
+        <param name="ssl-cacert" value="$${base_dir}/conf/cacert.pem"/>
+        <param name="ssl-verifyhost" value="true"/>
+        <param name="ssl-verifypeer" value="true"/>
+        <!-- default is 300 seconds, override here -->
+        <!--param name="connect-timeout" value="300"/-->
+        <!-- default is 300 seconds, override here -->
+        <!--param name="download-timeout" value="300"/-->
+    </settings>
+    <profiles>
+        <profile name="s3">
+            <!-- Credentials for AWS account. -->
+            <aws-s3>
+                <!-- Required: Key identifier -->
+                <access-key-id><![CDATA[AKIAIOSFODNN7EXAMPLE]]></access-key-id>
+                <!-- Required: Key secret -->
+                <secret-access-key><![CDATA[wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY]]></secret-access-key>
+                <!-- Optional: Backup folder to save uploaded file in case of failure (for example recording a .wav file to a webserver). Must ended without a slash !-->
+                <!--  If you want to use your own s3-compatible service, base domain MUST be set -->
+                <!--<base-domain><![CDATA[stg.vinadata.vn]]></base-domain>-->
+                <!-- Required: AWS region -->
+                <region><![CDATA[ap-southeast-1]]></region>         <!-- base domain is s3-ap-southeast-1.amazonaws.com -->
+                <!-- Optional: Expiration time in seconds for URL signature. Default is 604800s -->
+                <expires>604800</expires>
+            </aws-s3>
 
-  <profiles>
-    <profile name="s3">
-       <!-- Credentials for AWS account. -->
-       <aws-s3>
-          <!-- 20 character key identifier, can override with AWS_ACCESS_KEY_ID environment variable -->
-          <access-key-id><![CDATA[AKIAIOSFODNN7EXAMPLE]]></access-key-id>
-          <!-- 40 character secret, can override with AWS_SECRET_ACCESS_KEY environment variable -->
-          <secret-access-key><![CDATA[wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY]]></secret-access-key>
-          <!--base-url><![CDATA[s3.example.com]]></base-url-->
-       </aws-s3>
-       <!-- Domains that this profile applies to -->
-       <domains>
-          <domain name="bucket.s3.amazonaws.com"/>
-          <domain name="bucket2.s3.amazonaws.com"/>
-       </domains>
-    </profile>
-
-    <profile name="blob">
-      <azure-blob>
-        <!-- key identifier, can override with AZURE_STORAGE_ACCESS_KEY environment variable -->
-        <secret-access-key>kOOY4Y/sqZU9bsLjmN+9McVwTry+UIn1Owt4Zs/2S2FQT0eAWLKsk
-Z0V6/gGFqCAKVvwXoGjqUn7PNbVjhZiNA==</secret-access-key>
-      </azure-blob>
-      <domains>
-        <domain name="account.blob.core.windows.net"/>
-      </domains>
-    </profile>
-  </profiles>
+            <!-- Required: Domains that this profile will automatically be applied. Domains must have the same base domain (s3-ap-southeast-1.amazonaws.com) -->
+            <domains>
+                <domain name="bucket1.s3-ap-southeast-1.amazonaws.com"/>
+                <domain name="bucket2.s3-ap-southeast-1.amazonaws.com"/>
+            </domains>
+        </profile>
+        <profile name="s3-compatible">
+            <!-- Credentials for AWS account or any account on s3-like storage service -->
+            <aws-s3>
+                <!-- Required: Key identifier -->
+                <access-key-id><![CDATA[506665ebbbaffc1701aaf5a61ad88421]]></access-key-id>
+                <!-- Required: Key secret -->
+                <secret-access-key><![CDATA[2dd63d700744e2c1c277be7dc81bfb1b]]></secret-access-key>
+                <!-- Optional: Base domain for the service -->
+                <!-- If you want to use your own s3-compatible service, base domain MUST be set -->
+                <base-domain><![CDATA[stg.vinadata.vn]]></base-domain>
+                <!-- Required: Storage region -->
+                <region><![CDATA[HCM]]></region>
+                <!-- Optional: Expiration time in seconds for URL signature. Default is 604800s (a week) -->
+                <expires>604800</expires>
+            </aws-s3>
 
+            <!-- Optional: List of domains that this profile will automatically be applied to -->
+            <!-- Domains in this list must have the same base domain with base-domain (if base-domain is set)  -->
+            <!-- If you wish to apply the s3 credentials to a domain not listed here, then use {profile=s3}http://foo.s3... -->
+            <domains>
+                <domain name="bucket1.stg.vinadata.vn"/>
+                <domain name="bucket2.stg.vinadata.vn"/>
+            </domains>
+        </profile>
+        <profile name="blob">
+            <azure-blob>
+                <!-- key identifier, can override with AZURE_STORAGE_ACCESS_KEY environment variable -->
+                <secret-access-key>kOOY4Y/sqZU9bsLjmN+9McVwTry+UIn1Owt4Zs/2S2FQT0eAWLKskZ0V6/gGFqCAKVvwXoGjqUn7PNbVjhZiNA==</secret-access-key>
+            </azure-blob>
+            <domains>
+                <domain name="account.blob.core.windows.net"/>
+            </domains>
+        </profile>
+    </profiles>
 </configuration>
-
diff --git a/src/mod/applications/mod_http_cache/test/s3_auth.py b/src/mod/applications/mod_http_cache/test/s3_auth.py
new file mode 100755
index 0000000000..e346c5af8e
--- /dev/null
+++ b/src/mod/applications/mod_http_cache/test/s3_auth.py
@@ -0,0 +1,144 @@
+#!/usr/bin/python2.7
+# -*- coding: utf-8 -*-
+#
+# s3_auth.py for unit tests in mod_http_cache
+# Copyright (C) 2019 Vinadata Corporation (vinadata.vn). All rights reserved.
+#
+# Version: MPL 1.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Initial Developer of the Original Code is Quoc-Bao Nguyen <baonq5@vng.com.vn>
+# Portions created by the Initial Developer are Copyright (C)
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Quoc-Bao Nguyen <baonq5@vng.com.vn>
+#
+# s3_auth.python - Generate signature for AWS Signature version 4 for unit test
+#
+
+import base64
+import datetime
+import hashlib
+import hmac
+from collections import OrderedDict
+
+import requests
+from requests.utils import quote
+
+
+# hashing methods
+def hmac256(key, msg):
+    return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()
+
+
+def hmac256_hex(key, msg):
+    return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).hexdigest()
+
+
+def sha256_hex(msg):
+    return hashlib.sha256(msg).hexdigest()
+
+
+# region is a wildcard value that takes the place of the AWS region value
+# as COS doesn't use regions like AWS, this parameter can accept any string
+def createSignatureKey(key, date_stamp, region, service):
+    keyDate = hmac256(('AWS4' + key).encode('utf-8'), date_stamp)
+    keyRegion = hmac256(keyDate, region)
+    keyService = hmac256(keyRegion, service)
+    keySigning = hmac256(keyService, 'aws4_request')
+    return keySigning
+
+
+def query_string(access_key, date_stamp, time_stamp, region):
+    fields = OrderedDict()
+    fields["X-Amz-Algorithm"] = "AWS4-HMAC-SHA256"
+    fields["X-Amz-Credential"] = access_key + '/' + date_stamp + '/' + region + '/s3/aws4_request'
+    fields["X-Amz-Date"] = time_stamp
+    fields["X-Amz-Expires"] = "604800"  # in seconds
+    fields["X-Amz-SignedHeaders"] = "host"
+
+    queries_string = ''.join("%s=%s&" % (key, val) for (key, val) in fields.iteritems())[:-1]
+
+    return quote(queries_string, safe='&=')
+
+
+def main():
+    access_key = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
+    secret_key = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
+
+    # request elements
+    http_method = 'GET'
+    region = 'HCM'
+    bucket = 'bucket1'
+    host = 'stg.example.com'
+    endpoint = 'https://' + bucket + "." + host
+    object_name = 'document.docx'
+
+    # assemble the standardized request
+    time = datetime.datetime.utcnow()
+    time_stamp = time.strftime('%Y%m%dT%H%M%SZ')
+    date_stamp = time.strftime('%Y%m%d')
+
+    print "time_stamp: " + time_stamp
+    print "date_stamp: " + date_stamp
+
+    standardized_query_string = query_string(access_key, date_stamp, time_stamp, region)
+    print 'standardized_query_string: \n' + standardized_query_string
+
+    standardized_request = (http_method + '\n' +
+                            '/' + object_name + '\n' +
+                            standardized_query_string + '\n' +
+                            'host:' + bucket + '.' + host + '\n\n' +
+                            'host' + '\n' +
+                            'UNSIGNED-PAYLOAD')
+
+    print 'standardized_request: ' + hashlib.sha256(standardized_request).hexdigest()
+
+    print "\nStandardized request:\n" + standardized_request
+
+    # assemble string-to-sign
+    string_to_sign = ('AWS4-HMAC-SHA256' + '\n' +
+                      time_stamp + '\n' +
+                      date_stamp + '/' + region + '/s3/aws4_request' + '\n' +
+                      sha256_hex(standardized_request))
+
+    print "\nString to Sign:\n" + string_to_sign.replace('\n', "\\n")
+
+    # generate the signature
+    signature_key = createSignatureKey(secret_key, date_stamp, region, 's3')
+
+    print 'signature_key: ' + base64.b64encode(signature_key)
+    # signature = hmac.new(signature_key, sts.encode('utf-8'), hashlib.sha256).hexdigest()
+    signature = hmac256_hex(signature_key, string_to_sign)
+
+    print 'signature: ' + signature
+    # create and send the request
+    # the 'requests' package automatically adds the required 'host' header
+
+    request_url = (endpoint + '/' +
+                   object_name + '?' +
+                   standardized_query_string +
+                   '&X-Amz-Signature=' +
+                   signature)
+
+    print '\nRequest URL:\n' + request_url
+
+    request = requests.get(request_url)
+
+    print '\nResponse code: %d\n' % request.status_code
+    # print '\nResponse code: %s\n' % request.content
+    # print request.text
+
+
+if __name__ == "__main__":
+    main()
diff --git a/src/mod/applications/mod_http_cache/test/test_aws.c b/src/mod/applications/mod_http_cache/test/test_aws.c
index 615f336472..bc857af067 100644
--- a/src/mod/applications/mod_http_cache/test/test_aws.c
+++ b/src/mod/applications/mod_http_cache/test/test_aws.c
@@ -1,12 +1,66 @@
+/*
+ * aws.h for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
+ * Copyright (C) 2013-2014, Grasshopper
+ *
+ * Version: MPL 1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is aws.h for FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
+ *
+ * The Initial Developer of the Original Code is Grasshopper
+ * Portions created by the Initial Developer are Copyright (C)
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Chris Rienzo <chris.rienzo@grasshopper.com>
+ * Quoc-Bao Nguyen <baonq5@vng.com.vn>
+ * 
+ * test_aws.c - Unit tests for functions in aws.c
+ *
+ */
 
 #include <switch.h>
 #include <test/switch_test.h>
 #include "../aws.c"
 
+// Run test
+// make && libtool --mode=execute valgrind --leak-check=full  --log-file=vg.log ./test/test_aws && cat vg.log
+
 FST_BEGIN()
 {
+
 FST_SUITE_BEGIN(aws)
 {
+
+#if defined(HAVE_OPENSSL)
+	char url[100] = {'\0'};
+	switch_aws_s3_profile aws_s3_profile;
+
+	// Get bucket and object name from url
+	aws_s3_profile.bucket = "bucket6";
+	aws_s3_profile.object = "document.docx";
+	memcpy(aws_s3_profile.date_stamp, "20190729", DATE_STAMP_LENGTH);
+	memcpy(aws_s3_profile.time_stamp, "20190729T083832Z", TIME_STAMP_LENGTH);
+	aws_s3_profile.access_key_id = "cbc443a53fb06eafb2b83ca1e4233cbc";
+	aws_s3_profile.access_key_secret = "4a722120f27518abbb8573ca9005d175";
+
+	aws_s3_profile.base_domain = "stg.vinadata.vn";
+	aws_s3_profile.region = "HCM";
+	aws_s3_profile.verb = "GET";
+	aws_s3_profile.expires = DEFAULT_EXPIRATION_TIME;
+
+	switch_snprintf(url, sizeof(url), "http://%s.%s/%s", aws_s3_profile.bucket, aws_s3_profile.base_domain, aws_s3_profile.object);
+#endif
+
 FST_SETUP_BEGIN()
 {
 }
@@ -17,153 +71,272 @@ FST_TEARDOWN_BEGIN()
 }
 FST_TEARDOWN_END()
 
-FST_TEST_BEGIN(test_string_to_sign)
-{
-	char *string_to_sign = NULL;
-	string_to_sign = aws_s3_string_to_sign("GET", "rienzo-vault", "troporocks.mp3", "", "", "Fri, 17 May 2013 19:35:26 GMT")	;
-	fst_check_string_equals("GET\n\n\nFri, 17 May 2013 19:35:26 GMT\n/rienzo-vault/troporocks.mp3", string_to_sign);
-	switch_safe_free(string_to_sign);
-
-	string_to_sign = aws_s3_string_to_sign("GET", "foo", "man.chu", "audio/mpeg", "c8fdb181845a4ca6b8fec737b3581d76", "Thu, 17 Nov 2005 18:49:58 GMT");
-	fst_check_string_equals("GET\nc8fdb181845a4ca6b8fec737b3581d76\naudio/mpeg\nThu, 17 Nov 2005 18:49:58 GMT\n/foo/man.chu", string_to_sign);
-	switch_safe_free(string_to_sign);
-
-	string_to_sign = aws_s3_string_to_sign("", "", "", "", "", "");
-	fst_check_string_equals("\n\n\n\n//", string_to_sign);
-	switch_safe_free(string_to_sign);
-
-	string_to_sign = aws_s3_string_to_sign(NULL, NULL, NULL, NULL, NULL, NULL);
-	fst_check_string_equals("\n\n\n\n//", string_to_sign);
-	switch_safe_free(string_to_sign);
-
-	string_to_sign = aws_s3_string_to_sign("PUT", "bucket", "voicemails/recording.wav", "audio/wav", "", "Wed, 12 Jun 2013 13:16:58 GMT");
-	fst_check_string_equals("PUT\n\naudio/wav\nWed, 12 Jun 2013 13:16:58 GMT\n/bucket/voicemails/recording.wav", string_to_sign);
-	switch_safe_free(string_to_sign);
-}
-FST_TEST_END()
-
-FST_TEST_BEGIN(test_signature)
-{
-	char signature[S3_SIGNATURE_LENGTH_MAX];
-	signature[0] = '\0';
-	fst_check_string_equals("weGrLrc9HDlkYPTepVl0A9VYNlw=", aws_s3_signature(signature, S3_SIGNATURE_LENGTH_MAX, "GET\n\n\nFri, 17 May 2013 19:35:26 GMT\n/rienzo-vault/troporocks.mp3", "hOIZt1oeTX1JzINOMBoKf0BxONRZNQT1J8gIznLx"));
-	fst_check_string_equals("jZNOcbfWmD/A/f3hSvVzXZjM2HU=", aws_s3_signature(signature, S3_SIGNATURE_LENGTH_MAX, "PUT\nc8fdb181845a4ca6b8fec737b3581d76\ntext/html\nThu, 17 Nov 2005 18:49:58 GMT\nx-amz-magic:abracadabra\nx-amz-meta-author:foo@bar.com\n/quotes/nelson", "OtxrzxIsfpFjA7SwPzILwy8Bw21TLhquhboDYROV"));
-	fst_check_string_equals("5m+HAmc5JsrgyDelh9+a2dNrzN8=", aws_s3_signature(signature, S3_SIGNATURE_LENGTH_MAX, "GET\n\n\n\nx-amz-date:Thu, 17 Nov 2005 18:49:58 GMT\nx-amz-magic:abracadabra\n/quotes/nelson", "OtxrzxIsfpFjA7SwPzILwy8Bw21TLhquhboDYROV"));
-	fst_check_string_equals("OKA87rVp3c4kd59t8D3diFmTfuo=", aws_s3_signature(signature, S3_SIGNATURE_LENGTH_MAX, "", "OtxrzxIsfpFjA7SwPzILwy8Bw21TLhquhboDYROV"));
-	fst_check_string_equals("OKA87rVp3c4kd59t8D3diFmTfuo=", aws_s3_signature(signature, S3_SIGNATURE_LENGTH_MAX, NULL, "OtxrzxIsfpFjA7SwPzILwy8Bw21TLhquhboDYROV"));
-	fst_check(aws_s3_signature(signature, S3_SIGNATURE_LENGTH_MAX, "GET\n\n\n\nx-amz-date:Thu, 17 Nov 2005 18:49:58 GMT\nx-amz-magic:abracadabra\n/quotes/nelson", "") == NULL);
-	fst_check(aws_s3_signature(signature, S3_SIGNATURE_LENGTH_MAX, "", "") == NULL);
-	fst_check(aws_s3_signature(signature, S3_SIGNATURE_LENGTH_MAX, NULL, NULL) == NULL);
-	fst_check(aws_s3_signature(NULL, S3_SIGNATURE_LENGTH_MAX, "PUT\nc8fdb181845a4ca6b8fec737b3581d76\ntext/html\nThu, 17 Nov 2005 18:49:58 GMT\nx-amz-magic:abracadabra\nx-amz-meta-author:foo@bar.com\n/quotes/nelson", "OtxrzxIsfpFjA7SwPzILwy8Bw21TLhquhboDYROV") == NULL);
-	fst_check(aws_s3_signature(signature, 0, "PUT\nc8fdb181845a4ca6b8fec737b3581d76\ntext/html\nThu, 17 Nov 2005 18:49:58 GMT\nx-amz-magic:abracadabra\nx-amz-meta-author:foo@bar.com\n/quotes/nelson", "OtxrzxIsfpFjA7SwPzILwy8Bw21TLhquhboDYROV") == NULL);
-	fst_check_string_equals("jZNO", aws_s3_signature(signature, 5, "PUT\nc8fdb181845a4ca6b8fec737b3581d76\ntext/html\nThu, 17 Nov 2005 18:49:58 GMT\nx-amz-magic:abracadabra\nx-amz-meta-author:foo@bar.com\n/quotes/nelson", "OtxrzxIsfpFjA7SwPzILwy8Bw21TLhquhboDYROV"));
-}
-FST_TEST_END()
-
-FST_TEST_BEGIN(test_parse_url)
+#if defined(HAVE_OPENSSL)
+FST_TEST_BEGIN(parse_url)
 {
 	char *bucket;
 	char *object;
-	char url[512] = { 0 };
+	char url_dup[512] = { 0 };
 
-	snprintf(url, sizeof(url), "http://quotes.s3.amazonaws.com/nelson");
-	parse_url(url, NULL, "s3", &bucket, &object);
+	switch_snprintf(url_dup, sizeof(url_dup), url);
+	parse_url(url_dup, aws_s3_profile.base_domain, "s3", &bucket, &object);
+	fst_check_string_equals(aws_s3_profile.bucket, bucket);
+	fst_check_string_equals(aws_s3_profile.object, object);
+
+	switch_snprintf(url_dup, sizeof(url_dup), "https://bucket99.s3.amazonaws.com/image.png");
+	parse_url(url_dup, NULL, "s3", &bucket, &object);
+	fst_check_string_equals(bucket, "bucket99");
+	fst_check_string_equals(object, "image.png");
+
+	switch_snprintf(url_dup, sizeof(url_dup), "https://bucket99.s3.amazonaws.com/folder5/image.png");
+	parse_url(url_dup, NULL, "s3", &bucket, &object);
+	fst_check_string_equals(bucket, "bucket99");
+	fst_check_string_equals(object, "folder5/image.png");
+
+	switch_snprintf(url_dup, sizeof(url_dup), "https://bucket23.vn-hcm.vinadata.vn/image.png");
+	parse_url(url_dup, "vn-hcm.vinadata.vn", "s3", &bucket, &object);
+	fst_check_string_equals(bucket, "bucket23");
+	fst_check_string_equals(object, "image.png");
+
+	switch_snprintf(url_dup, sizeof(url_dup), "https://bucket335.s3-ap-southeast-1.amazonaws.com/vpnclient-v4.29-9680-rtm-2019.02.28-linux-x64-64bit.tar.gz");
+	parse_url(url_dup, NULL, "s3", &bucket, &object);
+	fst_check_string_equals(bucket, "bucket335");
+	fst_check_string_equals(object, "vpnclient-v4.29-9680-rtm-2019.02.28-linux-x64-64bit.tar.gz");
+
+	switch_snprintf(url_dup, sizeof(url_dup), "https://bucket335.s3-ap-southeast-1.amazonaws.com/vpnclient-v4.29-9680-rtm-2019.02.28-linux-x64-64bit.tar.gz");
+	parse_url(url_dup, "s3-ap-southeast-1.amazonaws.com", "s3", &bucket, &object);
+	fst_check_string_equals(bucket, "bucket335");
+	fst_check_string_equals(object, "vpnclient-v4.29-9680-rtm-2019.02.28-linux-x64-64bit.tar.gz");
+
+	switch_snprintf(url_dup, sizeof(url_dup), "http://quotes.s3.amazonaws.com/nelson");
+	parse_url(url_dup, NULL, "s3", &bucket, &object);
 	fst_check_string_equals("quotes", bucket);
 	fst_check_string_equals("nelson", object);
 
-	snprintf(url, sizeof(url), "https://quotes.s3.amazonaws.com/nelson.mp3");
-	parse_url(url, NULL, "s3", &bucket, &object);
+	switch_snprintf(url_dup, sizeof(url_dup), "https://quotes.s3.amazonaws.com/nelson.mp3");
+	parse_url(url_dup, NULL, "s3", &bucket, &object);
 	fst_check_string_equals("quotes", bucket);
 	fst_check_string_equals("nelson.mp3", object);
 
-	snprintf(url, sizeof(url), "http://s3.amazonaws.com/quotes/nelson");
-	parse_url(url, NULL, "s3", &bucket, &object);
+	switch_snprintf(url_dup, sizeof(url_dup), "http://s3.amazonaws.com/quotes/nelson");
+	parse_url(url_dup, NULL, "s3", &bucket, &object);
 	fst_check(bucket == NULL);
 	fst_check(object == NULL);
 
-	snprintf(url, sizeof(url), "http://quotes/quotes/nelson");
-	parse_url(url, NULL, "s3", &bucket, &object);
+	switch_snprintf(url_dup, sizeof(url_dup), "http://quotes/quotes/nelson");
+	parse_url(url_dup, NULL, "s3", &bucket, &object);
 	fst_check(bucket == NULL);
 	fst_check(object == NULL);
 
-	snprintf(url, sizeof(url), "http://quotes.s3.amazonaws.com/");
-	parse_url(url, NULL, "s3", &bucket, &object);
+	switch_snprintf(url_dup, sizeof(url_dup), "http://quotes.s3.amazonaws.com/");
+	parse_url(url_dup, NULL, "s3", &bucket, &object);
 	fst_check(bucket == NULL);
 	fst_check(object == NULL);
 
-	snprintf(url, sizeof(url), "http://quotes.s3.amazonaws.com");
-	parse_url(url, NULL, "s3", &bucket, &object);
+	switch_snprintf(url_dup, sizeof(url_dup), "http://quotes.s3.amazonaws.com");
+	parse_url(url_dup, NULL, "s3", &bucket, &object);
 	fst_check(bucket == NULL);
 	fst_check(object == NULL);
 
-	snprintf(url, sizeof(url), "http://quotes");
-	parse_url(url, NULL, "s3", &bucket, &object);
+	switch_snprintf(url_dup, sizeof(url_dup), "http://quotes");
+	parse_url(url_dup, NULL, "s3", &bucket, &object);
 	fst_check(bucket == NULL);
 	fst_check(object == NULL);
 
-	snprintf(url, sizeof(url), "%s", "");
-	parse_url(url, NULL, "s3", &bucket, &object);
+	switch_snprintf(url_dup, sizeof(url_dup), "%s", "");
+	parse_url(url_dup, NULL, "s3", &bucket, &object);
 	fst_check(bucket == NULL);
 	fst_check(object == NULL);
 
-	parse_url(NULL, NULL, "s3", &bucket, &object);
+	switch_snprintf(NULL, 0, "s3", &bucket, &object);
 	fst_check(bucket == NULL);
 	fst_check(object == NULL);
 
-	snprintf(url, sizeof(url), "http://bucket.s3.amazonaws.com/voicemails/recording.wav");
-	parse_url(url, NULL, "s3", &bucket, &object);
+	switch_snprintf(url_dup, sizeof(url_dup), "http://bucket.s3.amazonaws.com/voicemails/recording.wav");
+	parse_url(url_dup, NULL, "s3", &bucket, &object);
 	fst_check_string_equals("bucket", bucket);
 	fst_check_string_equals("voicemails/recording.wav", object);
 
-	snprintf(url, sizeof(url), "https://my-bucket-with-dash.s3-us-west-2.amazonaws.com/greeting/file/1002/Lumino.mp3");
-	parse_url(url, NULL, "s3", &bucket, &object);
+	switch_snprintf(url_dup, sizeof(url_dup), "https://my-bucket-with-dash.s3-us-west-2.amazonaws.com/greeting/file/1002/Lumino.mp3");
+	parse_url(url_dup, NULL, "s3", &bucket, &object);
 	fst_check_string_equals("my-bucket-with-dash", bucket);
 	fst_check_string_equals("greeting/file/1002/Lumino.mp3", object);
 
-	snprintf(url, sizeof(url), "http://quotes.s3.foo.bar.s3.amazonaws.com/greeting/file/1002/Lumino.mp3");
-	parse_url(url, NULL, "s3", &bucket, &object);
+	switch_snprintf(url_dup, sizeof(url_dup), "http://quotes.s3.foo.bar.s3.amazonaws.com/greeting/file/1002/Lumino.mp3");
+	parse_url(url_dup, NULL, "s3", &bucket, &object);
 	fst_check_string_equals("quotes.s3.foo.bar", bucket);
 	fst_check_string_equals("greeting/file/1002/Lumino.mp3", object);
 
-	snprintf(url, sizeof(url), "http://quotes.s3.foo.bar.example.com/greeting/file/1002/Lumino.mp3");
-	parse_url(url, "example.com", "s3", &bucket, &object);
+	switch_snprintf(url_dup, sizeof(url_dup), "http://quotes.s3.foo.bar.example.com/greeting/file/1002/Lumino.mp3");
+	parse_url(url_dup, "example.com", "s3", &bucket, &object);
 	fst_check_string_equals("quotes.s3.foo.bar", bucket);
 	fst_check_string_equals("greeting/file/1002/Lumino.mp3", object);
 }
 FST_TEST_END()
 
-FST_TEST_BEGIN(test_authorization_header)
+FST_TEST_BEGIN(aws_s3_standardized_query_string)
 {
-	char *authentication_header = aws_s3_authentication_create("GET", "https://vault.s3.amazonaws.com/awesome.mp3", NULL, "audio/mpeg", "", "AKIAIOSFODNN7EXAMPLE", "0123456789012345678901234567890123456789", "1234567890");
-	fst_check_string_equals("AWS AKIAIOSFODNN7EXAMPLE:YJkomOaqUJlvEluDq4fpusID38Y=", authentication_header);
-	switch_safe_free(authentication_header);
-
-	authentication_header = aws_s3_authentication_create("GET", "https://vault.s3.amazonaws.com/awesome.mp3", "s3.amazonaws.com", "audio/mpeg", "", "AKIAIOSFODNN7EXAMPLE", "0123456789012345678901234567890123456789", "1234567890");
-	fst_check_string_equals("AWS AKIAIOSFODNN7EXAMPLE:YJkomOaqUJlvEluDq4fpusID38Y=", authentication_header);
-	switch_safe_free(authentication_header);
-
-	authentication_header = aws_s3_authentication_create("GET", "https://vault.example.com/awesome.mp3", "example.com", "audio/mpeg", "", "AKIAIOSFODNN7EXAMPLE", "0123456789012345678901234567890123456789", "1234567890");
-	fst_check_string_equals("AWS AKIAIOSFODNN7EXAMPLE:YJkomOaqUJlvEluDq4fpusID38Y=", authentication_header);
-	switch_safe_free(authentication_header);
+	char* standardized_query_string = aws_s3_standardized_query_string(&aws_s3_profile);
+	fst_check_string_equals("X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=cbc443a53fb06eafb2b83ca1e4233cbc%2F20190729%2FHCM%2Fs3%2Faws4_request&X-Amz-Date=20190729T083832Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host", standardized_query_string);
+	switch_safe_free(standardized_query_string);
 }
 FST_TEST_END()
 
-FST_TEST_BEGIN(test_presigned_url)
+FST_TEST_BEGIN(get_time)
 {
-	char *presigned_url = aws_s3_presigned_url_create("GET", "https://vault.s3.amazonaws.com/awesome.mp3", NULL, "audio/mpeg", "", "AKIAIOSFODNN7EXAMPLE", "0123456789012345678901234567890123456789", "1234567890");
-	fst_check_string_equals("https://vault.s3.amazonaws.com/awesome.mp3?Signature=YJkomOaqUJlvEluDq4fpusID38Y%3D&Expires=1234567890&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE", presigned_url);
-	switch_safe_free(presigned_url);
+	char time_stamp[TIME_STAMP_LENGTH];
+	char date_stamp[DATE_STAMP_LENGTH];
+	char time_stamp_test[TIME_STAMP_LENGTH];
+	char date_stamp_test[DATE_STAMP_LENGTH];
 
-	presigned_url = aws_s3_presigned_url_create("GET", "https://vault.s3.amazonaws.com/awesome.mp3", "s3.amazonaws.com", "audio/mpeg", "", "AKIAIOSFODNN7EXAMPLE", "0123456789012345678901234567890123456789", "1234567890");
-	fst_check_string_equals("https://vault.s3.amazonaws.com/awesome.mp3?Signature=YJkomOaqUJlvEluDq4fpusID38Y%3D&Expires=1234567890&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE", presigned_url);
-	switch_safe_free(presigned_url);
+	// Get date and time for test case
+	time_t rawtime;
+	struct tm * timeinfo;
+	time(&rawtime);
+	timeinfo = gmtime(&rawtime);
 
-	presigned_url = aws_s3_presigned_url_create("GET", "https://vault.example.com/awesome.mp3", "example.com", "audio/mpeg", "", "AKIAIOSFODNN7EXAMPLE", "0123456789012345678901234567890123456789", "1234567890");
-	fst_check_string_equals("https://vault.example.com/awesome.mp3?Signature=YJkomOaqUJlvEluDq4fpusID38Y%3D&Expires=1234567890&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE", presigned_url);
-	switch_safe_free(presigned_url);
+	// Get date and time to test
+	get_time("%Y%m%d", date_stamp, DATE_STAMP_LENGTH);
+	get_time("%Y%m%dT%H%M%SZ", time_stamp, TIME_STAMP_LENGTH);
+
+	// https://fresh2refresh.com/c-programming/c-time-related-functions/
+	// https://stackoverflow.com/questions/5141960/get-the-current-time-in-c/5142028
+	// https://linux.die.net/man/3/ctime
+	// https://stackoverflow.com/questions/153890/printing-leading-0s-in-c
+	switch_snprintf(date_stamp_test, DATE_STAMP_LENGTH, "%d%02d%02d", timeinfo->tm_year + 1900, timeinfo->tm_mon + 1, timeinfo->tm_mday);
+	switch_snprintf(time_stamp_test, TIME_STAMP_LENGTH, "%d%02d%02dT%02d%02d%02dZ", timeinfo->tm_year + 1900, timeinfo->tm_mon + 1, timeinfo->tm_mday, timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);
+
+	fst_check_string_equals(time_stamp_test, time_stamp);
+	fst_check_string_equals(date_stamp_test, date_stamp);
 }
 FST_TEST_END()
 
+FST_TEST_BEGIN(hmac256_hex)
+{
+	char hex[SHA256_DIGEST_LENGTH * 2 + 1];
+
+	fst_check_string_equals("61d8c60f9c2cd767d3db37a52966965ef508136693d99ea533cff1b712044653", hmac256_hex(hex, "d8a1c4f68b15844de5d07960a57b1669", SHA256_DIGEST_LENGTH, "27a7d569d0c12cc576f20665651fb72c"));
+	fst_check_string_equals("5a98f20477a538bd29f0903cc30accaf4151b22e1f44577b75bae4cc5068df9e", hmac256_hex(hex, "66b0d6c5b3fd9c57a345b03877c902cb", SHA256_DIGEST_LENGTH, "2da091ff2a9818ce6deb5c4b6d9ad51c"));
+	fst_check_string_equals("6accbbef08f240dbdebf154cda91f7c66ef178023d53db7f3656d204996effaa", hmac256_hex(hex, "820f6b29b5ca8fa1077b69edf4ee456f", SHA256_DIGEST_LENGTH, "063ee28c963df34342ffb7ac0feae1d9"));
+}
+FST_TEST_END()
+
+FST_TEST_BEGIN(sha256_hex)
+{
+	char hex[SHA256_DIGEST_LENGTH * 2 + 1];
+
+	fst_check_string_equals("ebab701faffb9cd018d7fa566ca0e7f55dd7a9850cae06e088554238d6fae257", sha256_hex(hex, "eccbb6195a0f08664e2a35c0d686e892"));
+	fst_check_string_equals("4884c0be257758ded0381f940870a9280b367002e5c518fb42d56641b451a66b", sha256_hex(hex, "1993f63438fe482cd3040aeb2390b98c"));
+	fst_check_string_equals("1c930bd8e5034a418fef94b1cb753ec82b2a510429bfcdf41b597c6f6c7b21e4", sha256_hex(hex, "3705c5709dc52a04d844ebbcf59e7672"));
+}
+FST_TEST_END()
+
+FST_TEST_BEGIN(aws_s3_standardized_request)
+{
+	char* aws_s3_standardized_request_str = aws_s3_standardized_request(&aws_s3_profile);
+
+	fst_check_string_equals("GET\n/document.docx\nX-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=cbc443a53fb06eafb2b83ca1e4233cbc%2F20190729%2FHCM%2Fs3%2Faws4_request&X-Amz-Date=20190729T083832Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host\nhost:bucket6.stg.vinadata.vn\n\nhost\nUNSIGNED-PAYLOAD", aws_s3_standardized_request_str);
+	switch_safe_free(aws_s3_standardized_request_str);
+}
+FST_TEST_END()
+
+FST_TEST_BEGIN(aws_s3_string_to_sign)
+{
+	char* aws_s3_standardized_request_str = aws_s3_standardized_request(&aws_s3_profile);
+	char* aws_s3_string_to_sign_str = aws_s3_string_to_sign(aws_s3_standardized_request_str, &aws_s3_profile);
+
+	fst_check_string_equals("AWS4-HMAC-SHA256\n20190729T083832Z\n20190729/HCM/s3/aws4_request\n945cd2782c8685f5b2472873252fa048eaa37cf8b132ef667bd98b6ad33238ac", aws_s3_string_to_sign_str);
+
+	switch_safe_free(aws_s3_standardized_request_str);
+	switch_safe_free(aws_s3_string_to_sign_str);
+}
+FST_TEST_END()
+
+FST_TEST_BEGIN(aws_s3_signature_key)
+{
+	char signature_key[SHA256_DIGEST_LENGTH];
+	unsigned int aws_s3_signature_key_b64_size = SHA256_DIGEST_LENGTH * 4 / 3 + 5;
+	unsigned char* aws_s3_signature_key_b64 = (unsigned char*)malloc(aws_s3_signature_key_b64_size);
+	char* aws_s3_signature_key_buffer = aws_s3_signature_key(signature_key, &aws_s3_profile);
+
+	switch_b64_encode((unsigned char*)aws_s3_signature_key_buffer, SHA256_DIGEST_LENGTH, aws_s3_signature_key_b64, aws_s3_signature_key_b64_size);
+	fst_check_string_equals("2TBIZBxK1k+qh/pvEs0d2iNQ4SSX63o/8pLzzFPeA7c=", (char*)aws_s3_signature_key_b64);
+
+	switch_safe_free(aws_s3_signature_key_b64);
+}
+FST_TEST_END()
+
+FST_TEST_BEGIN(aws_s3_authentication_create)
+{
+	char* query_param = aws_s3_authentication_create(&aws_s3_profile);
+
+	fst_check_string_equals("X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=cbc443a53fb06eafb2b83ca1e4233cbc%2F20190729%2FHCM%2Fs3%2Faws4_request&X-Amz-Date=20190729T083832Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=3d0e5c18e85440a6cd38bdf8b3d07476fe6f98b8456a39ec401d1c628ce19175", query_param);
+
+	switch_safe_free(query_param);
+}
+FST_TEST_END()
+
+FST_TEST_BEGIN(parse_xml_config_with_aws)
+{
+	switch_xml_t cfg, profiles, profile, aws_s3_profile;
+	http_profile_t http_profile;
+	int fd;
+	int i = 0;
+
+	printf("\n");
+
+	fd = open("test_aws_http_cache.conf.xml", O_RDONLY);
+	if (fd < 0) {
+		fd = open("test/test_aws_http_cache.conf.xml", O_RDONLY);
+	}
+	fst_check(fd > 0);
+
+	cfg = switch_xml_parse_fd(fd);
+	fst_check(cfg != NULL);
+
+	profiles = switch_xml_child(cfg, "profiles");
+	fst_check(profiles);
+
+	for (profile = switch_xml_child(profiles, "profile"); profile; profile = profile->next) {
+		const char *name = NULL;
+		i++;
+
+		fst_check(profile);
+
+		name = switch_xml_attr_soft(profile, "name");
+		printf("testing profile name: %s\n", name);
+		fst_check(name);
+
+		http_profile.name = name;
+		http_profile.aws_s3_access_key_id = NULL;
+		http_profile.secret_access_key = NULL;
+		http_profile.base_domain = NULL;
+		http_profile.region = NULL;
+		http_profile.append_headers_ptr = NULL;
+
+		aws_s3_profile = switch_xml_child(profile, "aws-s3");
+		fst_check(aws_s3_profile);
+
+		fst_check(aws_s3_config_profile(aws_s3_profile, &http_profile) == SWITCH_STATUS_SUCCESS);
+
+		fst_check(!zstr(http_profile.region));
+		fst_check(!zstr(http_profile.aws_s3_access_key_id));
+		fst_check(!zstr(http_profile.secret_access_key));
+		printf("base domain: %s\n", http_profile.base_domain);
+		fst_check(!zstr(http_profile.base_domain));
+		switch_safe_free(http_profile.region);
+		switch_safe_free(http_profile.aws_s3_access_key_id);
+		switch_safe_free(http_profile.secret_access_key);
+		switch_safe_free(http_profile.base_domain);
+	}
+
+	fst_check(i == 2);      // test data contain two config
+
+	switch_xml_free(cfg);
+}
+FST_TEST_END()
+#endif
+
 }
 FST_SUITE_END()
 
diff --git a/src/mod/applications/mod_http_cache/test/test_aws_http_cache.conf.xml b/src/mod/applications/mod_http_cache/test/test_aws_http_cache.conf.xml
new file mode 100644
index 0000000000..c21955ee21
--- /dev/null
+++ b/src/mod/applications/mod_http_cache/test/test_aws_http_cache.conf.xml
@@ -0,0 +1,66 @@
+<configuration name="http_cache.conf" description="HTTP GET cache">
+    <settings>
+        <!-- set to true if you want to enable http:// and https:// formats.  Do not use if mod_httapi is also loaded -->
+        <param name="enable-file-formats" value="false"/>
+        <param name="max-urls" value="10000"/>
+        <param name="location" value="$${cache_dir}"/>
+        <param name="default-max-age" value="86400"/>
+        <param name="prefetch-thread-count" value="8"/>
+        <param name="prefetch-queue-size" value="100"/>
+        <!-- absolute path to CA bundle file -->
+        <param name="ssl-cacert" value="$${certs_dir}/cacert.pem"/>
+        <!-- verify certificates -->
+        <param name="ssl-verifypeer" value="true"/>
+        <!-- verify host name matches certificate -->
+        <param name="ssl-verifyhost" value="true"/>
+        <!-- default is 300 seconds, override here -->
+        <!--param name="connect-timeout" value="300"/-->
+        <!-- default is 300 seconds, override here -->
+        <!--param name="download-timeout" value="300"/-->
+    </settings>
+
+    <profiles>
+        <profile name="s3">
+            <!-- Credentials for AWS account -->
+            <aws-s3>
+                <!-- Required: Key identifier -->
+                <access-key-id><![CDATA[AKIAIOSFODNN7EXAMPLE]]></access-key-id>
+                <!-- Required: Key secret -->
+                <secret-access-key><![CDATA[wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY]]></secret-access-key>
+                <!-- Required: Storage region -->
+                <region><![CDATA[ap-southeast-1]]></region>
+                <!-- Optional: Expiration time in seconds for URL signature. Default is 604800s (a week) -->
+                <expires>604800</expires>
+            </aws-s3>
+            <!-- Optional: List of domains that this profile will automatically be applied to -->
+            <!-- Domains in this list must have the same base domain with base-domain (if base-domain is set)  -->
+            <!-- If you wish to apply the s3 credentials to a domain not listed here, then use {profile=s3}http://foo.s3... -->
+            <domains>
+                <domain name="bucket1.s3-ap-southeast-1.amazonaws.com"/>
+            </domains>
+        </profile>
+
+        <profile name="s3-compatible">
+            <!-- Credentials for account on s3-like service -->
+            <aws-s3>
+                <!-- Required: Key identifier -->
+                <access-key-id><![CDATA[XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX]]></access-key-id>
+                <!-- Required: Key secret -->
+                <secret-access-key><![CDATA[XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX]]></secret-access-key>
+                <!-- Optional: Base domain for the service -->
+                <!-- If you want to use your own s3-compatible service, base domain MUST be set -->
+                <base-domain><![CDATA[stg.example.com]]></base-domain>
+                <!-- Required: Storage region -->
+                <region><![CDATA[ap-southeast-1]]></region>
+                <!-- Optional: Expiration time in seconds for URL signature. Default is 604800s (a week) -->
+                <expires>604800</expires>
+            </aws-s3>
+            <!-- Optional: List of domains that this profile will automatically be applied to -->
+            <!-- Domains in this list must have the same base domain with base-domain (if base-domain is set)  -->
+            <!-- If you wish to apply the s3 credentials to a domain not listed here, then use {profile=s3}http://foo.s3... -->
+            <domains>
+                <domain name="bucket2.stg.example.com"/>
+            </domains>
+        </profile>
+    </profiles>
+</configuration>