github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/signature-v4-parser_test.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package cmd
    19  
    20  import (
    21  	"net/url"
    22  	"strconv"
    23  	"strings"
    24  	"testing"
    25  	"time"
    26  )
    27  
    28  // generates credential string from its fields.
    29  func generateCredentialStr(accessKey, date, region, service, requestVersion string) string {
    30  	return "Credential=" + joinWithSlash(accessKey, date, region, service, requestVersion)
    31  }
    32  
    33  // joins the argument strings with a '/' and returns it.
    34  func joinWithSlash(accessKey, date, region, service, requestVersion string) string {
    35  	return strings.Join([]string{
    36  		accessKey,
    37  		date,
    38  		region,
    39  		service,
    40  		requestVersion,
    41  	}, SlashSeparator)
    42  }
    43  
    44  // generate CredentialHeader from its fields.
    45  func generateCredentials(t *testing.T, accessKey string, date string, region, service, requestVersion string) credentialHeader {
    46  	cred := credentialHeader{
    47  		accessKey: accessKey,
    48  	}
    49  	parsedDate, err := time.Parse(yyyymmdd, date)
    50  	if err != nil {
    51  		t.Fatalf("Failed to parse date")
    52  	}
    53  	cred.scope.date = parsedDate
    54  	cred.scope.region = region
    55  	cred.scope.service = service
    56  	cred.scope.request = requestVersion
    57  
    58  	return cred
    59  }
    60  
    61  // validates the credential fields against the expected credential.
    62  func validateCredentialfields(t *testing.T, testNum int, expectedCredentials credentialHeader, actualCredential credentialHeader) {
    63  	if expectedCredentials.accessKey != actualCredential.accessKey {
    64  		t.Errorf("Test %d: AccessKey mismatch: Expected \"%s\", got \"%s\"", testNum, expectedCredentials.accessKey, actualCredential.accessKey)
    65  	}
    66  	if !expectedCredentials.scope.date.Equal(actualCredential.scope.date) {
    67  		t.Errorf("Test %d: Date mismatch:Expected \"%s\", got \"%s\"", testNum, expectedCredentials.scope.date, actualCredential.scope.date)
    68  	}
    69  	if expectedCredentials.scope.region != actualCredential.scope.region {
    70  		t.Errorf("Test %d: region mismatch:Expected \"%s\", got \"%s\"", testNum, expectedCredentials.scope.region, actualCredential.scope.region)
    71  	}
    72  	if expectedCredentials.scope.service != actualCredential.scope.service {
    73  		t.Errorf("Test %d: service mismatch:Expected \"%s\", got \"%s\"", testNum, expectedCredentials.scope.service, actualCredential.scope.service)
    74  	}
    75  
    76  	if expectedCredentials.scope.request != actualCredential.scope.request {
    77  		t.Errorf("Test %d: scope request mismatch:Expected \"%s\", got \"%s\"", testNum, expectedCredentials.scope.request, actualCredential.scope.request)
    78  	}
    79  }
    80  
    81  // TestParseCredentialHeader - validates the format validator and extractor for the Credential header in an aws v4 request.
    82  // A valid format of credential should be of the following format.
    83  // Credential = accessKey + SlashSeparator+ scope
    84  // where scope = string.Join([]string{  currTime.Format(yyyymmdd),
    85  //
    86  //				globalMinioDefaultRegion,
    87  //	              	"s3",
    88  //			        "aws4_request",
    89  //	                      },SlashSeparator)
    90  func TestParseCredentialHeader(t *testing.T) {
    91  	sampleTimeStr := UTCNow().Format(yyyymmdd)
    92  
    93  	testCases := []struct {
    94  		inputCredentialStr  string
    95  		expectedCredentials credentialHeader
    96  		expectedErrCode     APIErrorCode
    97  	}{
    98  		// Test Case - 1.
    99  		// Test case with no '=' in te inputCredentialStr.
   100  		{
   101  			inputCredentialStr:  "Credential",
   102  			expectedCredentials: credentialHeader{},
   103  			expectedErrCode:     ErrMissingFields,
   104  		},
   105  		// Test Case - 2.
   106  		// Test case with no "Credential" string in te inputCredentialStr.
   107  		{
   108  			inputCredentialStr:  "Cred=",
   109  			expectedCredentials: credentialHeader{},
   110  			expectedErrCode:     ErrMissingCredTag,
   111  		},
   112  		// Test Case - 3.
   113  		// Test case with malformed credentials.
   114  		{
   115  			inputCredentialStr:  "Credential=abc",
   116  			expectedCredentials: credentialHeader{},
   117  			expectedErrCode:     ErrCredMalformed,
   118  		},
   119  		// Test Case - 4.
   120  		// Test case with AccessKey of length 2.
   121  		{
   122  			inputCredentialStr: generateCredentialStr(
   123  				"^#",
   124  				UTCNow().Format(yyyymmdd),
   125  				"ABCD",
   126  				"ABCD",
   127  				"ABCD"),
   128  			expectedCredentials: credentialHeader{},
   129  			expectedErrCode:     ErrInvalidAccessKeyID,
   130  		},
   131  		// Test Case - 5.
   132  		// Test case with invalid date format date.
   133  		// a valid date format for credentials is "yyyymmdd".
   134  		{
   135  			inputCredentialStr: generateCredentialStr(
   136  				"Z7IXGOO6BZ0REAN1Q26I",
   137  				UTCNow().String(),
   138  				"ABCD",
   139  				"ABCD",
   140  				"ABCD"),
   141  			expectedCredentials: credentialHeader{},
   142  			expectedErrCode:     ErrMalformedCredentialDate,
   143  		},
   144  		// Test Case - 6.
   145  		// Test case with invalid service.
   146  		// "s3" is the valid service string.
   147  		{
   148  			inputCredentialStr: generateCredentialStr(
   149  				"Z7IXGOO6BZ0REAN1Q26I",
   150  				UTCNow().Format(yyyymmdd),
   151  				"us-west-1",
   152  				"ABCD",
   153  				"ABCD"),
   154  			expectedCredentials: credentialHeader{},
   155  			expectedErrCode:     ErrInvalidServiceS3,
   156  		},
   157  		// Test Case - 7.
   158  		// Test case with invalid region.
   159  		{
   160  			inputCredentialStr: generateCredentialStr(
   161  				"Z7IXGOO6BZ0REAN1Q26I",
   162  				UTCNow().Format(yyyymmdd),
   163  				"us-west-2",
   164  				"s3",
   165  				"aws4_request"),
   166  			expectedCredentials: credentialHeader{},
   167  			expectedErrCode:     ErrAuthorizationHeaderMalformed,
   168  		},
   169  		// Test Case - 8.
   170  		// Test case with invalid request version.
   171  		// "aws4_request" is the valid request version.
   172  		{
   173  			inputCredentialStr: generateCredentialStr(
   174  				"Z7IXGOO6BZ0REAN1Q26I",
   175  				UTCNow().Format(yyyymmdd),
   176  				"us-west-1",
   177  				"s3",
   178  				"ABCD"),
   179  			expectedCredentials: credentialHeader{},
   180  			expectedErrCode:     ErrInvalidRequestVersion,
   181  		},
   182  		// Test Case - 9.
   183  		// Test case with right inputs. Expected to return a valid CredentialHeader.
   184  		// "aws4_request" is the valid request version.
   185  		{
   186  			inputCredentialStr: generateCredentialStr(
   187  				"Z7IXGOO6BZ0REAN1Q26I",
   188  				sampleTimeStr,
   189  				"us-west-1",
   190  				"s3",
   191  				"aws4_request"),
   192  			expectedCredentials: generateCredentials(
   193  				t,
   194  				"Z7IXGOO6BZ0REAN1Q26I",
   195  				sampleTimeStr,
   196  				"us-west-1",
   197  				"s3",
   198  				"aws4_request"),
   199  			expectedErrCode: ErrNone,
   200  		},
   201  		// Test Case - 10.
   202  		// Test case with right inputs -> AccessKey contains `/`. See minio/#6443
   203  		// "aws4_request" is the valid request version.
   204  		{
   205  			inputCredentialStr: generateCredentialStr(
   206  				"LOCALKEY/DEV/1",
   207  				sampleTimeStr,
   208  				"us-west-1",
   209  				"s3",
   210  				"aws4_request"),
   211  			expectedCredentials: generateCredentials(
   212  				t,
   213  				"LOCALKEY/DEV/1",
   214  				sampleTimeStr,
   215  				"us-west-1",
   216  				"s3",
   217  				"aws4_request"),
   218  			expectedErrCode: ErrNone,
   219  		},
   220  		// Test Case - 11.
   221  		// Test case with right inputs -> AccessKey contains `=`. See minio/#7376
   222  		// "aws4_request" is the valid request version.
   223  		{
   224  			inputCredentialStr: generateCredentialStr(
   225  				"LOCALKEY/DEV/1=",
   226  				sampleTimeStr,
   227  				"us-west-1",
   228  				"s3",
   229  				"aws4_request"),
   230  			expectedCredentials: generateCredentials(
   231  				t,
   232  				"LOCALKEY/DEV/1=",
   233  				sampleTimeStr,
   234  				"us-west-1",
   235  				"s3",
   236  				"aws4_request"),
   237  			expectedErrCode: ErrNone,
   238  		},
   239  	}
   240  
   241  	for i, testCase := range testCases {
   242  		actualCredential, actualErrCode := parseCredentialHeader(testCase.inputCredentialStr, "us-west-1", "s3")
   243  		// validating the credential fields.
   244  		if testCase.expectedErrCode != actualErrCode {
   245  			t.Fatalf("Test %d: Expected the APIErrCode to be %s, got %s", i+1, errorCodes[testCase.expectedErrCode].Code, errorCodes[actualErrCode].Code)
   246  		}
   247  		if actualErrCode == ErrNone {
   248  			validateCredentialfields(t, i+1, testCase.expectedCredentials, actualCredential)
   249  		}
   250  	}
   251  }
   252  
   253  // TestParseSignature - validates the logic for extracting the signature string.
   254  func TestParseSignature(t *testing.T) {
   255  	testCases := []struct {
   256  		inputSignElement string
   257  		expectedSignStr  string
   258  		expectedErrCode  APIErrorCode
   259  	}{
   260  		// Test case - 1.
   261  		// SignElement doesn't have 2 parts on an attempt to split at '='.
   262  		// ErrMissingFields expected.
   263  		{
   264  			inputSignElement: "Signature",
   265  			expectedSignStr:  "",
   266  			expectedErrCode:  ErrMissingFields,
   267  		},
   268  		// Test case - 2.
   269  		// SignElement does have 2 parts but doesn't have valid signature value.
   270  		// ErrMissingFields expected.
   271  		{
   272  			inputSignElement: "Signature=",
   273  			expectedSignStr:  "",
   274  			expectedErrCode:  ErrMissingFields,
   275  		},
   276  		// Test case - 3.
   277  		// SignElement with missing "SignatureTag",ErrMissingSignTag expected.
   278  		{
   279  			inputSignElement: "Sign=",
   280  			expectedSignStr:  "",
   281  			expectedErrCode:  ErrMissingSignTag,
   282  		},
   283  		// Test case - 4.
   284  		// Test case with valid inputs.
   285  		{
   286  			inputSignElement: "Signature=abcd",
   287  			expectedSignStr:  "abcd",
   288  			expectedErrCode:  ErrNone,
   289  		},
   290  	}
   291  	for i, testCase := range testCases {
   292  		actualSignStr, actualErrCode := parseSignature(testCase.inputSignElement)
   293  		if testCase.expectedErrCode != actualErrCode {
   294  			t.Fatalf("Test %d: Expected the APIErrCode to be %d, got %d", i+1, testCase.expectedErrCode, actualErrCode)
   295  		}
   296  		if actualErrCode == ErrNone {
   297  			if testCase.expectedSignStr != actualSignStr {
   298  				t.Errorf("Test %d: Expected the result to be \"%s\", but got \"%s\". ", i+1, testCase.expectedSignStr, actualSignStr)
   299  			}
   300  		}
   301  
   302  	}
   303  }
   304  
   305  // TestParseSignedHeaders - validates the logic for extracting the signature string.
   306  func TestParseSignedHeaders(t *testing.T) {
   307  	testCases := []struct {
   308  		inputSignElement      string
   309  		expectedSignedHeaders []string
   310  		expectedErrCode       APIErrorCode
   311  	}{
   312  		// Test case - 1.
   313  		// SignElement doesn't have 2 parts on an attempt to split at '='.
   314  		// ErrMissingFields expected.
   315  		{
   316  			inputSignElement:      "SignedHeaders",
   317  			expectedSignedHeaders: nil,
   318  			expectedErrCode:       ErrMissingFields,
   319  		},
   320  		// Test case - 2.
   321  		// SignElement with missing "SigHeaderTag",ErrMissingSignHeadersTag expected.
   322  		{
   323  			inputSignElement:      "Sign=",
   324  			expectedSignedHeaders: nil,
   325  			expectedErrCode:       ErrMissingSignHeadersTag,
   326  		},
   327  		// Test case - 3.
   328  		// Test case with valid inputs.
   329  		{
   330  			inputSignElement:      "SignedHeaders=host;x-amz-content-sha256;x-amz-date",
   331  			expectedSignedHeaders: []string{"host", "x-amz-content-sha256", "x-amz-date"},
   332  			expectedErrCode:       ErrNone,
   333  		},
   334  	}
   335  
   336  	for i, testCase := range testCases {
   337  		actualSignedHeaders, actualErrCode := parseSignedHeader(testCase.inputSignElement)
   338  		if testCase.expectedErrCode != actualErrCode {
   339  			t.Errorf("Test %d: Expected the APIErrCode to be %d, got %d", i+1, testCase.expectedErrCode, actualErrCode)
   340  		}
   341  		if actualErrCode == ErrNone {
   342  			if strings.Join(testCase.expectedSignedHeaders, ",") != strings.Join(actualSignedHeaders, ",") {
   343  				t.Errorf("Test %d: Expected the result to be \"%v\", but got \"%v\". ", i+1, testCase.expectedSignedHeaders, actualSignedHeaders)
   344  			}
   345  		}
   346  
   347  	}
   348  }
   349  
   350  // TestParseSignV4 - Tests Parsing of v4 signature form the authorization string.
   351  func TestParseSignV4(t *testing.T) {
   352  	sampleTimeStr := UTCNow().Format(yyyymmdd)
   353  	testCases := []struct {
   354  		inputV4AuthStr    string
   355  		expectedAuthField signValues
   356  		expectedErrCode   APIErrorCode
   357  	}{
   358  		// Test case - 1.
   359  		// Test case with empty auth string.
   360  		{
   361  			inputV4AuthStr:    "",
   362  			expectedAuthField: signValues{},
   363  			expectedErrCode:   ErrAuthHeaderEmpty,
   364  		},
   365  		// Test case - 2.
   366  		// Test case with no sign v4 Algorithm prefix.
   367  		// A valid authorization string should begin(prefix)
   368  		{
   369  			inputV4AuthStr:    "no-singv4AlgorithmPrefix",
   370  			expectedAuthField: signValues{},
   371  			expectedErrCode:   ErrSignatureVersionNotSupported,
   372  		},
   373  		// Test case - 3.
   374  		// Test case with missing fields.
   375  		// A valid authorization string should have 3 fields.
   376  		{
   377  			inputV4AuthStr:    signV4Algorithm,
   378  			expectedAuthField: signValues{},
   379  			expectedErrCode:   ErrMissingFields,
   380  		},
   381  		// Test case - 4.
   382  		// Test case with invalid credential field.
   383  		{
   384  			inputV4AuthStr:    signV4Algorithm + " Cred=,a,b",
   385  			expectedAuthField: signValues{},
   386  			expectedErrCode:   ErrMissingCredTag,
   387  		},
   388  		// Test case - 5.
   389  		// Auth field with missing "SigHeaderTag",ErrMissingSignHeadersTag expected.
   390  		// A valid credential is generated.
   391  		// Test case with invalid credential field.
   392  		{
   393  			inputV4AuthStr: signV4Algorithm +
   394  				strings.Join([]string{
   395  					// generating a valid credential field.
   396  					generateCredentialStr(
   397  						"Z7IXGOO6BZ0REAN1Q26I",
   398  						sampleTimeStr,
   399  						"us-west-1",
   400  						"s3",
   401  						"aws4_request"),
   402  					// Incorrect SignedHeader field.
   403  					"SignIncorrectHeader=",
   404  					"b",
   405  				}, ","),
   406  
   407  			expectedAuthField: signValues{},
   408  			expectedErrCode:   ErrMissingSignHeadersTag,
   409  		},
   410  		// Test case - 6.
   411  		// Auth string with missing "SignatureTag",ErrMissingSignTag expected.
   412  		// A valid credential is generated.
   413  		// Test case with invalid credential field.
   414  		{
   415  			inputV4AuthStr: signV4Algorithm +
   416  				strings.Join([]string{
   417  					// generating a valid credential.
   418  					generateCredentialStr(
   419  						"Z7IXGOO6BZ0REAN1Q26I",
   420  						sampleTimeStr,
   421  						"us-west-1",
   422  						"s3",
   423  						"aws4_request"),
   424  					// valid SignedHeader.
   425  					"SignedHeaders=host;x-amz-content-sha256;x-amz-date",
   426  					// invalid Signature field.
   427  					// a valid signature is of form "Signature="
   428  					"Sign=",
   429  				}, ","),
   430  
   431  			expectedAuthField: signValues{},
   432  			expectedErrCode:   ErrMissingSignTag,
   433  		},
   434  		// Test case - 7.
   435  		{
   436  			inputV4AuthStr: signV4Algorithm +
   437  				strings.Join([]string{
   438  					// generating a valid credential.
   439  					generateCredentialStr(
   440  						"Z7IXGOO6BZ0REAN1Q26I",
   441  						sampleTimeStr,
   442  						"us-west-1",
   443  						"s3",
   444  						"aws4_request"),
   445  					// valid SignedHeader.
   446  					"SignedHeaders=host;x-amz-content-sha256;x-amz-date",
   447  					// valid Signature field.
   448  					// a valid signature is of form "Signature="
   449  					"Signature=abcd",
   450  				}, ","),
   451  			expectedAuthField: signValues{
   452  				Credential: generateCredentials(
   453  					t,
   454  					"Z7IXGOO6BZ0REAN1Q26I",
   455  					sampleTimeStr,
   456  					"us-west-1",
   457  					"s3",
   458  					"aws4_request"),
   459  				SignedHeaders: []string{"host", "x-amz-content-sha256", "x-amz-date"},
   460  				Signature:     "abcd",
   461  			},
   462  			expectedErrCode: ErrNone,
   463  		},
   464  		// Test case - 8.
   465  		{
   466  			inputV4AuthStr: signV4Algorithm +
   467  				strings.Join([]string{
   468  					// generating a valid credential.
   469  					generateCredentialStr(
   470  						"access key",
   471  						sampleTimeStr,
   472  						"us-west-1",
   473  						"s3",
   474  						"aws4_request"),
   475  					// valid SignedHeader.
   476  					"SignedHeaders=host;x-amz-content-sha256;x-amz-date",
   477  					// valid Signature field.
   478  					// a valid signature is of form "Signature="
   479  					"Signature=abcd",
   480  				}, ","),
   481  			expectedAuthField: signValues{
   482  				Credential: generateCredentials(
   483  					t,
   484  					"access key",
   485  					sampleTimeStr,
   486  					"us-west-1",
   487  					"s3",
   488  					"aws4_request"),
   489  				SignedHeaders: []string{"host", "x-amz-content-sha256", "x-amz-date"},
   490  				Signature:     "abcd",
   491  			},
   492  			expectedErrCode: ErrNone,
   493  		},
   494  	}
   495  
   496  	for i, testCase := range testCases {
   497  		parsedAuthField, actualErrCode := parseSignV4(testCase.inputV4AuthStr, "", "s3")
   498  
   499  		if testCase.expectedErrCode != actualErrCode {
   500  			t.Fatalf("Test %d: Expected the APIErrCode to be %d, got %d", i+1, testCase.expectedErrCode, actualErrCode)
   501  		}
   502  
   503  		if actualErrCode == ErrNone {
   504  			// validating the extracted/parsed credential fields.
   505  			validateCredentialfields(t, i+1, testCase.expectedAuthField.Credential, parsedAuthField.Credential)
   506  
   507  			// validating the extraction/parsing of signature field.
   508  			if !compareSignatureV4(testCase.expectedAuthField.Signature, parsedAuthField.Signature) {
   509  				t.Errorf("Test %d: Parsed Signature field mismatch: Expected \"%s\", got \"%s\"", i+1, testCase.expectedAuthField.Signature, parsedAuthField.Signature)
   510  			}
   511  
   512  			// validating the extracted signed headers.
   513  			if strings.Join(testCase.expectedAuthField.SignedHeaders, ",") != strings.Join(parsedAuthField.SignedHeaders, ",") {
   514  				t.Errorf("Test %d: Expected the result to be \"%v\", but got \"%v\". ", i+1, testCase.expectedAuthField, parsedAuthField.SignedHeaders)
   515  			}
   516  		}
   517  
   518  	}
   519  }
   520  
   521  // TestDoesV4PresignParamsExist - tests validate the logic to
   522  func TestDoesV4PresignParamsExist(t *testing.T) {
   523  	testCases := []struct {
   524  		inputQueryKeyVals []string
   525  		expectedErrCode   APIErrorCode
   526  	}{
   527  		// Test case - 1.
   528  		// contains all query param keys which are necessary for v4 presign request.
   529  		{
   530  			inputQueryKeyVals: []string{
   531  				"X-Amz-Algorithm", "",
   532  				"X-Amz-Credential", "",
   533  				"X-Amz-Signature", "",
   534  				"X-Amz-Date", "",
   535  				"X-Amz-SignedHeaders", "",
   536  				"X-Amz-Expires", "",
   537  			},
   538  			expectedErrCode: ErrNone,
   539  		},
   540  		// Test case - 2.
   541  		// missing 	"X-Amz-Algorithm" in tdhe query param.
   542  		// contains all query param keys which are necessary for v4 presign request.
   543  		{
   544  			inputQueryKeyVals: []string{
   545  				"X-Amz-Credential", "",
   546  				"X-Amz-Signature", "",
   547  				"X-Amz-Date", "",
   548  				"X-Amz-SignedHeaders", "",
   549  				"X-Amz-Expires", "",
   550  			},
   551  			expectedErrCode: ErrInvalidQueryParams,
   552  		},
   553  		// Test case - 3.
   554  		// missing "X-Amz-Credential" in the query param.
   555  		{
   556  			inputQueryKeyVals: []string{
   557  				"X-Amz-Algorithm", "",
   558  				"X-Amz-Signature", "",
   559  				"X-Amz-Date", "",
   560  				"X-Amz-SignedHeaders", "",
   561  				"X-Amz-Expires", "",
   562  			},
   563  			expectedErrCode: ErrInvalidQueryParams,
   564  		},
   565  		// Test case - 4.
   566  		// missing "X-Amz-Signature" in the query param.
   567  		{
   568  			inputQueryKeyVals: []string{
   569  				"X-Amz-Algorithm", "",
   570  				"X-Amz-Credential", "",
   571  				"X-Amz-Date", "",
   572  				"X-Amz-SignedHeaders", "",
   573  				"X-Amz-Expires", "",
   574  			},
   575  			expectedErrCode: ErrInvalidQueryParams,
   576  		},
   577  		// Test case - 5.
   578  		// missing "X-Amz-Date" in the query param.
   579  		{
   580  			inputQueryKeyVals: []string{
   581  				"X-Amz-Algorithm", "",
   582  				"X-Amz-Credential", "",
   583  				"X-Amz-Signature", "",
   584  				"X-Amz-SignedHeaders", "",
   585  				"X-Amz-Expires", "",
   586  			},
   587  			expectedErrCode: ErrInvalidQueryParams,
   588  		},
   589  		// Test case - 6.
   590  		// missing "X-Amz-SignedHeaders" in the query param.
   591  		{
   592  			inputQueryKeyVals: []string{
   593  				"X-Amz-Algorithm", "",
   594  				"X-Amz-Credential", "",
   595  				"X-Amz-Signature", "",
   596  				"X-Amz-Date", "",
   597  				"X-Amz-Expires", "",
   598  			},
   599  			expectedErrCode: ErrInvalidQueryParams,
   600  		},
   601  		// Test case - 7.
   602  		// missing "X-Amz-Expires" in the query param.
   603  		{
   604  			inputQueryKeyVals: []string{
   605  				"X-Amz-Algorithm", "",
   606  				"X-Amz-Credential", "",
   607  				"X-Amz-Signature", "",
   608  				"X-Amz-Date", "",
   609  				"X-Amz-SignedHeaders", "",
   610  			},
   611  			expectedErrCode: ErrInvalidQueryParams,
   612  		},
   613  	}
   614  
   615  	for i, testCase := range testCases {
   616  		inputQuery := url.Values{}
   617  		// iterating through input query key value and setting the inputQuery of type url.Values.
   618  		for j := 0; j < len(testCase.inputQueryKeyVals)-1; j += 2 {
   619  			inputQuery.Set(testCase.inputQueryKeyVals[j], testCase.inputQueryKeyVals[j+1])
   620  		}
   621  
   622  		actualErrCode := doesV4PresignParamsExist(inputQuery)
   623  
   624  		if testCase.expectedErrCode != actualErrCode {
   625  			t.Fatalf("Test %d: Expected the APIErrCode to be %d, got %d", i+1, testCase.expectedErrCode, actualErrCode)
   626  		}
   627  	}
   628  }
   629  
   630  // TestParsePreSignV4 - Validates the parsing logic of Presignied v4 request from its url query values.
   631  func TestParsePreSignV4(t *testing.T) {
   632  	// converts the duration in seconds into string format.
   633  	getDurationStr := strconv.Itoa
   634  
   635  	// used in expected preSignValues, preSignValues.Date is of type time.Time .
   636  	queryTime := UTCNow()
   637  
   638  	sampleTimeStr := UTCNow().Format(yyyymmdd)
   639  
   640  	testCases := []struct {
   641  		inputQueryKeyVals     []string
   642  		expectedPreSignValues preSignValues
   643  		expectedErrCode       APIErrorCode
   644  	}{
   645  		// Test case - 1.
   646  		// A Valid v4 presign URL requires the following params to be in the query.
   647  		// "X-Amz-Algorithm", "X-Amz-Credential", "X-Amz-Signature", " X-Amz-Date", "X-Amz-SignedHeaders", "X-Amz-Expires".
   648  		// If these params are missing its expected to get ErrInvalidQueryParams .
   649  		// In the following test case 2 out of 6 query params are missing.
   650  		{
   651  			inputQueryKeyVals: []string{
   652  				"X-Amz-Algorithm", "",
   653  				"X-Amz-Credential", "",
   654  				"X-Amz-Signature", "",
   655  				"X-Amz-Expires", "",
   656  			},
   657  			expectedPreSignValues: preSignValues{},
   658  			expectedErrCode:       ErrInvalidQueryParams,
   659  		},
   660  		// Test case - 2.
   661  		// Test case with invalid  "X-Amz-Algorithm" query value.
   662  		// The other query params should exist, other wise ErrInvalidQueryParams will be returned because of missing fields.
   663  		{
   664  			inputQueryKeyVals: []string{
   665  				"X-Amz-Algorithm", "InvalidValue",
   666  				"X-Amz-Credential", "",
   667  				"X-Amz-Signature", "",
   668  				"X-Amz-Date", "",
   669  				"X-Amz-SignedHeaders", "",
   670  				"X-Amz-Expires", "",
   671  			},
   672  			expectedPreSignValues: preSignValues{},
   673  			expectedErrCode:       ErrInvalidQuerySignatureAlgo,
   674  		},
   675  		// Test case - 3.
   676  		// Test case with valid "X-Amz-Algorithm" query value, but invalid  "X-Amz-Credential" header.
   677  		// Malformed crenential.
   678  		{
   679  			inputQueryKeyVals: []string{
   680  				// valid  "X-Amz-Algorithm" header.
   681  				"X-Amz-Algorithm", signV4Algorithm,
   682  				// valid  "X-Amz-Credential" header.
   683  				"X-Amz-Credential", "invalid-credential",
   684  				"X-Amz-Signature", "",
   685  				"X-Amz-Date", "",
   686  				"X-Amz-SignedHeaders", "",
   687  				"X-Amz-Expires", "",
   688  			},
   689  			expectedPreSignValues: preSignValues{},
   690  			expectedErrCode:       ErrCredMalformed,
   691  		},
   692  
   693  		// Test case - 4.
   694  		// Test case with valid "X-Amz-Algorithm" query value.
   695  		// Malformed date.
   696  		{
   697  			inputQueryKeyVals: []string{
   698  				// valid  "X-Amz-Algorithm" header.
   699  				"X-Amz-Algorithm", signV4Algorithm,
   700  				// valid  "X-Amz-Credential" header.
   701  				"X-Amz-Credential", joinWithSlash(
   702  					"Z7IXGOO6BZ0REAN1Q26I",
   703  					sampleTimeStr,
   704  					"us-west-1",
   705  					"s3",
   706  					"aws4_request"),
   707  				// invalid "X-Amz-Date" query.
   708  				"X-Amz-Date", "invalid-time",
   709  				"X-Amz-SignedHeaders", "",
   710  				"X-Amz-Expires", "",
   711  				"X-Amz-Signature", "",
   712  			},
   713  			expectedPreSignValues: preSignValues{},
   714  			expectedErrCode:       ErrMalformedPresignedDate,
   715  		},
   716  		// Test case - 5.
   717  		// Test case with valid "X-Amz-Algorithm", "X-Amz-Credential", "X-Amz-Date" query value.
   718  		// Malformed Expiry, a valid expiry should be of format "<int>s".
   719  		{
   720  			inputQueryKeyVals: []string{
   721  				// valid  "X-Amz-Algorithm" header.
   722  				"X-Amz-Algorithm", signV4Algorithm,
   723  				// valid  "X-Amz-Credential" header.
   724  				"X-Amz-Credential", joinWithSlash(
   725  					"Z7IXGOO6BZ0REAN1Q26I",
   726  					sampleTimeStr,
   727  					"us-west-1",
   728  					"s3",
   729  					"aws4_request"),
   730  				// valid "X-Amz-Date" query.
   731  				"X-Amz-Date", UTCNow().Format(iso8601Format),
   732  				"X-Amz-Expires", "MalformedExpiry",
   733  				"X-Amz-SignedHeaders", "",
   734  				"X-Amz-Signature", "",
   735  			},
   736  			expectedPreSignValues: preSignValues{},
   737  			expectedErrCode:       ErrMalformedExpires,
   738  		},
   739  		// Test case - 6.
   740  		// Test case with negative X-Amz-Expires header.
   741  		{
   742  			inputQueryKeyVals: []string{
   743  				// valid  "X-Amz-Algorithm" header.
   744  				"X-Amz-Algorithm", signV4Algorithm,
   745  				// valid  "X-Amz-Credential" header.
   746  				"X-Amz-Credential", joinWithSlash(
   747  					"Z7IXGOO6BZ0REAN1Q26I",
   748  					sampleTimeStr,
   749  					"us-west-1",
   750  					"s3",
   751  					"aws4_request"),
   752  				// valid "X-Amz-Date" query.
   753  				"X-Amz-Date", queryTime.UTC().Format(iso8601Format),
   754  				"X-Amz-Expires", getDurationStr(-1),
   755  				"X-Amz-Signature", "abcd",
   756  				"X-Amz-SignedHeaders", "host;x-amz-content-sha256;x-amz-date",
   757  			},
   758  			expectedPreSignValues: preSignValues{},
   759  			expectedErrCode:       ErrNegativeExpires,
   760  		},
   761  		// Test case - 7.
   762  		// Test case with empty X-Amz-SignedHeaders.
   763  		{
   764  			inputQueryKeyVals: []string{
   765  				// valid  "X-Amz-Algorithm" header.
   766  				"X-Amz-Algorithm", signV4Algorithm,
   767  				// valid  "X-Amz-Credential" header.
   768  				"X-Amz-Credential", joinWithSlash(
   769  					"Z7IXGOO6BZ0REAN1Q26I",
   770  					sampleTimeStr,
   771  					"us-west-1",
   772  					"s3",
   773  					"aws4_request"),
   774  				// valid "X-Amz-Date" query.
   775  				"X-Amz-Date", queryTime.UTC().Format(iso8601Format),
   776  				"X-Amz-Expires", getDurationStr(100),
   777  				"X-Amz-Signature", "abcd",
   778  				"X-Amz-SignedHeaders", "",
   779  			},
   780  			expectedPreSignValues: preSignValues{},
   781  			expectedErrCode:       ErrMissingFields,
   782  		},
   783  		// Test case - 8.
   784  		// Test case with valid "X-Amz-Algorithm", "X-Amz-Credential", "X-Amz-Date" query value.
   785  		// Malformed Expiry, a valid expiry should be of format "<int>s".
   786  		{
   787  			inputQueryKeyVals: []string{
   788  				// valid  "X-Amz-Algorithm" header.
   789  				"X-Amz-Algorithm", signV4Algorithm,
   790  				// valid  "X-Amz-Credential" header.
   791  				"X-Amz-Credential", joinWithSlash(
   792  					"Z7IXGOO6BZ0REAN1Q26I",
   793  					sampleTimeStr,
   794  					"us-west-1",
   795  					"s3",
   796  					"aws4_request"),
   797  				// valid "X-Amz-Date" query.
   798  				"X-Amz-Date", queryTime.UTC().Format(iso8601Format),
   799  				"X-Amz-Expires", getDurationStr(100),
   800  				"X-Amz-Signature", "abcd",
   801  				"X-Amz-SignedHeaders", "host;x-amz-content-sha256;x-amz-date",
   802  			},
   803  			expectedPreSignValues: preSignValues{
   804  				signValues{
   805  					// Credentials.
   806  					generateCredentials(
   807  						t,
   808  						"Z7IXGOO6BZ0REAN1Q26I",
   809  						sampleTimeStr,
   810  						"us-west-1",
   811  						"s3",
   812  						"aws4_request",
   813  					),
   814  					// SignedHeaders.
   815  					[]string{"host", "x-amz-content-sha256", "x-amz-date"},
   816  					// Signature.
   817  					"abcd",
   818  				},
   819  				// Date
   820  				queryTime,
   821  				// Expires.
   822  				100 * time.Second,
   823  			},
   824  			expectedErrCode: ErrNone,
   825  		},
   826  
   827  		// Test case - 9.
   828  		// Test case with value greater than 604800 in X-Amz-Expires header.
   829  		{
   830  			inputQueryKeyVals: []string{
   831  				// valid  "X-Amz-Algorithm" header.
   832  				"X-Amz-Algorithm", signV4Algorithm,
   833  				// valid  "X-Amz-Credential" header.
   834  				"X-Amz-Credential", joinWithSlash(
   835  					"Z7IXGOO6BZ0REAN1Q26I",
   836  					sampleTimeStr,
   837  					"us-west-1",
   838  					"s3",
   839  					"aws4_request"),
   840  				// valid "X-Amz-Date" query.
   841  				"X-Amz-Date", queryTime.UTC().Format(iso8601Format),
   842  				// Invalid Expiry time greater than 7 days (604800 in seconds).
   843  				"X-Amz-Expires", getDurationStr(605000),
   844  				"X-Amz-Signature", "abcd",
   845  				"X-Amz-SignedHeaders", "host;x-amz-content-sha256;x-amz-date",
   846  			},
   847  			expectedPreSignValues: preSignValues{},
   848  			expectedErrCode:       ErrMaximumExpires,
   849  		},
   850  	}
   851  
   852  	for i, testCase := range testCases {
   853  		inputQuery := url.Values{}
   854  		// iterating through input query key value and setting the inputQuery of type url.Values.
   855  		for j := 0; j < len(testCase.inputQueryKeyVals)-1; j += 2 {
   856  			inputQuery.Set(testCase.inputQueryKeyVals[j], testCase.inputQueryKeyVals[j+1])
   857  		}
   858  		// call the function under test.
   859  		parsedPreSign, actualErrCode := parsePreSignV4(inputQuery, "", serviceS3)
   860  		if testCase.expectedErrCode != actualErrCode {
   861  			t.Fatalf("Test %d: Expected the APIErrCode to be %d, got %d", i+1, testCase.expectedErrCode, actualErrCode)
   862  		}
   863  		if actualErrCode == ErrNone {
   864  			// validating credentials.
   865  			validateCredentialfields(t, i+1, testCase.expectedPreSignValues.Credential, parsedPreSign.Credential)
   866  			// validating signed headers.
   867  			if strings.Join(testCase.expectedPreSignValues.SignedHeaders, ",") != strings.Join(parsedPreSign.SignedHeaders, ",") {
   868  				t.Errorf("Test %d: Expected the result to be \"%v\", but got \"%v\". ", i+1, testCase.expectedPreSignValues.SignedHeaders, parsedPreSign.SignedHeaders)
   869  			}
   870  			// validating signature field.
   871  			if !compareSignatureV4(testCase.expectedPreSignValues.Signature, parsedPreSign.Signature) {
   872  				t.Errorf("Test %d: Signature field mismatch: Expected \"%s\", got \"%s\"", i+1, testCase.expectedPreSignValues.Signature, parsedPreSign.Signature)
   873  			}
   874  			// validating expiry duration.
   875  			if testCase.expectedPreSignValues.Expires != parsedPreSign.Expires {
   876  				t.Errorf("Test %d: Expected expiry time to be %v, but got %v", i+1, testCase.expectedPreSignValues.Expires, parsedPreSign.Expires)
   877  			}
   878  			// validating presign date field.
   879  			if testCase.expectedPreSignValues.Date.UTC().Format(iso8601Format) != parsedPreSign.Date.UTC().Format(iso8601Format) {
   880  				t.Errorf("Test %d: Expected date to be %v, but got %v", i+1, testCase.expectedPreSignValues.Date.UTC().Format(iso8601Format), parsedPreSign.Date.UTC().Format(iso8601Format))
   881  			}
   882  		}
   883  
   884  	}
   885  }