Add v4 sig to http cache (#741)
* FS-9676 [mod_http_cache] Support for AWS Signature version 4 and drop support for version 2 Co-authored-by: baonq-me <quocbao747@gmail.com>
This commit is contained in:
parent
90fb233ac6
commit
17ebce7e3b
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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()
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue