trafficserver-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From gan...@apache.org
Subject [trafficserver] branch master updated: Unit tests for AWS Signature Version 4
Date Tue, 29 Aug 2017 17:56:09 GMT
This is an automated email from the ASF dual-hosted git repository.

gancho pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 3bcc7ce  Unit tests for AWS Signature Version 4
3bcc7ce is described below

commit 3bcc7ce051c391b1833fbb4f9343b45a96ae19ff
Author: Gancho Tenev <gttenev@gmail.com>
AuthorDate: Tue Apr 25 13:02:44 2017 -0700

    Unit tests for AWS Signature Version 4
---
 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 &regionMap);
-  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 &regionMap, 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>'].

Mime
View raw message