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 }