storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/jwt/parser.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2020 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 jwt 18 19 // This file is a re-implementation of the original code here with some 20 // additional allocation tweaks reproduced using GODEBUG=allocfreetrace=1 21 // original file https://github.com/dgrijalva/jwt-go/blob/master/parser.go 22 // borrowed under MIT License https://github.com/dgrijalva/jwt-go/blob/master/LICENSE 23 24 import ( 25 "crypto" 26 "crypto/hmac" 27 "encoding/base64" 28 "encoding/json" 29 "fmt" 30 "strings" 31 "sync" 32 "time" 33 34 jwtgo "github.com/dgrijalva/jwt-go" 35 jsoniter "github.com/json-iterator/go" 36 ) 37 38 // SigningMethodHMAC - Implements the HMAC-SHA family of signing methods signing methods 39 // Expects key type of []byte for both signing and validation 40 type SigningMethodHMAC struct { 41 Name string 42 Hash crypto.Hash 43 } 44 45 // Specific instances for HS256, HS384, HS512 46 var ( 47 SigningMethodHS256 *SigningMethodHMAC 48 SigningMethodHS384 *SigningMethodHMAC 49 SigningMethodHS512 *SigningMethodHMAC 50 ) 51 52 var ( 53 base64BufPool sync.Pool 54 hmacSigners []*SigningMethodHMAC 55 ) 56 57 func init() { 58 base64BufPool = sync.Pool{ 59 New: func() interface{} { 60 buf := make([]byte, 8192) 61 return &buf 62 }, 63 } 64 65 hmacSigners = []*SigningMethodHMAC{ 66 {"HS256", crypto.SHA256}, 67 {"HS384", crypto.SHA384}, 68 {"HS512", crypto.SHA512}, 69 } 70 } 71 72 // StandardClaims are basically standard claims with "accessKey" 73 type StandardClaims struct { 74 AccessKey string `json:"accessKey,omitempty"` 75 jwtgo.StandardClaims 76 } 77 78 // MapClaims - implements custom unmarshaller 79 type MapClaims struct { 80 AccessKey string `json:"accessKey,omitempty"` 81 jwtgo.MapClaims 82 } 83 84 // GetAccessKey will return the access key. 85 // If nil an empty string will be returned. 86 func (c *MapClaims) GetAccessKey() string { 87 if c == nil { 88 return "" 89 } 90 return c.AccessKey 91 } 92 93 // NewStandardClaims - initializes standard claims 94 func NewStandardClaims() *StandardClaims { 95 return &StandardClaims{} 96 } 97 98 // SetIssuer sets issuer for these claims 99 func (c *StandardClaims) SetIssuer(issuer string) { 100 c.Issuer = issuer 101 } 102 103 // SetAudience sets audience for these claims 104 func (c *StandardClaims) SetAudience(aud string) { 105 c.Audience = aud 106 } 107 108 // SetExpiry sets expiry in unix epoch secs 109 func (c *StandardClaims) SetExpiry(t time.Time) { 110 c.ExpiresAt = t.Unix() 111 } 112 113 // SetAccessKey sets access key as jwt subject and custom 114 // "accessKey" field. 115 func (c *StandardClaims) SetAccessKey(accessKey string) { 116 c.Subject = accessKey 117 c.AccessKey = accessKey 118 } 119 120 // Valid - implements https://godoc.org/github.com/dgrijalva/jwt-go#Claims compatible 121 // claims interface, additionally validates "accessKey" fields. 122 func (c *StandardClaims) Valid() error { 123 if err := c.StandardClaims.Valid(); err != nil { 124 return err 125 } 126 127 if c.AccessKey == "" && c.Subject == "" { 128 return jwtgo.NewValidationError("accessKey/sub missing", 129 jwtgo.ValidationErrorClaimsInvalid) 130 } 131 132 return nil 133 } 134 135 // NewMapClaims - Initializes a new map claims 136 func NewMapClaims() *MapClaims { 137 return &MapClaims{MapClaims: jwtgo.MapClaims{}} 138 } 139 140 // Lookup returns the value and if the key is found. 141 func (c *MapClaims) Lookup(key string) (value string, ok bool) { 142 if c == nil { 143 return "", false 144 } 145 var vinterface interface{} 146 vinterface, ok = c.MapClaims[key] 147 if ok { 148 value, ok = vinterface.(string) 149 } 150 return 151 } 152 153 // SetExpiry sets expiry in unix epoch secs 154 func (c *MapClaims) SetExpiry(t time.Time) { 155 c.MapClaims["exp"] = t.Unix() 156 } 157 158 // SetAccessKey sets access key as jwt subject and custom 159 // "accessKey" field. 160 func (c *MapClaims) SetAccessKey(accessKey string) { 161 c.MapClaims["sub"] = accessKey 162 c.MapClaims["accessKey"] = accessKey 163 } 164 165 // Valid - implements https://godoc.org/github.com/dgrijalva/jwt-go#Claims compatible 166 // claims interface, additionally validates "accessKey" fields. 167 func (c *MapClaims) Valid() error { 168 if err := c.MapClaims.Valid(); err != nil { 169 return err 170 } 171 172 if c.AccessKey == "" { 173 return jwtgo.NewValidationError("accessKey/sub missing", 174 jwtgo.ValidationErrorClaimsInvalid) 175 } 176 177 return nil 178 } 179 180 // Map returns underlying low-level map claims. 181 func (c *MapClaims) Map() map[string]interface{} { 182 if c == nil { 183 return nil 184 } 185 return c.MapClaims 186 } 187 188 // MarshalJSON marshals the MapClaims struct 189 func (c *MapClaims) MarshalJSON() ([]byte, error) { 190 return json.Marshal(c.MapClaims) 191 } 192 193 // ParseWithStandardClaims - parse the token string, valid methods. 194 func ParseWithStandardClaims(tokenStr string, claims *StandardClaims, key []byte) error { 195 // Key is not provided. 196 if key == nil { 197 // keyFunc was not provided, return error. 198 return jwtgo.NewValidationError("no key was provided.", jwtgo.ValidationErrorUnverifiable) 199 } 200 201 bufp := base64BufPool.Get().(*[]byte) 202 defer base64BufPool.Put(bufp) 203 204 signer, err := ParseUnverifiedStandardClaims(tokenStr, claims, *bufp) 205 if err != nil { 206 return err 207 } 208 209 i := strings.LastIndex(tokenStr, ".") 210 if i < 0 { 211 return jwtgo.ErrSignatureInvalid 212 } 213 214 n, err := base64Decode(tokenStr[i+1:], *bufp) 215 if err != nil { 216 return err 217 } 218 219 hasher := hmac.New(signer.Hash.New, key) 220 hasher.Write([]byte(tokenStr[:i])) 221 if !hmac.Equal((*bufp)[:n], hasher.Sum(nil)) { 222 return jwtgo.ErrSignatureInvalid 223 } 224 225 if claims.AccessKey == "" && claims.Subject == "" { 226 return jwtgo.NewValidationError("accessKey/sub missing", 227 jwtgo.ValidationErrorClaimsInvalid) 228 } 229 230 // Signature is valid, lets validate the claims for 231 // other fields such as expiry etc. 232 return claims.Valid() 233 } 234 235 // https://tools.ietf.org/html/rfc7519#page-11 236 type jwtHeader struct { 237 Algorithm string `json:"alg"` 238 Type string `json:"typ"` 239 } 240 241 // ParseUnverifiedStandardClaims - WARNING: Don't use this method unless you know what you're doing 242 // 243 // This method parses the token but doesn't validate the signature. It's only 244 // ever useful in cases where you know the signature is valid (because it has 245 // been checked previously in the stack) and you want to extract values from 246 // it. 247 func ParseUnverifiedStandardClaims(tokenString string, claims *StandardClaims, buf []byte) (*SigningMethodHMAC, error) { 248 if strings.Count(tokenString, ".") != 2 { 249 return nil, jwtgo.ErrSignatureInvalid 250 } 251 252 i := strings.Index(tokenString, ".") 253 j := strings.LastIndex(tokenString, ".") 254 255 n, err := base64Decode(tokenString[:i], buf) 256 if err != nil { 257 return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed} 258 } 259 260 var header = jwtHeader{} 261 var json = jsoniter.ConfigCompatibleWithStandardLibrary 262 if err = json.Unmarshal(buf[:n], &header); err != nil { 263 return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed} 264 } 265 266 n, err = base64Decode(tokenString[i+1:j], buf) 267 if err != nil { 268 return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed} 269 } 270 271 if err = json.Unmarshal(buf[:n], claims); err != nil { 272 return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed} 273 } 274 275 for _, signer := range hmacSigners { 276 if header.Algorithm == signer.Name { 277 return signer, nil 278 } 279 } 280 281 return nil, jwtgo.NewValidationError(fmt.Sprintf("signing method (%s) is unavailable.", header.Algorithm), 282 jwtgo.ValidationErrorUnverifiable) 283 } 284 285 // ParseWithClaims - parse the token string, valid methods. 286 func ParseWithClaims(tokenStr string, claims *MapClaims, fn func(*MapClaims) ([]byte, error)) error { 287 // Key lookup function has to be provided. 288 if fn == nil { 289 // keyFunc was not provided, return error. 290 return jwtgo.NewValidationError("no Keyfunc was provided.", jwtgo.ValidationErrorUnverifiable) 291 } 292 293 bufp := base64BufPool.Get().(*[]byte) 294 defer base64BufPool.Put(bufp) 295 296 signer, err := ParseUnverifiedMapClaims(tokenStr, claims, *bufp) 297 if err != nil { 298 return err 299 } 300 301 i := strings.LastIndex(tokenStr, ".") 302 if i < 0 { 303 return jwtgo.ErrSignatureInvalid 304 } 305 306 n, err := base64Decode(tokenStr[i+1:], *bufp) 307 if err != nil { 308 return err 309 } 310 311 var ok bool 312 claims.AccessKey, ok = claims.Lookup("accessKey") 313 if !ok { 314 claims.AccessKey, ok = claims.Lookup("sub") 315 if !ok { 316 return jwtgo.NewValidationError("accessKey/sub missing", 317 jwtgo.ValidationErrorClaimsInvalid) 318 } 319 } 320 321 // Lookup key from claims, claims may not be valid and may return 322 // invalid key which is okay as the signature verification will fail. 323 key, err := fn(claims) 324 if err != nil { 325 return err 326 } 327 328 hasher := hmac.New(signer.Hash.New, key) 329 hasher.Write([]byte(tokenStr[:i])) 330 if !hmac.Equal((*bufp)[:n], hasher.Sum(nil)) { 331 return jwtgo.ErrSignatureInvalid 332 } 333 334 // Signature is valid, lets validate the claims for 335 // other fields such as expiry etc. 336 return claims.Valid() 337 } 338 339 // base64Decode returns the bytes represented by the base64 string s. 340 func base64Decode(s string, buf []byte) (int, error) { 341 return base64.RawURLEncoding.Decode(buf, []byte(s)) 342 } 343 344 // ParseUnverifiedMapClaims - WARNING: Don't use this method unless you know what you're doing 345 // 346 // This method parses the token but doesn't validate the signature. It's only 347 // ever useful in cases where you know the signature is valid (because it has 348 // been checked previously in the stack) and you want to extract values from 349 // it. 350 func ParseUnverifiedMapClaims(tokenString string, claims *MapClaims, buf []byte) (*SigningMethodHMAC, error) { 351 if strings.Count(tokenString, ".") != 2 { 352 return nil, jwtgo.ErrSignatureInvalid 353 } 354 355 i := strings.Index(tokenString, ".") 356 j := strings.LastIndex(tokenString, ".") 357 358 n, err := base64Decode(tokenString[:i], buf) 359 if err != nil { 360 return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed} 361 } 362 363 var header = jwtHeader{} 364 var json = jsoniter.ConfigCompatibleWithStandardLibrary 365 if err = json.Unmarshal(buf[:n], &header); err != nil { 366 return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed} 367 } 368 369 n, err = base64Decode(tokenString[i+1:j], buf) 370 if err != nil { 371 return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed} 372 } 373 374 if err = json.Unmarshal(buf[:n], &claims.MapClaims); err != nil { 375 return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed} 376 } 377 378 for _, signer := range hmacSigners { 379 if header.Algorithm == signer.Name { 380 return signer, nil 381 } 382 } 383 384 return nil, jwtgo.NewValidationError(fmt.Sprintf("signing method (%s) is unavailable.", header.Algorithm), 385 jwtgo.ValidationErrorUnverifiable) 386 }