storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/signature-v4-utils.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2015, 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 "bytes" 21 "context" 22 "crypto/hmac" 23 "crypto/sha256" 24 "encoding/hex" 25 "io" 26 "io/ioutil" 27 "net/http" 28 "strconv" 29 "strings" 30 31 xhttp "storj.io/minio/cmd/http" 32 "storj.io/minio/cmd/logger" 33 "storj.io/minio/pkg/auth" 34 ) 35 36 // http Header "x-amz-content-sha256" == "UNSIGNED-PAYLOAD" indicates that the 37 // client did not calculate sha256 of the payload. 38 const unsignedPayload = "UNSIGNED-PAYLOAD" 39 40 // skipContentSha256Cksum returns true if caller needs to skip 41 // payload checksum, false if not. 42 func skipContentSha256Cksum(r *http.Request) bool { 43 var ( 44 v []string 45 ok bool 46 ) 47 48 if isRequestPresignedSignatureV4(r) { 49 v, ok = r.URL.Query()[xhttp.AmzContentSha256] 50 if !ok { 51 v, ok = r.Header[xhttp.AmzContentSha256] 52 } 53 } else { 54 v, ok = r.Header[xhttp.AmzContentSha256] 55 } 56 57 // If x-amz-content-sha256 is set and the value is not 58 // 'UNSIGNED-PAYLOAD' we should validate the content sha256. 59 return !(ok && v[0] != unsignedPayload) 60 } 61 62 // Returns SHA256 for calculating canonical-request. 63 func getContentSha256Cksum(r *http.Request, stype serviceType) string { 64 if stype == serviceSTS { 65 payload, err := ioutil.ReadAll(io.LimitReader(r.Body, stsRequestBodyLimit)) 66 if err != nil { 67 logger.CriticalIf(GlobalContext, err) 68 } 69 sum256 := sha256.Sum256(payload) 70 r.Body = ioutil.NopCloser(bytes.NewReader(payload)) 71 return hex.EncodeToString(sum256[:]) 72 } 73 74 var ( 75 defaultSha256Cksum string 76 v []string 77 ok bool 78 ) 79 80 // For a presigned request we look at the query param for sha256. 81 if isRequestPresignedSignatureV4(r) { 82 // X-Amz-Content-Sha256, if not set in presigned requests, checksum 83 // will default to 'UNSIGNED-PAYLOAD'. 84 defaultSha256Cksum = unsignedPayload 85 v, ok = r.URL.Query()[xhttp.AmzContentSha256] 86 if !ok { 87 v, ok = r.Header[xhttp.AmzContentSha256] 88 } 89 } else { 90 // X-Amz-Content-Sha256, if not set in signed requests, checksum 91 // will default to sha256([]byte("")). 92 defaultSha256Cksum = emptySHA256 93 v, ok = r.Header[xhttp.AmzContentSha256] 94 } 95 96 // We found 'X-Amz-Content-Sha256' return the captured value. 97 if ok { 98 return v[0] 99 } 100 101 // We couldn't find 'X-Amz-Content-Sha256'. 102 return defaultSha256Cksum 103 } 104 105 // isValidRegion - verify if incoming region value is valid with configured Region. 106 func isValidRegion(reqRegion string, confRegion string) bool { 107 if confRegion == "" { 108 return true 109 } 110 if confRegion == "US" { 111 confRegion = globalMinioDefaultRegion 112 } 113 // Some older s3 clients set region as "US" instead of 114 // globalMinioDefaultRegion, handle it. 115 if reqRegion == "US" { 116 reqRegion = globalMinioDefaultRegion 117 } 118 return reqRegion == confRegion 119 } 120 121 // check if the access key is valid and recognized, additionally 122 // also returns if the access key is owner/admin. 123 func checkKeyValid(ctx context.Context, accessKey string) (auth.Credentials, bool, APIErrorCode) { 124 var owner = true 125 var cred = globalActiveCred 126 if cred.AccessKey != accessKey { 127 // Check if the access key is part of users credentials. 128 var ok bool 129 if cred, ok = GlobalIAMSys.GetUser(ctx, accessKey); !ok { 130 return cred, false, ErrInvalidAccessKeyID 131 } 132 owner = false 133 } 134 return cred, owner, ErrNone 135 } 136 137 // sumHMAC calculate hmac between two input byte array. 138 func sumHMAC(key []byte, data []byte) []byte { 139 hash := hmac.New(sha256.New, key) 140 hash.Write(data) 141 return hash.Sum(nil) 142 } 143 144 // extractSignedHeaders extract signed headers from Authorization header 145 func extractSignedHeaders(signedHeaders []string, r *http.Request) (http.Header, APIErrorCode) { 146 reqHeaders := r.Header 147 reqQueries := r.URL.Query() 148 // find whether "host" is part of list of signed headers. 149 // if not return ErrUnsignedHeaders. "host" is mandatory. 150 if !contains(signedHeaders, "host") { 151 return nil, ErrUnsignedHeaders 152 } 153 extractedSignedHeaders := make(http.Header) 154 for _, header := range signedHeaders { 155 // `host` will not be found in the headers, can be found in r.Host. 156 // but its alway necessary that the list of signed headers containing host in it. 157 val, ok := reqHeaders[http.CanonicalHeaderKey(header)] 158 if !ok { 159 // try to set headers from Query String 160 val, ok = reqQueries[header] 161 } 162 if ok { 163 extractedSignedHeaders[http.CanonicalHeaderKey(header)] = val 164 continue 165 } 166 switch header { 167 case "expect": 168 // Golang http server strips off 'Expect' header, if the 169 // client sent this as part of signed headers we need to 170 // handle otherwise we would see a signature mismatch. 171 // `aws-cli` sets this as part of signed headers. 172 // 173 // According to 174 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.20 175 // Expect header is always of form: 176 // 177 // Expect = "Expect" ":" 1#expectation 178 // expectation = "100-continue" | expectation-extension 179 // 180 // So it safe to assume that '100-continue' is what would 181 // be sent, for the time being keep this work around. 182 // Adding a *TODO* to remove this later when Golang server 183 // doesn't filter out the 'Expect' header. 184 extractedSignedHeaders.Set(header, "100-continue") 185 case "host": 186 // Go http server removes "host" from Request.Header 187 extractedSignedHeaders.Set(header, r.Host) 188 case "transfer-encoding": 189 // Go http server removes "host" from Request.Header 190 extractedSignedHeaders[http.CanonicalHeaderKey(header)] = r.TransferEncoding 191 case "content-length": 192 // Signature-V4 spec excludes Content-Length from signed headers list for signature calculation. 193 // But some clients deviate from this rule. Hence we consider Content-Length for signature 194 // calculation to be compatible with such clients. 195 extractedSignedHeaders.Set(header, strconv.FormatInt(r.ContentLength, 10)) 196 default: 197 return nil, ErrUnsignedHeaders 198 } 199 } 200 return extractedSignedHeaders, ErrNone 201 } 202 203 // Trim leading and trailing spaces and replace sequential spaces with one space, following Trimall() 204 // in http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html 205 func signV4TrimAll(input string) string { 206 // Compress adjacent spaces (a space is determined by 207 // unicode.IsSpace() internally here) to one space and return 208 return strings.Join(strings.Fields(input), " ") 209 }