storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/signature-v4-parser.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2015 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 "net/http" 21 "net/url" 22 "strings" 23 "time" 24 25 xhttp "storj.io/minio/cmd/http" 26 "storj.io/minio/pkg/auth" 27 ) 28 29 // credentialHeader data type represents structured form of Credential 30 // string from authorization header. 31 type credentialHeader struct { 32 accessKey string 33 scope struct { 34 date time.Time 35 region string 36 service string 37 request string 38 } 39 } 40 41 // Return scope string. 42 func (c credentialHeader) getScope() string { 43 return strings.Join([]string{ 44 c.scope.date.Format(yyyymmdd), 45 c.scope.region, 46 c.scope.service, 47 c.scope.request, 48 }, SlashSeparator) 49 } 50 51 func getReqAccessKeyV4(r *http.Request, region string, stype serviceType) (auth.Credentials, bool, APIErrorCode) { 52 ch, s3Err := parseCredentialHeader("Credential="+r.URL.Query().Get(xhttp.AmzCredential), region, stype) 53 if s3Err != ErrNone { 54 // Strip off the Algorithm prefix. 55 v4Auth := strings.TrimPrefix(r.Header.Get("Authorization"), signV4Algorithm) 56 authFields := strings.Split(strings.TrimSpace(v4Auth), ",") 57 if len(authFields) != 3 { 58 return auth.Credentials{}, false, ErrMissingFields 59 } 60 ch, s3Err = parseCredentialHeader(authFields[0], region, stype) 61 if s3Err != ErrNone { 62 return auth.Credentials{}, false, s3Err 63 } 64 } 65 return checkKeyValid(r.Context(), ch.accessKey) 66 } 67 68 // parse credentialHeader string into its structured form. 69 func parseCredentialHeader(credElement string, region string, stype serviceType) (ch credentialHeader, aec APIErrorCode) { 70 creds := strings.SplitN(strings.TrimSpace(credElement), "=", 2) 71 if len(creds) != 2 { 72 return ch, ErrMissingFields 73 } 74 if creds[0] != "Credential" { 75 return ch, ErrMissingCredTag 76 } 77 credElements := strings.Split(strings.TrimSpace(creds[1]), SlashSeparator) 78 if len(credElements) < 5 { 79 return ch, ErrCredMalformed 80 } 81 accessKey := strings.Join(credElements[:len(credElements)-4], SlashSeparator) // The access key may contain one or more `/` 82 if !auth.IsAccessKeyValid(accessKey) { 83 return ch, ErrInvalidAccessKeyID 84 } 85 // Save access key id. 86 cred := credentialHeader{ 87 accessKey: accessKey, 88 } 89 credElements = credElements[len(credElements)-4:] 90 var e error 91 cred.scope.date, e = time.Parse(yyyymmdd, credElements[0]) 92 if e != nil { 93 return ch, ErrMalformedCredentialDate 94 } 95 96 cred.scope.region = credElements[1] 97 // Verify if region is valid. 98 sRegion := cred.scope.region 99 // Region is set to be empty, we use whatever was sent by the 100 // request and proceed further. This is a work-around to address 101 // an important problem for ListBuckets() getting signed with 102 // different regions. 103 if region == "" { 104 region = sRegion 105 } 106 // Should validate region, only if region is set. 107 if !isValidRegion(sRegion, region) { 108 return ch, ErrAuthorizationHeaderMalformed 109 110 } 111 if credElements[2] != string(stype) { 112 switch stype { 113 case serviceSTS: 114 return ch, ErrInvalidServiceSTS 115 } 116 return ch, ErrInvalidServiceS3 117 } 118 cred.scope.service = credElements[2] 119 if credElements[3] != "aws4_request" { 120 return ch, ErrInvalidRequestVersion 121 } 122 cred.scope.request = credElements[3] 123 return cred, ErrNone 124 } 125 126 // Parse signature from signature tag. 127 func parseSignature(signElement string) (string, APIErrorCode) { 128 signFields := strings.Split(strings.TrimSpace(signElement), "=") 129 if len(signFields) != 2 { 130 return "", ErrMissingFields 131 } 132 if signFields[0] != "Signature" { 133 return "", ErrMissingSignTag 134 } 135 if signFields[1] == "" { 136 return "", ErrMissingFields 137 } 138 signature := signFields[1] 139 return signature, ErrNone 140 } 141 142 // Parse slice of signed headers from signed headers tag. 143 func parseSignedHeader(signedHdrElement string) ([]string, APIErrorCode) { 144 signedHdrFields := strings.Split(strings.TrimSpace(signedHdrElement), "=") 145 if len(signedHdrFields) != 2 { 146 return nil, ErrMissingFields 147 } 148 if signedHdrFields[0] != "SignedHeaders" { 149 return nil, ErrMissingSignHeadersTag 150 } 151 if signedHdrFields[1] == "" { 152 return nil, ErrMissingFields 153 } 154 signedHeaders := strings.Split(signedHdrFields[1], ";") 155 return signedHeaders, ErrNone 156 } 157 158 // signValues data type represents structured form of AWS Signature V4 header. 159 type signValues struct { 160 Credential credentialHeader 161 SignedHeaders []string 162 Signature string 163 } 164 165 // preSignValues data type represents structued form of AWS Signature V4 query string. 166 type preSignValues struct { 167 signValues 168 Date time.Time 169 Expires time.Duration 170 } 171 172 // Parses signature version '4' query string of the following form. 173 // 174 // querystring = X-Amz-Algorithm=algorithm 175 // querystring += &X-Amz-Credential= urlencode(accessKey + '/' + credential_scope) 176 // querystring += &X-Amz-Date=date 177 // querystring += &X-Amz-Expires=timeout interval 178 // querystring += &X-Amz-SignedHeaders=signed_headers 179 // querystring += &X-Amz-Signature=signature 180 // 181 // verifies if any of the necessary query params are missing in the presigned request. 182 func doesV4PresignParamsExist(query url.Values) APIErrorCode { 183 v4PresignQueryParams := []string{xhttp.AmzAlgorithm, xhttp.AmzCredential, xhttp.AmzSignature, xhttp.AmzDate, xhttp.AmzSignedHeaders, xhttp.AmzExpires} 184 for _, v4PresignQueryParam := range v4PresignQueryParams { 185 if _, ok := query[v4PresignQueryParam]; !ok { 186 return ErrInvalidQueryParams 187 } 188 } 189 return ErrNone 190 } 191 192 // Parses all the presigned signature values into separate elements. 193 func parsePreSignV4(query url.Values, region string, stype serviceType) (psv preSignValues, aec APIErrorCode) { 194 // verify whether the required query params exist. 195 aec = doesV4PresignParamsExist(query) 196 if aec != ErrNone { 197 return psv, aec 198 } 199 200 // Verify if the query algorithm is supported or not. 201 if query.Get(xhttp.AmzAlgorithm) != signV4Algorithm { 202 return psv, ErrInvalidQuerySignatureAlgo 203 } 204 205 // Initialize signature version '4' structured header. 206 preSignV4Values := preSignValues{} 207 208 // Save credential. 209 preSignV4Values.Credential, aec = parseCredentialHeader("Credential="+query.Get(xhttp.AmzCredential), region, stype) 210 if aec != ErrNone { 211 return psv, aec 212 } 213 214 var e error 215 // Save date in native time.Time. 216 preSignV4Values.Date, e = time.Parse(iso8601Format, query.Get(xhttp.AmzDate)) 217 if e != nil { 218 return psv, ErrMalformedPresignedDate 219 } 220 221 // Save expires in native time.Duration. 222 preSignV4Values.Expires, e = time.ParseDuration(query.Get(xhttp.AmzExpires) + "s") 223 if e != nil { 224 return psv, ErrMalformedExpires 225 } 226 227 if preSignV4Values.Expires < 0 { 228 return psv, ErrNegativeExpires 229 } 230 231 // Check if Expiry time is less than 7 days (value in seconds). 232 if preSignV4Values.Expires.Seconds() > 604800 { 233 return psv, ErrMaximumExpires 234 } 235 236 // Save signed headers. 237 preSignV4Values.SignedHeaders, aec = parseSignedHeader("SignedHeaders=" + query.Get(xhttp.AmzSignedHeaders)) 238 if aec != ErrNone { 239 return psv, aec 240 } 241 242 // Save signature. 243 preSignV4Values.Signature, aec = parseSignature("Signature=" + query.Get(xhttp.AmzSignature)) 244 if aec != ErrNone { 245 return psv, aec 246 } 247 248 // Return structed form of signature query string. 249 return preSignV4Values, ErrNone 250 } 251 252 // Parses signature version '4' header of the following form. 253 // 254 // Authorization: algorithm Credential=accessKeyID/credScope, \ 255 // SignedHeaders=signedHeaders, Signature=signature 256 // 257 func parseSignV4(v4Auth string, region string, stype serviceType) (sv signValues, aec APIErrorCode) { 258 // credElement is fetched first to skip replacing the space in access key. 259 credElement := strings.TrimPrefix(strings.Split(strings.TrimSpace(v4Auth), ",")[0], signV4Algorithm) 260 // Replace all spaced strings, some clients can send spaced 261 // parameters and some won't. So we pro-actively remove any spaces 262 // to make parsing easier. 263 v4Auth = strings.Replace(v4Auth, " ", "", -1) 264 if v4Auth == "" { 265 return sv, ErrAuthHeaderEmpty 266 } 267 268 // Verify if the header algorithm is supported or not. 269 if !strings.HasPrefix(v4Auth, signV4Algorithm) { 270 return sv, ErrSignatureVersionNotSupported 271 } 272 273 // Strip off the Algorithm prefix. 274 v4Auth = strings.TrimPrefix(v4Auth, signV4Algorithm) 275 authFields := strings.Split(strings.TrimSpace(v4Auth), ",") 276 if len(authFields) != 3 { 277 return sv, ErrMissingFields 278 } 279 280 // Initialize signature version '4' structured header. 281 signV4Values := signValues{} 282 283 var s3Err APIErrorCode 284 // Save credentail values. 285 signV4Values.Credential, s3Err = parseCredentialHeader(strings.TrimSpace(credElement), region, stype) 286 if s3Err != ErrNone { 287 return sv, s3Err 288 } 289 290 // Save signed headers. 291 signV4Values.SignedHeaders, s3Err = parseSignedHeader(authFields[1]) 292 if s3Err != ErrNone { 293 return sv, s3Err 294 } 295 296 // Save signature. 297 signV4Values.Signature, s3Err = parseSignature(authFields[2]) 298 if s3Err != ErrNone { 299 return sv, s3Err 300 } 301 302 // Return the structure here. 303 return signV4Values, ErrNone 304 }