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  }