diff --git a/signature-v4_test.go b/signature-v4_test.go new file mode 100644 index 000000000..22d4c8211 --- /dev/null +++ b/signature-v4_test.go @@ -0,0 +1,251 @@ +/* + * Minio Cloud Storage, (C) 2016 Minio, Inc. + * + * Licensed 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. + */ + +package main + +import ( + "fmt" + "net/http" + "net/url" + "testing" + "time" +) + +func niceError(code APIErrorCode) string { + // Special-handle ErrNone + if code == ErrNone { + return "ErrNone" + } + + return fmt.Sprintf("%s (%s)", errorCodeResponse[code].Code, errorCodeResponse[code].Description) +} + +func TestDoesPolicySignatureMatch(t *testing.T) { + credentialTemplate := "%s/%s/%s/s3/aws4_request" + now := time.Now().UTC() + accessKey := serverConfig.GetCredential().AccessKeyID + + testCases := []struct { + form map[string]string + expected APIErrorCode + }{ + // (0) It should fail if 'X-Amz-Credential' is missing. + { + form: map[string]string{}, + expected: ErrMissingFields, + }, + // (1) It should fail if the access key is incorrect. + { + form: map[string]string{ + "X-Amz-Credential": fmt.Sprintf(credentialTemplate, "EXAMPLEINVALIDEXAMPL", now.Format(yyyymmdd), "us-east-1"), + }, + expected: ErrInvalidAccessKeyID, + }, + // (2) It should fail if the region is invalid. + { + form: map[string]string{ + "X-Amz-Credential": fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), "invalidregion"), + }, + expected: ErrInvalidRegion, + }, + // (3) It should fail if the date is invalid (or missing, in this case). + { + form: map[string]string{ + "X-Amz-Credential": fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), "us-east-1"), + }, + expected: ErrMalformedDate, + }, + // (4) It should fail with a bad signature. + { + form: map[string]string{ + "X-Amz-Credential": fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), "us-east-1"), + "X-Amz-Date": now.Format(iso8601Format), + "X-Amz-Signature": "invalidsignature", + "Policy": "policy", + }, + expected: ErrSignatureDoesNotMatch, + }, + // (5) It should succeed if everything is correct. + { + form: map[string]string{ + "X-Amz-Credential": fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), "us-east-1"), + "X-Amz-Date": now.Format(iso8601Format), + "X-Amz-Signature": getSignature(getSigningKey(serverConfig.GetCredential().SecretAccessKey, now, "us-east-1"), "policy"), + "Policy": "policy", + }, + expected: ErrNone, + }, + } + + // Run each test case individually. + for i, testCase := range testCases { + code := doesPolicySignatureMatch(testCase.form) + if code != testCase.expected { + t.Errorf("(%d) expected to get %s, instead got %s", i, niceError(testCase.expected), niceError(code)) + } + } +} + +func TestDoesPresignedSignatureMatch(t *testing.T) { + // sha256 hash of "payload" + payload := "239f59ed55e737c77147cf55ad0c1b030b6d7ee748a7426952f9b852d5a935e5" + now := time.Now().UTC() + credentialTemplate := "%s/%s/%s/s3/aws4_request" + + testCases := []struct { + queryParams map[string]string + headers map[string]string + verifyRegion bool + expected APIErrorCode + }{ + // (0) Should error without a set URL query. + { + verifyRegion: false, + expected: ErrInvalidQueryParams, + }, + // (1) Should error on an invalid access key. + { + queryParams: map[string]string{ + "X-Amz-Algorithm": signV4Algorithm, + "X-Amz-Date": now.Format(iso8601Format), + "X-Amz-Expires": "60", + "X-Amz-Signature": "badsignature", + "X-Amz-SignedHeaders": "host;x-amz-content-sha256;x-amz-date", + "X-Amz-Credential": fmt.Sprintf(credentialTemplate, "Z7IXGOO6BZ0REAN1Q26I", now.Format(yyyymmdd), "us-west-1"), + }, + verifyRegion: false, + expected: ErrInvalidAccessKeyID, + }, + // (2) Should error when the payload sha256 doesn't match. + { + queryParams: map[string]string{ + "X-Amz-Algorithm": signV4Algorithm, + "X-Amz-Date": now.Format(iso8601Format), + "X-Amz-Expires": "60", + "X-Amz-Signature": "badsignature", + "X-Amz-SignedHeaders": "host;x-amz-content-sha256;x-amz-date", + "X-Amz-Credential": fmt.Sprintf(credentialTemplate, serverConfig.GetCredential().AccessKeyID, now.Format(yyyymmdd), "us-west-1"), + "X-Amz-Content-Sha256": "ThisIsNotThePayloadHash", + }, + verifyRegion: false, + expected: ErrContentSHA256Mismatch, + }, + // (3) Should fail with an invalid region. + { + queryParams: map[string]string{ + "X-Amz-Algorithm": signV4Algorithm, + "X-Amz-Date": now.Format(iso8601Format), + "X-Amz-Expires": "60", + "X-Amz-Signature": "badsignature", + "X-Amz-SignedHeaders": "host;x-amz-content-sha256;x-amz-date", + "X-Amz-Credential": fmt.Sprintf(credentialTemplate, serverConfig.GetCredential().AccessKeyID, now.Format(yyyymmdd), "us-west-1"), + "X-Amz-Content-Sha256": payload, + }, + verifyRegion: true, + expected: ErrInvalidRegion, + }, + // (4) Should NOT fail with an invalid region if it doesn't verify it. + { + queryParams: map[string]string{ + "X-Amz-Algorithm": signV4Algorithm, + "X-Amz-Date": now.Format(iso8601Format), + "X-Amz-Expires": "60", + "X-Amz-Signature": "badsignature", + "X-Amz-SignedHeaders": "host;x-amz-content-sha256;x-amz-date", + "X-Amz-Credential": fmt.Sprintf(credentialTemplate, serverConfig.GetCredential().AccessKeyID, now.Format(yyyymmdd), "us-west-1"), + "X-Amz-Content-Sha256": payload, + }, + verifyRegion: false, + expected: ErrUnsignedHeaders, + }, + // (5) Should fail to extract headers if the host header is not signed. + { + queryParams: map[string]string{ + "X-Amz-Algorithm": signV4Algorithm, + "X-Amz-Date": now.Format(iso8601Format), + "X-Amz-Expires": "60", + "X-Amz-Signature": "badsignature", + "X-Amz-SignedHeaders": "x-amz-content-sha256;x-amz-date", + "X-Amz-Credential": fmt.Sprintf(credentialTemplate, serverConfig.GetCredential().AccessKeyID, now.Format(yyyymmdd), serverConfig.GetRegion()), + "X-Amz-Content-Sha256": payload, + }, + verifyRegion: true, + expected: ErrUnsignedHeaders, + }, + // (6) Should give an expired request if it has expired. + { + queryParams: map[string]string{ + "X-Amz-Algorithm": signV4Algorithm, + "X-Amz-Date": now.AddDate(0, 0, -2).Format(iso8601Format), + "X-Amz-Expires": "60", + "X-Amz-Signature": "badsignature", + "X-Amz-SignedHeaders": "host;x-amz-content-sha256;x-amz-date", + "X-Amz-Credential": fmt.Sprintf(credentialTemplate, serverConfig.GetCredential().AccessKeyID, now.Format(yyyymmdd), serverConfig.GetRegion()), + "X-Amz-Content-Sha256": payload, + }, + headers: map[string]string{ + "X-Amz-Date": now.AddDate(0, 0, -2).Format(iso8601Format), + "X-Amz-Content-Sha256": payload, + }, + verifyRegion: false, + expected: ErrExpiredPresignRequest, + }, + // (7) Should error if the signature is incorrect. + { + queryParams: map[string]string{ + "X-Amz-Algorithm": signV4Algorithm, + "X-Amz-Date": now.Format(iso8601Format), + "X-Amz-Expires": "60", + "X-Amz-Signature": "badsignature", + "X-Amz-SignedHeaders": "host;x-amz-content-sha256;x-amz-date", + "X-Amz-Credential": fmt.Sprintf(credentialTemplate, serverConfig.GetCredential().AccessKeyID, now.Format(yyyymmdd), serverConfig.GetRegion()), + "X-Amz-Content-Sha256": payload, + }, + headers: map[string]string{ + "X-Amz-Date": now.Format(iso8601Format), + "X-Amz-Content-Sha256": payload, + }, + verifyRegion: false, + expected: ErrSignatureDoesNotMatch, + }, + } + + // Run each test case individually. + for i, testCase := range testCases { + // Turn the map[string]string into map[string][]string, because Go. + query := url.Values{} + for key, value := range testCase.queryParams { + query.Set(key, value) + } + + // Create a request to use. + req, e := http.NewRequest(http.MethodGet, "http://host/a/b?"+query.Encode(), nil) + if e != nil { + t.Errorf("(%d) failed to create http.Request, got %v", i, e) + } + + // Do the same for the headers. + for key, value := range testCase.headers { + req.Header.Set(key, value) + } + + // Check if it matches! + err := doesPresignedSignatureMatch(payload, req, testCase.verifyRegion) + if err != testCase.expected { + t.Errorf("(%d) expected to get %s, instead got %s", i, niceError(testCase.expected), niceError(err)) + } + } +}