storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/signature-v4_test.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2016, 2017 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cmd 18 19 import ( 20 "context" 21 "fmt" 22 "net/http" 23 "net/url" 24 "os" 25 "testing" 26 "time" 27 ) 28 29 func niceError(code APIErrorCode) string { 30 // Special-handle ErrNone 31 if code == ErrNone { 32 return "ErrNone" 33 } 34 35 return fmt.Sprintf("%s (%s)", errorCodes[code].Code, errorCodes[code].Description) 36 } 37 38 func TestDoesPolicySignatureMatch(t *testing.T) { 39 credentialTemplate := "%s/%s/%s/s3/aws4_request" 40 now := UTCNow() 41 accessKey := globalActiveCred.AccessKey 42 43 testCases := []struct { 44 form http.Header 45 expected APIErrorCode 46 }{ 47 // (0) It should fail if 'X-Amz-Credential' is missing. 48 { 49 form: http.Header{}, 50 expected: ErrCredMalformed, 51 }, 52 // (1) It should fail if the access key is incorrect. 53 { 54 form: http.Header{ 55 "X-Amz-Credential": []string{fmt.Sprintf(credentialTemplate, "EXAMPLEINVALIDEXAMPL", now.Format(yyyymmdd), globalMinioDefaultRegion)}, 56 }, 57 expected: ErrInvalidAccessKeyID, 58 }, 59 // (2) It should fail with a bad signature. 60 { 61 form: http.Header{ 62 "X-Amz-Credential": []string{fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), globalMinioDefaultRegion)}, 63 "X-Amz-Date": []string{now.Format(iso8601Format)}, 64 "X-Amz-Signature": []string{"invalidsignature"}, 65 "Policy": []string{"policy"}, 66 }, 67 expected: ErrSignatureDoesNotMatch, 68 }, 69 // (3) It should succeed if everything is correct. 70 { 71 form: http.Header{ 72 "X-Amz-Credential": []string{ 73 fmt.Sprintf(credentialTemplate, accessKey, now.Format(yyyymmdd), globalMinioDefaultRegion), 74 }, 75 "X-Amz-Date": []string{now.Format(iso8601Format)}, 76 "X-Amz-Signature": []string{ 77 getSignature(getSigningKey(globalActiveCred.SecretKey, now, 78 globalMinioDefaultRegion, serviceS3), "policy"), 79 }, 80 "Policy": []string{"policy"}, 81 }, 82 expected: ErrNone, 83 }, 84 } 85 86 // Run each test case individually. 87 for i, testCase := range testCases { 88 _, code := doesPolicySignatureMatch(context.TODO(), testCase.form) 89 if code != testCase.expected { 90 t.Errorf("(%d) expected to get %s, instead got %s", i, niceError(testCase.expected), niceError(code)) 91 } 92 } 93 } 94 95 func TestDoesPresignedSignatureMatch(t *testing.T) { 96 obj, fsDir, err := prepareFS() 97 if err != nil { 98 t.Fatal(err) 99 } 100 defer os.RemoveAll(fsDir) 101 if err = newTestConfig(globalMinioDefaultRegion, obj); err != nil { 102 t.Fatal(err) 103 } 104 105 // sha256 hash of "payload" 106 payloadSHA256 := "239f59ed55e737c77147cf55ad0c1b030b6d7ee748a7426952f9b852d5a935e5" 107 now := UTCNow() 108 credentialTemplate := "%s/%s/%s/s3/aws4_request" 109 110 region := globalServerRegion 111 accessKeyID := globalActiveCred.AccessKey 112 testCases := []struct { 113 queryParams map[string]string 114 headers map[string]string 115 region string 116 expected APIErrorCode 117 }{ 118 // (0) Should error without a set URL query. 119 { 120 region: globalMinioDefaultRegion, 121 expected: ErrInvalidQueryParams, 122 }, 123 // (1) Should error on an invalid access key. 124 { 125 queryParams: map[string]string{ 126 "X-Amz-Algorithm": signV4Algorithm, 127 "X-Amz-Date": now.Format(iso8601Format), 128 "X-Amz-Expires": "60", 129 "X-Amz-Signature": "badsignature", 130 "X-Amz-SignedHeaders": "host;x-amz-content-sha256;x-amz-date", 131 "X-Amz-Credential": fmt.Sprintf(credentialTemplate, "Z7IXGOO6BZ0REAN1Q26I", now.Format(yyyymmdd), "us-west-1"), 132 }, 133 region: "us-west-1", 134 expected: ErrInvalidAccessKeyID, 135 }, 136 // (2) Should NOT fail with an invalid region if it doesn't verify it. 137 { 138 queryParams: map[string]string{ 139 "X-Amz-Algorithm": signV4Algorithm, 140 "X-Amz-Date": now.Format(iso8601Format), 141 "X-Amz-Expires": "60", 142 "X-Amz-Signature": "badsignature", 143 "X-Amz-SignedHeaders": "host;x-amz-content-sha256;x-amz-date", 144 "X-Amz-Credential": fmt.Sprintf(credentialTemplate, accessKeyID, now.Format(yyyymmdd), "us-west-1"), 145 "X-Amz-Content-Sha256": payloadSHA256, 146 }, 147 region: "us-west-1", 148 expected: ErrUnsignedHeaders, 149 }, 150 // (3) Should fail to extract headers if the host header is not signed. 151 { 152 queryParams: map[string]string{ 153 "X-Amz-Algorithm": signV4Algorithm, 154 "X-Amz-Date": now.Format(iso8601Format), 155 "X-Amz-Expires": "60", 156 "X-Amz-Signature": "badsignature", 157 "X-Amz-SignedHeaders": "x-amz-content-sha256;x-amz-date", 158 "X-Amz-Credential": fmt.Sprintf(credentialTemplate, accessKeyID, now.Format(yyyymmdd), region), 159 "X-Amz-Content-Sha256": payloadSHA256, 160 }, 161 region: region, 162 expected: ErrUnsignedHeaders, 163 }, 164 // (4) Should give an expired request if it has expired. 165 { 166 queryParams: map[string]string{ 167 "X-Amz-Algorithm": signV4Algorithm, 168 "X-Amz-Date": now.AddDate(0, 0, -2).Format(iso8601Format), 169 "X-Amz-Expires": "60", 170 "X-Amz-Signature": "badsignature", 171 "X-Amz-SignedHeaders": "host;x-amz-content-sha256;x-amz-date", 172 "X-Amz-Credential": fmt.Sprintf(credentialTemplate, accessKeyID, now.Format(yyyymmdd), region), 173 "X-Amz-Content-Sha256": payloadSHA256, 174 }, 175 headers: map[string]string{ 176 "X-Amz-Date": now.AddDate(0, 0, -2).Format(iso8601Format), 177 "X-Amz-Content-Sha256": payloadSHA256, 178 }, 179 region: region, 180 expected: ErrExpiredPresignRequest, 181 }, 182 // (5) Should error if the signature is incorrect. 183 { 184 queryParams: map[string]string{ 185 "X-Amz-Algorithm": signV4Algorithm, 186 "X-Amz-Date": now.Format(iso8601Format), 187 "X-Amz-Expires": "60", 188 "X-Amz-Signature": "badsignature", 189 "X-Amz-SignedHeaders": "host;x-amz-content-sha256;x-amz-date", 190 "X-Amz-Credential": fmt.Sprintf(credentialTemplate, accessKeyID, now.Format(yyyymmdd), region), 191 "X-Amz-Content-Sha256": payloadSHA256, 192 }, 193 headers: map[string]string{ 194 "X-Amz-Date": now.Format(iso8601Format), 195 "X-Amz-Content-Sha256": payloadSHA256, 196 }, 197 region: region, 198 expected: ErrSignatureDoesNotMatch, 199 }, 200 // (6) Should error if the request is not ready yet, ie X-Amz-Date is in the future. 201 { 202 queryParams: map[string]string{ 203 "X-Amz-Algorithm": signV4Algorithm, 204 "X-Amz-Date": now.Add(1 * time.Hour).Format(iso8601Format), 205 "X-Amz-Expires": "60", 206 "X-Amz-Signature": "badsignature", 207 "X-Amz-SignedHeaders": "host;x-amz-content-sha256;x-amz-date", 208 "X-Amz-Credential": fmt.Sprintf(credentialTemplate, accessKeyID, now.Format(yyyymmdd), region), 209 "X-Amz-Content-Sha256": payloadSHA256, 210 }, 211 headers: map[string]string{ 212 "X-Amz-Date": now.Format(iso8601Format), 213 "X-Amz-Content-Sha256": payloadSHA256, 214 }, 215 region: region, 216 expected: ErrRequestNotReadyYet, 217 }, 218 // (7) Should not error with invalid region instead, call should proceed 219 // with sigature does not match. 220 { 221 queryParams: map[string]string{ 222 "X-Amz-Algorithm": signV4Algorithm, 223 "X-Amz-Date": now.Format(iso8601Format), 224 "X-Amz-Expires": "60", 225 "X-Amz-Signature": "badsignature", 226 "X-Amz-SignedHeaders": "host;x-amz-content-sha256;x-amz-date", 227 "X-Amz-Credential": fmt.Sprintf(credentialTemplate, accessKeyID, now.Format(yyyymmdd), region), 228 "X-Amz-Content-Sha256": payloadSHA256, 229 }, 230 headers: map[string]string{ 231 "X-Amz-Date": now.Format(iso8601Format), 232 "X-Amz-Content-Sha256": payloadSHA256, 233 }, 234 region: "", 235 expected: ErrSignatureDoesNotMatch, 236 }, 237 // (8) Should error with signature does not match. But handles 238 // query params which do not precede with "x-amz-" header. 239 { 240 queryParams: map[string]string{ 241 "X-Amz-Algorithm": signV4Algorithm, 242 "X-Amz-Date": now.Format(iso8601Format), 243 "X-Amz-Expires": "60", 244 "X-Amz-Signature": "badsignature", 245 "X-Amz-SignedHeaders": "host;x-amz-content-sha256;x-amz-date", 246 "X-Amz-Credential": fmt.Sprintf(credentialTemplate, accessKeyID, now.Format(yyyymmdd), region), 247 "X-Amz-Content-Sha256": payloadSHA256, 248 "response-content-type": "application/json", 249 }, 250 headers: map[string]string{ 251 "X-Amz-Date": now.Format(iso8601Format), 252 "X-Amz-Content-Sha256": payloadSHA256, 253 }, 254 region: "", 255 expected: ErrSignatureDoesNotMatch, 256 }, 257 // (9) Should error with unsigned headers. 258 { 259 queryParams: map[string]string{ 260 "X-Amz-Algorithm": signV4Algorithm, 261 "X-Amz-Date": now.Format(iso8601Format), 262 "X-Amz-Expires": "60", 263 "X-Amz-Signature": "badsignature", 264 "X-Amz-SignedHeaders": "host;x-amz-content-sha256;x-amz-date", 265 "X-Amz-Credential": fmt.Sprintf(credentialTemplate, accessKeyID, now.Format(yyyymmdd), region), 266 "X-Amz-Content-Sha256": payloadSHA256, 267 "response-content-type": "application/json", 268 }, 269 headers: map[string]string{ 270 "X-Amz-Date": now.Format(iso8601Format), 271 }, 272 region: "", 273 expected: ErrUnsignedHeaders, 274 }, 275 } 276 277 // Run each test case individually. 278 for i, testCase := range testCases { 279 // Turn the map[string]string into map[string][]string, because Go. 280 query := url.Values{} 281 for key, value := range testCase.queryParams { 282 query.Set(key, value) 283 } 284 285 // Create a request to use. 286 req, e := http.NewRequest(http.MethodGet, "http://host/a/b?"+query.Encode(), nil) 287 if e != nil { 288 t.Errorf("(%d) failed to create http.Request, got %v", i, e) 289 } 290 291 // Do the same for the headers. 292 for key, value := range testCase.headers { 293 req.Header.Set(key, value) 294 } 295 296 // Check if it matches! 297 err := doesPresignedSignatureMatch(payloadSHA256, req, testCase.region, serviceS3) 298 if err != testCase.expected { 299 t.Errorf("(%d) expected to get %s, instead got %s", i, niceError(testCase.expected), niceError(err)) 300 } 301 } 302 }