This is an automated email from the ASF dual-hosted git repository. zwoop pushed a commit to branch 7.1.x in repository https://gitbox.apache.org/repos/asf/trafficserver.git
commit e798dfea32938f8c2bee716b1a34890a85ad15e2 Author: Gancho Tenev <gtte...@gmail.com> AuthorDate: Tue Apr 25 13:02:44 2017 -0700 Unit tests for AWS Signature Version 4 (cherry picked from commit 3bcc7ce051c391b1833fbb4f9343b45a96ae19ff) --- plugins/s3_auth/Makefile.inc | 9 + plugins/s3_auth/aws_auth_v4.h | 105 +-- .../s3_auth/{aws_auth_v4.h => aws_auth_v4_wrap.h} | 90 +-- plugins/s3_auth/unit-tests/main.cpp | 25 + plugins/s3_auth/unit-tests/test_aws_auth_v4.cc | 861 +++++++++++++++++++++ plugins/s3_auth/unit-tests/test_aws_auth_v4.h | 145 ++++ 6 files changed, 1051 insertions(+), 184 deletions(-) diff --git a/plugins/s3_auth/Makefile.inc b/plugins/s3_auth/Makefile.inc index 7865d5e..83af007 100644 --- a/plugins/s3_auth/Makefile.inc +++ b/plugins/s3_auth/Makefile.inc @@ -16,3 +16,12 @@ pkglib_LTLIBRARIES += s3_auth/s3_auth.la s3_auth_s3_auth_la_SOURCES = s3_auth/s3_auth.cc s3_auth/aws_auth_v4.cc + +check_PROGRAMS += test_s3auth + +test_s3auth_CPPFLAGS = $(AM_CPPFLAGS) -I$(abs_top_srcdir)/tests/include -DAWS_AUTH_V4_UNIT_TEST +test_s3auth_LDADD = $(OPENSSL_LIBS) +test_s3auth_SOURCES = \ + s3_auth/unit-tests/main.cpp \ + s3_auth/unit-tests/test_aws_auth_v4.cc \ + s3_auth/aws_auth_v4.cc diff --git a/plugins/s3_auth/aws_auth_v4.h b/plugins/s3_auth/aws_auth_v4.h index 1959ddf..94d2a52 100644 --- a/plugins/s3_auth/aws_auth_v4.h +++ b/plugins/s3_auth/aws_auth_v4.h @@ -52,106 +52,11 @@ public: virtual HeaderIterator headerEnd() = 0; }; -/* Define a header iterator to be used in the plugin using ATS API */ -class HeaderIterator -{ -public: - HeaderIterator() : _bufp(nullptr), _hdrs(TS_NULL_MLOC), _field(TS_NULL_MLOC) {} - HeaderIterator(TSMBuffer bufp, TSMLoc hdrs, TSMLoc field) : _bufp(bufp), _hdrs(hdrs), _field(field) {} - HeaderIterator(const HeaderIterator &it) - { - _bufp = it._bufp; - _hdrs = it._hdrs; - _field = it._field; - } - ~HeaderIterator() {} - HeaderIterator & - operator=(HeaderIterator &it) - { - _bufp = it._bufp; - _hdrs = it._hdrs; - _field = it._field; - return *this; - } - HeaderIterator &operator++() - { - /* @todo this is said to be slow in the API call comments, do something better here */ - TSMLoc next = TSMimeHdrFieldNext(_bufp, _hdrs, _field); - TSHandleMLocRelease(_bufp, _hdrs, _field); - _field = next; - return *this; - } - HeaderIterator operator++(int) - { - HeaderIterator tmp(*this); - operator++(); - return tmp; - } - bool - operator!=(const HeaderIterator &it) - { - return _bufp != it._bufp || _hdrs != it._hdrs || _field != it._field; - } - bool - operator==(const HeaderIterator &it) - { - return _bufp == it._bufp && _hdrs == it._hdrs && _field == it._field; - } - const char * - getName(int *len) - { - return TSMimeHdrFieldNameGet(_bufp, _hdrs, _field, len); - } - const char * - getValue(int *len) - { - return TSMimeHdrFieldValueStringGet(_bufp, _hdrs, _field, -1, len); - } - TSMBuffer _bufp; - TSMLoc _hdrs; - TSMLoc _field; -}; - -/* Define a API to be used in the plugin using ATS API */ -class TsApi : public TsInterface -{ -public: - TsApi(TSMBuffer bufp, TSMLoc hdrs, TSMLoc url) : _bufp(bufp), _hdrs(hdrs), _url(url) {} - ~TsApi() {} - const char * - getMethod(int *len) - { - return TSHttpHdrMethodGet(_bufp, _hdrs, len); - } - const char * - getHost(int *len) - { - return TSHttpHdrHostGet(_bufp, _hdrs, len); - } - const char * - getPath(int *len) - { - return TSUrlPathGet(_bufp, _url, len); - } - const char * - getQuery(int *len) - { - return TSUrlHttpQueryGet(_bufp, _url, len); - } - HeaderIterator - headerBegin() - { - return HeaderIterator(_bufp, _hdrs, TSMimeHdrFieldGet(_bufp, _hdrs, 0)); - } - HeaderIterator - headerEnd() - { - return HeaderIterator(_bufp, _hdrs, TS_NULL_MLOC); - } - TSMBuffer _bufp; - TSMLoc _hdrs; - TSMLoc _url; -}; +#ifdef AWS_AUTH_V4_UNIT_TEST +#include "unit-tests/test_aws_auth_v4.h" +#else +#include "aws_auth_v4_wrap.h" +#endif /* S3 auth v4 utility API */ diff --git a/plugins/s3_auth/aws_auth_v4.h b/plugins/s3_auth/aws_auth_v4_wrap.h similarity index 53% copy from plugins/s3_auth/aws_auth_v4.h copy to plugins/s3_auth/aws_auth_v4_wrap.h index 1959ddf..b14f9c6 100644 --- a/plugins/s3_auth/aws_auth_v4.h +++ b/plugins/s3_auth/aws_auth_v4_wrap.h @@ -17,40 +17,13 @@ */ /** - * @file aws_auth_v4.h - * @brief AWS Auth v4 signing utility. - * @see aws_auth_v4.cc + * @file aws_auth_v4_ts.h + * @brief TS API adaptor and header iterator using the TS API which are swapped with mocks during testing. + * @see aws_auth_v4.h */ -#ifndef PLUGINS_S3_AUTH_AWS_AUTH_V4_CC_ -#define PLUGINS_S3_AUTH_AWS_AUTH_V4_CC_ - -#include <algorithm> /* transform() */ -#include <cstddef> /* soze_t */ -#include <string> /* std::string */ -#include <sstream> /* std::stringstream */ -#include <map> /* std::map */ -#include <set> /* std::set */ - -#include <ts/ts.h> - -typedef std::string String; -typedef std::set<std::string> StringSet; -typedef std::map<std::string, std::string> StringMap; - -class HeaderIterator; - -class TsInterface -{ -public: - virtual ~TsInterface(){}; - virtual const char *getMethod(int *length) = 0; - virtual const char *getHost(int *length) = 0; - virtual const char *getPath(int *length) = 0; - virtual const char *getQuery(int *length) = 0; - virtual HeaderIterator headerBegin() = 0; - virtual HeaderIterator headerEnd() = 0; -}; +#ifndef PLUGINS_S3_AUTH_AWS_AUTH_V4_WRAP_H_ +#define PLUGINS_S3_AUTH_AWS_AUTH_V4_WRAP_H_ /* Define a header iterator to be used in the plugin using ATS API */ class HeaderIterator @@ -153,55 +126,4 @@ public: TSMLoc _url; }; -/* S3 auth v4 utility API */ - -static const String X_AMZ_CONTENT_SHA256 = "x-amz-content-sha256"; -static const String X_AMX_DATE = "x-amz-date"; -static const String X_AMZ = "x-amz-"; -static const String CONTENT_TYPE = "content-type"; -static const String HOST = "host"; - -String trimWhiteSpaces(const String &s); - -template <typename ContainerType> -void -commaSeparateString(ContainerType &ss, const String &input, bool trim = true, bool lowerCase = true) -{ - std::istringstream istr(input); - String token; - - while (std::getline(istr, token, ',')) { - token = trim ? trimWhiteSpaces(token) : token; - if (lowerCase) { - std::transform(token.begin(), token.end(), token.begin(), ::tolower); - } - ss.insert(ss.end(), token); - } -} - -class AwsAuthV4 -{ -public: - AwsAuthV4(TsInterface &api, time_t *now, bool signPayload, const char *awsAccessKeyId, size_t awsAccessKeyIdLen, - const char *awsSecretAccessKey, size_t awsSecretAccessKeyLen, const char *awsService, size_t awsServiceLen, - const StringSet &includedHeaders, const StringSet &excludedHeaders, const StringMap ®ionMap); - const char *getDateTime(size_t *dateTimeLen); - String getPayloadHash(); - String getAuthorizationHeader(); - -private: - TsInterface &_api; - char _dateTime[sizeof "20170428T010203Z"]; - bool _signPayload = false; - const char *_awsAccessKeyId = nullptr; - size_t _awsAccessKeyIdLen = 0; - const char *_awsSecretAccessKey = nullptr; - size_t _awsSecretAccessKeyLen = 0; - const char *_awsService = nullptr; - size_t _awsServiceLen = 0; - - const StringSet &_includedHeaders; - const StringSet &_excludedHeaders; - const StringMap &_regionMap; -}; -#endif /* PLUGINS_S3_AUTH_AWS_AUTH_V4_CC_ */ +#endif /* PLUGINS_S3_AUTH_AWS_AUTH_V4_WRAP_H_ */ diff --git a/plugins/s3_auth/unit-tests/main.cpp b/plugins/s3_auth/unit-tests/main.cpp new file mode 100644 index 0000000..6aed3a6 --- /dev/null +++ b/plugins/s3_auth/unit-tests/main.cpp @@ -0,0 +1,25 @@ +/** @file + + This file used for catch based tests. It is the main() stub. + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#define CATCH_CONFIG_MAIN +#include "catch.hpp" diff --git a/plugins/s3_auth/unit-tests/test_aws_auth_v4.cc b/plugins/s3_auth/unit-tests/test_aws_auth_v4.cc new file mode 100644 index 0000000..4589aca --- /dev/null +++ b/plugins/s3_auth/unit-tests/test_aws_auth_v4.cc @@ -0,0 +1,861 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/** + * @file aws_auth_v4_test.cc + * @brief Unit tests for functions implementing S3 auth version 4 + */ + +#include <string.h> +#include <openssl/hmac.h> /* EVP_MAX_MD_SIZE */ +#include <catch.hpp> /* catch unit-test framework */ +#include "../aws_auth_v4.h" /* S3 auth v4 utility */ + +/* uriEncode() ***************************************************************************************************************** */ + +TEST_CASE("uriEncode(): encode empty input", "[AWS][auth][utility]") +{ + String in(""); + String encoded = uriEncode(in, /* isObjectName */ false); + CHECK(0 == encoded.length()); /* 0 encoded because of the invalid input */ +} + +TEST_CASE("uriEncode(): encode unreserved chars", "[s3_auth]") +{ + const String in = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "-._~"; + String encoded = uriEncode(in, /* isObjectName */ false); + + CHECK(in.length() == encoded.length()); + CHECK_FALSE(encoded.compare("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "-._~")); +} + +TEST_CASE("uriEncode(): encode reserved chars in a name which is not object name", "[AWS][auth][utility]") +{ + const String in = " /!\"#$%&'()*+,:;<=>?@[\\]^`{|}"; /* some printable but reserved chars */ + String encoded = uriEncode(in, /* isObjectName */ false); + + CHECK(3 * in.length() == encoded.length()); /* size of "%NN" = 3 */ + CHECK_FALSE(encoded.compare("%20%2F%21%22%23%24%25%26%27%28%29%2A%2B%2C%3A%3B%3C%3D%3E%3F%40%5B%5C%5D%5E%60%7B%7C%7D")); +} + +TEST_CASE("uriEncode(): encode reserved chars in an object name", "[AWS][auth][utility]") +{ + const String in = " /!\"#$%&'()*+,:;<=>?@[\\]^`{|}"; /* some printable but reserved chars */ + String encoded = uriEncode(in, /* isObjectName */ true); + + CHECK(3 * in.length() - 2 == encoded.length()); /* size of "%NN" = 3, '/' is not encoded */ + CHECK_FALSE(encoded.compare("%20/%21%22%23%24%25%26%27%28%29%2A%2B%2C%3A%3B%3C%3D%3E%3F%40%5B%5C%5D%5E%60%7B%7C%7D")); +} + +/* base16Encode() ************************************************************************************************************** */ + +TEST_CASE("base16Encode(): base16 encode empty string", "[utility]") +{ + const char *in = nullptr; + size_t inLen = 0; + String encoded = base16Encode(in, inLen); + + CHECK(0 == encoded.length()); +} + +TEST_CASE("base16Encode(): base16 encode RFC4648 test vectors", "[utility]") +{ + /* use the test vectors from RFC4648: https://tools.ietf.org/html/rfc4648#section-10 (just convert to lower case) */ + const char *bench[] = {"", "", "f", "66", "fo", "666f", "foo", + "666f6f", "foob", "666f6f62", "fooba", "666f6f6261", "foobar", "666f6f626172"}; + + for (size_t i = 0; i < sizeof(bench) / sizeof(char *); i += 2) { + const char *in = bench[i]; + size_t inLen = strlen(in); + String encoded = base16Encode(in, inLen); + + CHECK(inLen * 2 == encoded.length()); + CHECK_FALSE(encoded.compare(bench[i + 1])); + } +} + +/* trimWhiteSpaces() ******************************************************************************************************** */ + +TEST_CASE("trimWhiteSpaces(): trim invalid arguments, check pointers", "[utility]") +{ + const char *in = nullptr; + size_t inLen = 0; + size_t outLen = 0; + + const char *start = trimWhiteSpaces(in, inLen, outLen); + + CHECK(in == start); +} + +TEST_CASE("trimWhiteSpaces(): trim empty input, check pointers", "[utility]") +{ + const char *in = ""; + size_t inLen = 0; + size_t outLen = 0; + + const char *start = trimWhiteSpaces(in, inLen, outLen); + + CHECK(in == start); +} + +TEST_CASE("trimWhiteSpaces(): trim nothing to trim, check pointers", "[utility]") +{ + const char in[] = "Important Message"; + size_t inLen = strlen(in); + size_t newLen = 0; + + const char *start = trimWhiteSpaces(in, inLen, newLen); + + CHECK(in == start); + CHECK(inLen == newLen); +} + +TEST_CASE("trimWhiteSpaces(): trim beginning, check pointers", "[utility]") +{ + const char in[] = " \t\nImportant Message"; + size_t inLen = strlen(in); + size_t newLen = 0; + + const char *start = trimWhiteSpaces(in, inLen, newLen); + + CHECK(in + 3 == start); + CHECK(inLen - 3 == newLen); +} + +TEST_CASE("trimWhiteSpaces(): trim end, check pointers", "[utility]") +{ + const char in[] = "Important Message \t\n"; + size_t inLen = strlen(in); + size_t newLen = 0; + + const char *start = trimWhiteSpaces(in, inLen, newLen); + + CHECK(in == start); + CHECK(inLen - 3 == newLen); +} + +TEST_CASE("trimWhiteSpaces(): trim both ends, check pointers", "[utility]") +{ + const char in[] = "\v\t\n Important Message \t\n"; + size_t inLen = strlen(in); + size_t newLen = 0; + + const char *start = trimWhiteSpaces(in, inLen, newLen); + + CHECK(in + 4 == start); + CHECK(inLen - 7 == newLen); +} + +TEST_CASE("trimWhiteSpaces(): trim both, check string", "[utility]") +{ + String in = "\v\t\n Important Message \t\n"; + String trimmed = trimWhiteSpaces(in); + + CHECK_FALSE(trimmed.compare("Important Message")); + CHECK(in.length() - 7 == trimmed.length()); +} + +TEST_CASE("trimWhiteSpaces(): trim right, check string", "[utility]") +{ + String in = "Important Message \t\n"; + String trimmed = trimWhiteSpaces(in); + + CHECK_FALSE(trimmed.compare("Important Message")); + CHECK(in.length() - 3 == trimmed.length()); +} + +TEST_CASE("trimWhiteSpaces(): trim left, check string", "[utility]") +{ + String in = "\v\t\n Important Message"; + String trimmed = trimWhiteSpaces(in); + + CHECK_FALSE(trimmed.compare("Important Message")); + CHECK(in.length() - 4 == trimmed.length()); +} + +TEST_CASE("trimWhiteSpaces(): trim empty, check string", "[utility]") +{ + String in = "\v\t\n \t\n"; + String trimmed = trimWhiteSpaces(in); + + CHECK(trimmed.empty()); + CHECK(0 == trimmed.length()); +} + +/* AWS Regions ***************************************************************************************************** */ + +TEST_CASE("AWSRegions: get region empty input", "[AWS][auth][utility]") +{ + const char *host = ""; + String s = getRegion(defaultDefaultRegionMap, host, strlen(host)); + CHECK_FALSE(s.compare("us-east-1")); +} + +TEST_CASE("AWSRegions: get region by providing no bucket name", "[AWS][auth][utility]") +{ + const char *host = "s3.eu-west-2.amazonaws.com"; + String s = getRegion(defaultDefaultRegionMap, host, strlen(host)); + CHECK_FALSE(s.compare("eu-west-2")); +} + +TEST_CASE("AWSRegions: get region by providing bucket name having single label", "[AWS][auth][utility]") +{ + const char *host = "label1.label2.s3.eu-west-2.amazonaws.com"; + String s = getRegion(defaultDefaultRegionMap, host, strlen(host)); + CHECK_FALSE(s.compare("eu-west-2")); +} + +TEST_CASE("AWSRegions: get region by providing bucket name having multiple labels", "[AWS][auth][utility]") +{ + const char *host = "label1.label2.s3.eu-west-2.amazonaws.com"; + String s = getRegion(defaultDefaultRegionMap, host, strlen(host)); + CHECK_FALSE(s.compare("eu-west-2")); +} + +TEST_CASE("AWSRegions: get region by providing bucket name having single label not matching any entry point", + "[AWS][auth][utility]") +{ + const char *host = "THIS_NEVER_MATCHES.eu-west-2.amazonaws.com"; + String s = getRegion(defaultDefaultRegionMap, host, strlen(host)); + CHECK_FALSE(s.compare("us-east-1")); +} + +TEST_CASE("AWSRegions: get region by providing bucket name having multiple labels not matching any entry point", + "[AWS][auth][utility]") +{ + const char *host = "label1.label2.THIS_NEVER_MATCHES.eu-west-2.amazonaws.com"; + String s = getRegion(defaultDefaultRegionMap, host, strlen(host)); + CHECK_FALSE(s.compare("us-east-1")); +} + +/* AWS spec tests/example ****************************************************************************************** */ + +/* Test from docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html + * User id, secret and time */ +const char *awsSecretAccessKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"; +const char *awsAccessKeyId = "AKIAIOSFODNN7EXAMPLE"; +const char *awsService = "s3"; + +void +ValidateBench(TsInterface &api, bool signPayload, time_t *now, const char *bench[], const StringSet &includedHeaders, + const StringSet &excludedHeaders) +{ + /* Test the main entry point for calculation of the Authorization header content */ + AwsAuthV4 util(api, now, signPayload, awsAccessKeyId, strlen(awsAccessKeyId), awsSecretAccessKey, strlen(awsSecretAccessKey), + awsService, strlen(awsService), includedHeaders, excludedHeaders, defaultDefaultRegionMap); + String authorizationHeader = util.getAuthorizationHeader(); + CHECK_FALSE(authorizationHeader.compare(bench[0])); + + /* Test payload hash */ + String payloadHash = util.getPayloadHash(); + CHECK_FALSE(payloadHash.compare(bench[5])); + + /* Test the date time header content */ + size_t dateLen = 0; + const char *date = util.getDateTime(&dateLen); + CHECK_FALSE(String(date, dateLen).compare(bench[2])); + + /* Now test particular test points to pinpoint problems easier in case of regression */ + + /* test the canonization of the request */ + String signedHeaders; + String canonicalReq = getCanonicalRequestSha256Hash(api, signPayload, includedHeaders, excludedHeaders, signedHeaders); + CHECK_FALSE(canonicalReq.compare(bench[1])); + CHECK_FALSE(signedHeaders.compare(bench[6])); + + /* Test the formating of the date and time */ + char dateTime[sizeof("20170428T010203Z")]; + size_t dateTimeLen = getIso8601Time(now, dateTime, sizeof(dateTime)); + CHECK_FALSE(String(dateTime, dateTimeLen).compare(bench[2])); + + /* Test the region name */ + int hostLen = 0; + const char *host = api.getHost(&hostLen); + String awsRegion = getRegion(defaultDefaultRegionMap, host, hostLen); + + /* Test string to sign calculation */ + String stringToSign = getStringToSign(host, hostLen, dateTime, dateTimeLen, awsRegion.c_str(), awsRegion.length(), awsService, + strlen(awsService), canonicalReq.c_str(), canonicalReq.length()); + CHECK_FALSE(stringToSign.compare(bench[3])); + + /* Test the signature calculation */ + char signature[EVP_MAX_MD_SIZE]; + size_t signatureLen = + getSignature(awsSecretAccessKey, strlen(awsSecretAccessKey), awsRegion.c_str(), awsRegion.length(), awsService, + strlen(awsService), dateTime, 8, stringToSign.c_str(), stringToSign.length(), signature, EVP_MAX_MD_SIZE); + String base16Signature = base16Encode(signature, signatureLen); + CHECK_FALSE(base16Signature.compare(bench[4])); +} + +/** + * Test from docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html + * Example: GET Object + */ +TEST_CASE("AWSAuthSpecByExample: GET Object", "[AWS][auth][SpecByExample]") +{ + time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */ + + /* Define the HTTP request elements */ + MockTsInterface api; + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign("test.txt"); + api._query.assign(""); + api._headers["Host"] = "examplebucket.s3.amazonaws.com"; + api._headers["Range"] = "bytes=0-9"; + api._headers["x-amz-content-sha256"] = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + api._headers["x-amz-date"] = "20130524T000000Z"; + + const char *bench[] = { + /* Authorization Header */ + "AWS4-HMAC-SHA256 " + "Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request," + "SignedHeaders=host;range;x-amz-content-sha256;x-amz-date," + "Signature=f0e8bdb87c964420e857bd35b5d6ed310bd44f0170aba48dd91039c6036bdb41", + /* Canonical Request sha256 */ + "7344ae5b7ee6c3e7e6b0fe0640412a37625d1fbfff95c48bbb2dc43964946972", + /* Date and time*/ + "20130524T000000Z", + /* String to sign */ + "AWS4-HMAC-SHA256\n" + "20130524T000000Z\n" + "20130524/us-east-1/s3/aws4_request\n" + "7344ae5b7ee6c3e7e6b0fe0640412a37625d1fbfff95c48bbb2dc43964946972", + /* Signature */ + "f0e8bdb87c964420e857bd35b5d6ed310bd44f0170aba48dd91039c6036bdb41", + /* Payload hash */ + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + /* Signed Headers */ + "host;range;x-amz-content-sha256;x-amz-date", + }; + + ValidateBench(api, /*signePayload */ true, &now, bench, defaultIncludeHeaders, defaultExcludeHeaders); +} + +/** + * Test from docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html + * Example: GET Bucket Lifecycle + */ +TEST_CASE("AWSAuthSpecByExample: GET Bucket Lifecycle", "[AWS][auth][SpecByExample]") +{ + time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */ + + /* Define the HTTP request elements */ + MockTsInterface api; + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); + api._query.assign("lifecycle"); + api._headers["Host"] = "examplebucket.s3.amazonaws.com"; + api._headers["x-amz-content-sha256"] = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + api._headers["x-amz-date"] = "20130524T000000Z"; + + const char *bench[] = { + /* Authorization Header */ + "AWS4-HMAC-SHA256 " + "Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request," + "SignedHeaders=host;x-amz-content-sha256;x-amz-date," + "Signature=fea454ca298b7da1c68078a5d1bdbfbbe0d65c699e0f91ac7a200a0136783543", + /* Canonical Request sha256 */ + "9766c798316ff2757b517bc739a67f6213b4ab36dd5da2f94eaebf79c77395ca", + /* Date and time*/ + "20130524T000000Z", + /* String to sign */ + "AWS4-HMAC-SHA256\n" + "20130524T000000Z\n" + "20130524/us-east-1/s3/aws4_request\n" + "9766c798316ff2757b517bc739a67f6213b4ab36dd5da2f94eaebf79c77395ca", + /* Signature */ + "fea454ca298b7da1c68078a5d1bdbfbbe0d65c699e0f91ac7a200a0136783543", + /* Payload hash */ + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + /* Signed Headers */ + "host;x-amz-content-sha256;x-amz-date", + }; + + ValidateBench(api, /*signePayload */ true, &now, bench, defaultIncludeHeaders, defaultExcludeHeaders); +} + +/** + * Test from docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html + * Example: Get Bucket (List Objects) + */ +TEST_CASE("AWSAuthSpecByExample: Get Bucket List Objects", "[AWS][auth][SpecByExample]") +{ + time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */ + + /* Define the HTTP request elements */ + MockTsInterface api; + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers["Host"] = "examplebucket.s3.amazonaws.com"; + api._headers["x-amz-content-sha256"] = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + api._headers["x-amz-date"] = "20130524T000000Z"; + + const char *bench[] = { + /* Authorization Header */ + "AWS4-HMAC-SHA256 " + "Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request," + "SignedHeaders=host;x-amz-content-sha256;x-amz-date," + "Signature=34b48302e7b5fa45bde8084f4b7868a86f0a534bc59db6670ed5711ef69dc6f7", + /* Canonical Request sha256 */ + "df57d21db20da04d7fa30298dd4488ba3a2b47ca3a489c74750e0f1e7df1b9b7", + /* Date and time*/ + "20130524T000000Z", + /* String to sign */ + "AWS4-HMAC-SHA256\n" + "20130524T000000Z\n" + "20130524/us-east-1/s3/aws4_request\n" + "df57d21db20da04d7fa30298dd4488ba3a2b47ca3a489c74750e0f1e7df1b9b7", + /* Signature */ + "34b48302e7b5fa45bde8084f4b7868a86f0a534bc59db6670ed5711ef69dc6f7", + /* Payload hash */ + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + /* Signed Headers */ + "host;x-amz-content-sha256;x-amz-date", + }; + + ValidateBench(api, /*signePayload */ true, &now, bench, defaultIncludeHeaders, defaultExcludeHeaders); +} + +/** + * Test based on docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html + * but this time don't sign the payload to test "UNSIGNED-PAYLOAD" feature. + * Example: Get Bucket (List Objects) + */ +TEST_CASE("AWSAuthSpecByExample: GET Bucket List Objects, unsigned pay-load", "[AWS][auth][SpecByExample]") +{ + time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */ + + /* Define the HTTP request elements */ + MockTsInterface api; + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers["Host"] = "examplebucket.s3.amazonaws.com"; + api._headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD"; + api._headers["x-amz-date"] = "20130524T000000Z"; + + const char *bench[] = { + /* Authorization Header */ + "AWS4-HMAC-SHA256 " + "Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request," + "SignedHeaders=host;x-amz-content-sha256;x-amz-date," + "Signature=b1a076428fa68c2c42202ee5a5718b8207f725e451e2157d6b1c393e01fc2e68", + /* Canonical Request sha256 */ + "528623330c85041d6fb82795b6f8d5771825d3568b9f0bc1faa8a49e1f5f9cfc", + /* Date and time*/ + "20130524T000000Z", + /* String to sign */ + "AWS4-HMAC-SHA256\n" + "20130524T000000Z\n" + "20130524/us-east-1/s3/aws4_request\n" + "528623330c85041d6fb82795b6f8d5771825d3568b9f0bc1faa8a49e1f5f9cfc", + /* Signature */ + "b1a076428fa68c2c42202ee5a5718b8207f725e451e2157d6b1c393e01fc2e68", + /* Payload hash */ + "UNSIGNED-PAYLOAD", + /* Signed Headers */ + "host;x-amz-content-sha256;x-amz-date", + }; + + ValidateBench(api, /*signePayload */ false, &now, bench, defaultIncludeHeaders, defaultExcludeHeaders); +} + +/** + * Test based on docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html + * but this time don't sign the payload to test "UNSIGNED-PAYLOAD" feature and + * have extra headers to be excluded from the signature (internal and changing headers) + * Example: Get Bucket (List Objects) + */ +TEST_CASE("AWSAuthSpecByExample: GET Bucket List Objects, unsigned pay-load, exclude internal and changing headers", "[AWS][auth]") +{ + time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */ + + /* Define the HTTP request elements */ + MockTsInterface api; + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers["Host"] = "examplebucket.s3.amazonaws.com"; + api._headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD"; + api._headers["x-amz-date"] = "20130524T000000Z"; + api._headers["@internal"] = "internal value"; + api._headers["x-forwarded-for"] = "192.168.7.1"; + api._headers["via"] = "http/1.1 tcp ipv4 ats_dev[7e67ac60-c204-450d-90be-a426dd3b569f] (ApacheTrafficServer/7.2.0)"; + + const char *bench[] = { + /* Authorization Header */ + "AWS4-HMAC-SHA256 " + "Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request," + "SignedHeaders=host;x-amz-content-sha256;x-amz-date," + "Signature=b1a076428fa68c2c42202ee5a5718b8207f725e451e2157d6b1c393e01fc2e68", + /* Canonical Request sha256 */ + "528623330c85041d6fb82795b6f8d5771825d3568b9f0bc1faa8a49e1f5f9cfc", + /* Date and time*/ + "20130524T000000Z", + /* String to sign */ + "AWS4-HMAC-SHA256\n" + "20130524T000000Z\n" + "20130524/us-east-1/s3/aws4_request\n" + "528623330c85041d6fb82795b6f8d5771825d3568b9f0bc1faa8a49e1f5f9cfc", + /* Signature */ + "b1a076428fa68c2c42202ee5a5718b8207f725e451e2157d6b1c393e01fc2e68", + /* Payload hash */ + "UNSIGNED-PAYLOAD", + /* Signed Headers */ + "host;x-amz-content-sha256;x-amz-date", + }; + + ValidateBench(api, /*signePayload */ false, &now, bench, defaultIncludeHeaders, defaultExcludeHeaders); +} + +/* Utility parameters related tests ******************************************************************************** */ + +void +ValidateBenchCanonicalRequest(TsInterface &api, bool signPayload, time_t *now, const char *bench[], + const StringSet &includedHeaders, const StringSet &excludedHeaders) +{ + /* Test the main entry point for calculation of the Authorization header content */ + AwsAuthV4 util(api, now, signPayload, awsAccessKeyId, strlen(awsAccessKeyId), awsSecretAccessKey, strlen(awsSecretAccessKey), + awsService, strlen(awsService), includedHeaders, excludedHeaders, defaultDefaultRegionMap); + + /* test the canonization of the request */ + String signedHeaders; + String canonicalReq = getCanonicalRequestSha256Hash(api, signPayload, includedHeaders, excludedHeaders, signedHeaders); + CHECK_FALSE(signedHeaders.compare(bench[0])); + CHECK_FALSE(canonicalReq.compare(bench[1])); +} + +TEST_CASE("S3AuthV4UtilParams: include all headers by default", "[AWS][auth][utility]") +{ + time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */ + + /* Define the HTTP request elements */ + MockTsInterface api; + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers["Host"] = "examplebucket.s3.amazonaws.com"; + api._headers["Content-Type"] = "gzip"; + api._headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD"; + api._headers["x-amz-date"] = "20130524T000000Z"; + api._headers["HeaderA"] = "HeaderAValue"; + api._headers["HeaderB"] = "HeaderBValue"; + api._headers["HeaderC"] = "HeaderCValue"; + api._headers["HeaderD"] = "HeaderDValue"; + api._headers["HeaderE"] = "HeaderEValue"; + api._headers["HeaderF"] = "HeaderFValue"; + + StringSet include = defaultIncludeHeaders; + StringSet exclude = defaultExcludeHeaders; + + const char *bench[] = { + /* Signed Headers */ + "content-type;headera;headerb;headerc;headerd;headere;headerf;host;x-amz-content-sha256;x-amz-date", + /* Canonical Request sha256 */ + "819a275bbd601fd6f6ba39190ee8299d34fcb0f5e0a4c0d8017c35e79a026579", + }; + + ValidateBenchCanonicalRequest(api, /*signePayload */ false, &now, bench, include, exclude); +} + +TEST_CASE("S3AuthV4UtilParams: include all headers explicit", "[AWS][auth][SpecByExample]") +{ + time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */ + + /* Define the HTTP request elements */ + MockTsInterface api; + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers["Host"] = "examplebucket.s3.amazonaws.com"; + api._headers["Content-Type"] = "gzip"; + api._headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD"; + api._headers["x-amz-date"] = "20130524T000000Z"; + api._headers["HeaderA"] = "HeaderAValue"; + api._headers["HeaderB"] = "HeaderBValue"; + api._headers["HeaderC"] = "HeaderCValue"; + api._headers["HeaderD"] = "HeaderDValue"; + api._headers["HeaderE"] = "HeaderEValue"; + api._headers["HeaderF"] = "HeaderFValue"; + + StringSet include; + commaSeparateString<StringSet>(include, "HeaderA,HeaderB,HeaderC,HeaderD,HeaderE,HeaderF"); + StringSet exclude = defaultExcludeHeaders; + + const char *bench[] = { + /* Signed Headers */ + "content-type;headera;headerb;headerc;headerd;headere;headerf;host;x-amz-content-sha256;x-amz-date", + /* Canonical Request sha256 */ + "819a275bbd601fd6f6ba39190ee8299d34fcb0f5e0a4c0d8017c35e79a026579", + }; + + ValidateBenchCanonicalRequest(api, /*signePayload */ false, &now, bench, include, exclude); +} + +TEST_CASE("S3AuthV4UtilParams: exclude all headers explicit", "[AWS][auth][utility]") +{ + time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */ + + /* Define the HTTP request elements */ + MockTsInterface api; + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers["Host"] = "examplebucket.s3.amazonaws.com"; + api._headers["Content-Type"] = "gzip"; + api._headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD"; + api._headers["x-amz-date"] = "20130524T000000Z"; + api._headers["HeaderA"] = "HeaderAValue"; + api._headers["HeaderB"] = "HeaderBValue"; + api._headers["HeaderC"] = "HeaderCValue"; + api._headers["HeaderD"] = "HeaderDValue"; + api._headers["HeaderE"] = "HeaderEValue"; + api._headers["HeaderF"] = "HeaderFValue"; + + StringSet include = defaultIncludeHeaders; + StringSet exclude; + commaSeparateString<StringSet>(exclude, "HeaderA,HeaderB,HeaderC,HeaderD,HeaderE,HeaderF"); + + const char *bench[] = { + /* Signed Headers */ + "content-type;host;x-amz-content-sha256;x-amz-date", + /* Canonical Request sha256 */ + "ef3088997c69bc860e0bb36f97a8335f38863339e7fd01f2cd17b5391da575fb", + }; + + ValidateBenchCanonicalRequest(api, /*signePayload */ false, &now, bench, include, exclude); +} + +TEST_CASE("S3AuthV4UtilParams: include/exclude non overlapping headers", "[AWS][auth][utility]") +{ + time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */ + + /* Define the HTTP request elements */ + MockTsInterface api; + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers["Host"] = "examplebucket.s3.amazonaws.com"; + api._headers["Content-Type"] = "gzip"; + api._headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD"; + api._headers["x-amz-date"] = "20130524T000000Z"; + api._headers["HeaderA"] = "HeaderAValue"; + api._headers["HeaderB"] = "HeaderBValue"; + api._headers["HeaderC"] = "HeaderCValue"; + api._headers["HeaderD"] = "HeaderDValue"; + api._headers["HeaderE"] = "HeaderEValue"; + api._headers["HeaderF"] = "HeaderFValue"; + + StringSet include, exclude; + commaSeparateString<StringSet>(include, "HeaderA,HeaderB,HeaderC"); + commaSeparateString<StringSet>(exclude, "HeaderD,HeaderE,HeaderF"); + + const char *bench[] = { + /* Signed Headers */ + "content-type;headera;headerb;headerc;host;x-amz-content-sha256;x-amz-date", + /* Canonical Request sha256 */ + "c1c7fb808eefdb712192efeed168fdecef0f8d95e8df5a2569d127068c425209", + }; + + ValidateBenchCanonicalRequest(api, /*signePayload */ false, &now, bench, include, exclude); +} + +TEST_CASE("S3AuthV4UtilParams: include/exclude overlapping headers", "[AWS][auth][utility]") +{ + time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */ + + /* Define the HTTP request elements */ + MockTsInterface api; + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers["Host"] = "examplebucket.s3.amazonaws.com"; + api._headers["Content-Type"] = "gzip"; + api._headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD"; + api._headers["x-amz-date"] = "20130524T000000Z"; + api._headers["HeaderA"] = "HeaderAValue"; + api._headers["HeaderB"] = "HeaderBValue"; + api._headers["HeaderC"] = "HeaderCValue"; + api._headers["HeaderD"] = "HeaderDValue"; + api._headers["HeaderE"] = "HeaderEValue"; + api._headers["HeaderF"] = "HeaderFValue"; + + StringSet include, exclude; + commaSeparateString<StringSet>(include, "HeaderA,HeaderB,HeaderC"); + commaSeparateString<StringSet>(exclude, "HeaderC,HeaderD,HeaderE,HeaderF"); + + const char *bench[] = { + /* Signed Headers */ + "content-type;headera;headerb;host;x-amz-content-sha256;x-amz-date", + /* Canonical Request sha256 */ + "0ac0bd67e304b3c25ec51f01b86c824f7439cdb0a5bc16acdebab73f34e12a57", + }; + + ValidateBenchCanonicalRequest(api, /*signePayload */ false, &now, bench, include, exclude); +} + +TEST_CASE("S3AuthV4UtilParams: include/exclude overlapping headers missing include", "[AWS][auth][utility]") +{ + time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */ + + /* Define the HTTP request elements */ + MockTsInterface api; + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers["Host"] = "examplebucket.s3.amazonaws.com"; + api._headers["Content-Type"] = "gzip"; + api._headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD"; + api._headers["x-amz-date"] = "20130524T000000Z"; + api._headers["HeaderA"] = "HeaderAValue"; + api._headers["HeaderB"] = "HeaderBValue"; + api._headers["HeaderC"] = "HeaderCValue"; + api._headers["HeaderD"] = "HeaderDValue"; + api._headers["HeaderE"] = "HeaderEValue"; + api._headers["HeaderF"] = "HeaderFValue"; + + StringSet include, exclude; + commaSeparateString<StringSet>(include, "HeaderA,HeaderC"); + commaSeparateString<StringSet>(exclude, "HeaderC,HeaderD,HeaderE,HeaderF"); + + const char *bench[] = { + /* Signed Headers */ + "content-type;headera;host;x-amz-content-sha256;x-amz-date", + /* Canonical Request sha256 */ + "5b5bef63c923fed685230feb91d8059fe8d56c80d21ba6922ee335ff3fcc45bf", + }; + + ValidateBenchCanonicalRequest(api, /*signePayload */ false, &now, bench, include, exclude); +} + +TEST_CASE("S3AuthV4UtilParams: include/exclude overlapping headers missing exclude", "[AWS][auth][utility]") +{ + time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */ + + /* Define the HTTP request elements */ + MockTsInterface api; + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers["Host"] = "examplebucket.s3.amazonaws.com"; + api._headers["Content-Type"] = "gzip"; + api._headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD"; + api._headers["x-amz-date"] = "20130524T000000Z"; + api._headers["HeaderA"] = "HeaderAValue"; + api._headers["HeaderB"] = "HeaderBValue"; + api._headers["HeaderC"] = "HeaderCValue"; + api._headers["HeaderD"] = "HeaderDValue"; + api._headers["HeaderE"] = "HeaderEValue"; + api._headers["HeaderF"] = "HeaderFValue"; + + StringSet include, exclude; + commaSeparateString<StringSet>(include, "HeaderA,HeaderB,HeaderC"); + commaSeparateString<StringSet>(exclude, "HeaderC,HeaderD,HeaderF"); + + const char *bench[] = { + /* Signed Headers */ + "content-type;headera;headerb;host;x-amz-content-sha256;x-amz-date", + /* Canonical Request sha256 */ + "0ac0bd67e304b3c25ec51f01b86c824f7439cdb0a5bc16acdebab73f34e12a57", + }; + + ValidateBenchCanonicalRequest(api, /*signePayload */ false, &now, bench, include, exclude); +} + +/* + * Mandatory headers Host, x-amz-* and Content-Type will must be included even if the user asked to exclude them. + */ +TEST_CASE("S3AuthV4UtilParams: include content type", "[AWS][auth][utility]") +{ + time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */ + + /* Define the HTTP request elements */ + MockTsInterface api; + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers["Host"] = "examplebucket.s3.amazonaws.com"; + api._headers["Content-Type"] = "gzip"; + api._headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD"; + api._headers["x-amz-date"] = "20130524T000000Z"; + + StringSet include = defaultIncludeHeaders; + StringSet exclude; + commaSeparateString<StringSet>(exclude, "Content-Type,x-amz-content-sha256,x-amz-date"); + + const char *bench[] = { + /* Signed Headers */ + "content-type;host;x-amz-content-sha256;x-amz-date", + /* Canonical Request sha256 */ + "ef3088997c69bc860e0bb36f97a8335f38863339e7fd01f2cd17b5391da575fb", + }; + + ValidateBenchCanonicalRequest(api, /*signePayload */ false, &now, bench, include, exclude); +} + +/* + * Mandatory headers Host, x-amz-* and Content-Type will must be included even if the user asked to exclude them. + * Content-type should not be signed if missing from the HTTP request. + */ +TEST_CASE("S3AuthV4UtilParams: include missing content type", "[AWS][auth][utility]") +{ + time_t now = 1369353600; /* 5/24/2013 00:00:00 GMT */ + + /* Define the HTTP request elements */ + MockTsInterface api; + api._method.assign("GET"); + api._host.assign("examplebucket.s3.amazonaws.com"); + api._path.assign(""); + api._query.assign("max-keys=2&prefix=J"); + api._headers["Host"] = "examplebucket.s3.amazonaws.com"; + api._headers["x-amz-content-sha256"] = "UNSIGNED-PAYLOAD"; + api._headers["x-amz-date"] = "20130524T000000Z"; + + StringSet include = defaultIncludeHeaders; + StringSet exclude; + commaSeparateString<StringSet>(exclude, "Content-Type,x-amz-content-sha256,x-amz-date"); + + const char *bench[] = { + /* Signed Headers */ + "host;x-amz-content-sha256;x-amz-date", + /* Canonical Request sha256 */ + "528623330c85041d6fb82795b6f8d5771825d3568b9f0bc1faa8a49e1f5f9cfc", + }; + + ValidateBenchCanonicalRequest(api, /*signePayload */ false, &now, bench, include, exclude); +} diff --git a/plugins/s3_auth/unit-tests/test_aws_auth_v4.h b/plugins/s3_auth/unit-tests/test_aws_auth_v4.h new file mode 100644 index 0000000..e7889c5 --- /dev/null +++ b/plugins/s3_auth/unit-tests/test_aws_auth_v4.h @@ -0,0 +1,145 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +/** + * @file test_aws_auth_v4.h + * @brief TS API mock and and mock header iterator used for unit testing. + * @see test_aws_auth_v4.cc + */ + +#ifndef PLUGINS_S3_AUTH_UNIT_TESTS_TEST_AWS_AUTH_V4_H_ +#define PLUGINS_S3_AUTH_UNIT_TESTS_TEST_AWS_AUTH_V4_H_ + +#include <string> /* std::string */ + +/* Define a header iterator to be used in unit tests */ +class HeaderIterator +{ +public: + HeaderIterator(const StringMap::iterator &it) { _it = it; } + HeaderIterator(const HeaderIterator &i) { _it = i._it; } + ~HeaderIterator() {} + HeaderIterator & + operator=(HeaderIterator &i) + { + _it = i._it; + return *this; + } + HeaderIterator &operator++() + { + _it++; + return *this; + } + HeaderIterator operator++(int) + { + HeaderIterator tmp(*this); + operator++(); + return tmp; + } + bool + operator!=(const HeaderIterator &it) + { + return _it != it._it; + } + const char * + getName(int *len) + { + *len = _it->first.length(); + return _it->first.c_str(); + } + const char * + getValue(int *len) + { + *len = _it->second.length(); + return _it->second.c_str(); + } + StringMap::iterator _it; +}; + +/* Define a mock API to be used in unit-tests */ +class MockTsInterface : public TsInterface +{ +public: + const char * + getMethod(int *length) + { + *length = _method.length(); + return _method.c_str(); + } + const char * + getHost(int *length) + { + *length = _host.length(); + return _host.c_str(); + } + const char * + getPath(int *length) + { + *length = _path.length(); + return _path.c_str(); + } + const char * + getQuery(int *length) + { + *length = _query.length(); + return _query.c_str(); + } + HeaderIterator + headerBegin() + { + return HeaderIterator(_headers.begin()); + } + HeaderIterator + headerEnd() + { + return HeaderIterator(_headers.end()); + } + + String _method; + String _host; + String _path; + String _query; + StringMap _headers; +}; + +/* Expose the following methods only to the unit tests */ +String base16Encode(const char *in, size_t inLen); +String uriEncode(const String in, bool isObjectName = false); +String lowercase(const char *in, size_t inLen); +const char *trimWhiteSpaces(const char *in, size_t inLen, size_t &newLen); + +String getCanonicalRequestSha256Hash(TsInterface &api, bool signPayload, const StringSet &includeHeaders, + const StringSet &excludeHeaders, String &signedHeaders); +String getStringToSign(TsInterface &api, const char *dateTime, size_t dateTimeLen, const char *canonicalRequestSha256Hash, + size_t canonicalRequestSha256HashLen); +String getStringToSign(const char *host, size_t hostLen, const char *dateTime, size_t dateTimeLen, const char *region, + size_t regionLen, const char *service, size_t serviceLen, const char *canonicalRequestSha256Hash, + size_t canonicalRequestSha256HashLen); +String getRegion(const StringMap ®ionMap, const char *host, size_t hostLen); +size_t hmacSha256(const char *secret, size_t secretLen, const char *msg, size_t msgLen, char *hmac, size_t hmacLen); + +size_t getSignature(const char *awsSecret, size_t awsSecretLen, const char *awsRegion, size_t awsRegionLen, const char *awsService, + size_t awsServiceLen, const char *dateTime, size_t dateTimeLen, const char *stringToSign, + size_t stringToSignLen, char *base16Signature, size_t base16SignatureLen); +size_t getIso8601Time(time_t *now, char *dateTime, size_t dateTimeLen); + +extern const StringMap defaultDefaultRegionMap; +extern const StringSet defaultExcludeHeaders; +extern const StringSet defaultIncludeHeaders; + +#endif /* PLUGINS_S3_AUTH_UNIT_TESTS_TEST_AWS_AUTH_V4_H_ */ -- To stop receiving notification emails like this one, please contact "commits@trafficserver.apache.org" <commits@trafficserver.apache.org>.