github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/jwt/parser.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package jwt 19 20 // This file is a re-implementation of the original code here with some 21 // additional allocation tweaks reproduced using GODEBUG=allocfreetrace=1 22 // original file https://github.com/golang-jwt/jwt/blob/main/parser.go 23 // borrowed under MIT License https://github.com/golang-jwt/jwt/blob/main/LICENSE 24 25 import ( 26 "bytes" 27 "crypto" 28 "crypto/hmac" 29 "encoding/base64" 30 "errors" 31 "fmt" 32 "hash" 33 "sync" 34 "time" 35 36 "github.com/buger/jsonparser" 37 "github.com/dustin/go-humanize" 38 jwtgo "github.com/golang-jwt/jwt/v4" 39 jsoniter "github.com/json-iterator/go" 40 ) 41 42 // SigningMethodHMAC - Implements the HMAC-SHA family of signing methods signing methods 43 // Expects key type of []byte for both signing and validation 44 type SigningMethodHMAC struct { 45 Name string 46 Hash crypto.Hash 47 HasherPool sync.Pool 48 } 49 50 // Specific instances for HS256, HS384, HS512 51 var ( 52 SigningMethodHS256 *SigningMethodHMAC 53 SigningMethodHS384 *SigningMethodHMAC 54 SigningMethodHS512 *SigningMethodHMAC 55 ) 56 57 const base64BufferSize = 64 * humanize.KiByte 58 59 var ( 60 base64BufPool sync.Pool 61 hmacSigners []*SigningMethodHMAC 62 ) 63 64 func init() { 65 base64BufPool = sync.Pool{ 66 New: func() interface{} { 67 buf := make([]byte, base64BufferSize) 68 return &buf 69 }, 70 } 71 72 hmacSigners = []*SigningMethodHMAC{ 73 {Name: "HS256", Hash: crypto.SHA256}, 74 {Name: "HS384", Hash: crypto.SHA384}, 75 {Name: "HS512", Hash: crypto.SHA512}, 76 } 77 for i := range hmacSigners { 78 h := hmacSigners[i].Hash 79 hmacSigners[i].HasherPool.New = func() interface{} { 80 return h.New() 81 } 82 } 83 } 84 85 // HashBorrower allows borrowing hashes and will keep track of them. 86 func (s *SigningMethodHMAC) HashBorrower() HashBorrower { 87 return HashBorrower{pool: &s.HasherPool, borrowed: make([]hash.Hash, 0, 2)} 88 } 89 90 // HashBorrower keeps track of borrowed hashers and allows to return them all. 91 type HashBorrower struct { 92 pool *sync.Pool 93 borrowed []hash.Hash 94 } 95 96 // Borrow a single hasher. 97 func (h *HashBorrower) Borrow() hash.Hash { 98 hasher := h.pool.Get().(hash.Hash) 99 h.borrowed = append(h.borrowed, hasher) 100 hasher.Reset() 101 return hasher 102 } 103 104 // ReturnAll will return all borrowed hashes. 105 func (h *HashBorrower) ReturnAll() { 106 for _, hasher := range h.borrowed { 107 h.pool.Put(hasher) 108 } 109 h.borrowed = nil 110 } 111 112 // StandardClaims are basically standard claims with "accessKey" 113 type StandardClaims struct { 114 AccessKey string `json:"accessKey,omitempty"` 115 jwtgo.StandardClaims 116 } 117 118 // UnmarshalJSON provides custom JSON unmarshal. 119 // This is mainly implemented for speed. 120 func (c *StandardClaims) UnmarshalJSON(b []byte) (err error) { 121 return jsonparser.ObjectEach(b, func(key []byte, value []byte, dataType jsonparser.ValueType, _ int) error { 122 if len(key) == 0 { 123 return nil 124 } 125 switch key[0] { 126 case 'a': 127 if string(key) == "accessKey" { 128 if dataType != jsonparser.String { 129 return errors.New("accessKey: Expected string") 130 } 131 c.AccessKey, err = jsonparser.ParseString(value) 132 return err 133 } 134 if string(key) == "aud" { 135 if dataType != jsonparser.String { 136 return errors.New("aud: Expected string") 137 } 138 c.Audience, err = jsonparser.ParseString(value) 139 return err 140 } 141 case 'e': 142 if string(key) == "exp" { 143 if dataType != jsonparser.Number { 144 return errors.New("exp: Expected number") 145 } 146 c.ExpiresAt, err = jsonparser.ParseInt(value) 147 return err 148 } 149 case 'i': 150 if string(key) == "iat" { 151 if dataType != jsonparser.Number { 152 return errors.New("exp: Expected number") 153 } 154 c.IssuedAt, err = jsonparser.ParseInt(value) 155 return err 156 } 157 if string(key) == "iss" { 158 if dataType != jsonparser.String { 159 return errors.New("iss: Expected string") 160 } 161 c.Issuer, err = jsonparser.ParseString(value) 162 return err 163 } 164 case 'n': 165 if string(key) == "nbf" { 166 if dataType != jsonparser.Number { 167 return errors.New("nbf: Expected number") 168 } 169 c.NotBefore, err = jsonparser.ParseInt(value) 170 return err 171 } 172 case 's': 173 if string(key) == "sub" { 174 if dataType != jsonparser.String { 175 return errors.New("sub: Expected string") 176 } 177 c.Subject, err = jsonparser.ParseString(value) 178 return err 179 } 180 } 181 // Ignore unknown fields 182 return nil 183 }) 184 } 185 186 // MapClaims - implements custom unmarshaller 187 type MapClaims struct { 188 AccessKey string `json:"accessKey,omitempty"` 189 jwtgo.MapClaims 190 } 191 192 // GetAccessKey will return the access key. 193 // If nil an empty string will be returned. 194 func (c *MapClaims) GetAccessKey() string { 195 if c == nil { 196 return "" 197 } 198 return c.AccessKey 199 } 200 201 // NewStandardClaims - initializes standard claims 202 func NewStandardClaims() *StandardClaims { 203 return &StandardClaims{} 204 } 205 206 // SetIssuer sets issuer for these claims 207 func (c *StandardClaims) SetIssuer(issuer string) { 208 c.Issuer = issuer 209 } 210 211 // SetAudience sets audience for these claims 212 func (c *StandardClaims) SetAudience(aud string) { 213 c.Audience = aud 214 } 215 216 // SetExpiry sets expiry in unix epoch secs 217 func (c *StandardClaims) SetExpiry(t time.Time) { 218 c.ExpiresAt = t.Unix() 219 } 220 221 // SetAccessKey sets access key as jwt subject and custom 222 // "accessKey" field. 223 func (c *StandardClaims) SetAccessKey(accessKey string) { 224 c.Subject = accessKey 225 c.AccessKey = accessKey 226 } 227 228 // Valid - implements https://godoc.org/github.com/golang-jwt/jwt#Claims compatible 229 // claims interface, additionally validates "accessKey" fields. 230 func (c *StandardClaims) Valid() error { 231 if err := c.StandardClaims.Valid(); err != nil { 232 return err 233 } 234 235 if c.AccessKey == "" && c.Subject == "" { 236 return jwtgo.NewValidationError("accessKey/sub missing", 237 jwtgo.ValidationErrorClaimsInvalid) 238 } 239 240 return nil 241 } 242 243 // NewMapClaims - Initializes a new map claims 244 func NewMapClaims() *MapClaims { 245 return &MapClaims{MapClaims: jwtgo.MapClaims{}} 246 } 247 248 // Lookup returns the value and if the key is found. 249 func (c *MapClaims) Lookup(key string) (value string, ok bool) { 250 if c == nil { 251 return "", false 252 } 253 var vinterface interface{} 254 vinterface, ok = c.MapClaims[key] 255 if ok { 256 value, ok = vinterface.(string) 257 } 258 return 259 } 260 261 // SetExpiry sets expiry in unix epoch secs 262 func (c *MapClaims) SetExpiry(t time.Time) { 263 c.MapClaims["exp"] = t.Unix() 264 } 265 266 // SetAccessKey sets access key as jwt subject and custom 267 // "accessKey" field. 268 func (c *MapClaims) SetAccessKey(accessKey string) { 269 c.MapClaims["sub"] = accessKey 270 c.MapClaims["accessKey"] = accessKey 271 } 272 273 // Valid - implements https://godoc.org/github.com/golang-jwt/jwt#Claims compatible 274 // claims interface, additionally validates "accessKey" fields. 275 func (c *MapClaims) Valid() error { 276 if err := c.MapClaims.Valid(); err != nil { 277 return err 278 } 279 280 if c.AccessKey == "" { 281 return jwtgo.NewValidationError("accessKey/sub missing", 282 jwtgo.ValidationErrorClaimsInvalid) 283 } 284 285 return nil 286 } 287 288 // Map returns underlying low-level map claims. 289 func (c *MapClaims) Map() map[string]interface{} { 290 if c == nil { 291 return nil 292 } 293 return c.MapClaims 294 } 295 296 // MarshalJSON marshals the MapClaims struct 297 func (c *MapClaims) MarshalJSON() ([]byte, error) { 298 json := jsoniter.ConfigCompatibleWithStandardLibrary 299 return json.Marshal(c.MapClaims) 300 } 301 302 // ParseWithStandardClaims - parse the token string, valid methods. 303 func ParseWithStandardClaims(tokenStr string, claims *StandardClaims, key []byte) error { 304 // Key is not provided. 305 if key == nil { 306 // keyFunc was not provided, return error. 307 return jwtgo.NewValidationError("no key was provided.", jwtgo.ValidationErrorUnverifiable) 308 } 309 310 bufp := base64BufPool.Get().(*[]byte) 311 defer base64BufPool.Put(bufp) 312 313 tokenBuf := base64BufPool.Get().(*[]byte) 314 defer base64BufPool.Put(tokenBuf) 315 316 token := *tokenBuf 317 // Copy token to buffer, truncate to length. 318 token = token[:copy(token[:base64BufferSize], tokenStr)] 319 320 signer, err := ParseUnverifiedStandardClaims(token, claims, *bufp) 321 if err != nil { 322 return err 323 } 324 325 i := bytes.LastIndexByte(token, '.') 326 if i < 0 { 327 return jwtgo.ErrSignatureInvalid 328 } 329 330 n, err := base64DecodeBytes(token[i+1:], *bufp) 331 if err != nil { 332 return err 333 } 334 borrow := signer.HashBorrower() 335 hasher := hmac.New(borrow.Borrow, key) 336 hasher.Write(token[:i]) 337 if !hmac.Equal((*bufp)[:n], hasher.Sum(nil)) { 338 borrow.ReturnAll() 339 return jwtgo.ErrSignatureInvalid 340 } 341 borrow.ReturnAll() 342 343 if claims.AccessKey == "" && claims.Subject == "" { 344 return jwtgo.NewValidationError("accessKey/sub missing", 345 jwtgo.ValidationErrorClaimsInvalid) 346 } 347 348 // Signature is valid, lets validate the claims for 349 // other fields such as expiry etc. 350 return claims.Valid() 351 } 352 353 // ParseUnverifiedStandardClaims - WARNING: Don't use this method unless you know what you're doing 354 // 355 // This method parses the token but doesn't validate the signature. It's only 356 // ever useful in cases where you know the signature is valid (because it has 357 // been checked previously in the stack) and you want to extract values from 358 // it. 359 func ParseUnverifiedStandardClaims(token []byte, claims *StandardClaims, buf []byte) (*SigningMethodHMAC, error) { 360 if bytes.Count(token, []byte(".")) != 2 { 361 return nil, jwtgo.ErrSignatureInvalid 362 } 363 364 i := bytes.IndexByte(token, '.') 365 j := bytes.LastIndexByte(token, '.') 366 367 n, err := base64DecodeBytes(token[:i], buf) 368 if err != nil { 369 return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed} 370 } 371 headerDec := buf[:n] 372 buf = buf[n:] 373 374 alg, _, _, err := jsonparser.Get(headerDec, "alg") 375 if err != nil { 376 return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed} 377 } 378 379 n, err = base64DecodeBytes(token[i+1:j], buf) 380 if err != nil { 381 return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed} 382 } 383 384 if err = claims.UnmarshalJSON(buf[:n]); err != nil { 385 return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed} 386 } 387 388 for _, signer := range hmacSigners { 389 if string(alg) == signer.Name { 390 return signer, nil 391 } 392 } 393 394 return nil, jwtgo.NewValidationError(fmt.Sprintf("signing method (%s) is unavailable.", string(alg)), 395 jwtgo.ValidationErrorUnverifiable) 396 } 397 398 // ParseWithClaims - parse the token string, valid methods. 399 func ParseWithClaims(tokenStr string, claims *MapClaims, fn func(*MapClaims) ([]byte, error)) error { 400 // Key lookup function has to be provided. 401 if fn == nil { 402 // keyFunc was not provided, return error. 403 return jwtgo.NewValidationError("no Keyfunc was provided.", jwtgo.ValidationErrorUnverifiable) 404 } 405 406 bufp := base64BufPool.Get().(*[]byte) 407 defer base64BufPool.Put(bufp) 408 409 tokenBuf := base64BufPool.Get().(*[]byte) 410 defer base64BufPool.Put(tokenBuf) 411 412 token := *tokenBuf 413 // Copy token to buffer, truncate to length. 414 token = token[:copy(token[:base64BufferSize], tokenStr)] 415 416 signer, err := ParseUnverifiedMapClaims(token, claims, *bufp) 417 if err != nil { 418 return err 419 } 420 421 i := bytes.LastIndexByte(token, '.') 422 if i < 0 { 423 return jwtgo.ErrSignatureInvalid 424 } 425 426 n, err := base64DecodeBytes(token[i+1:], *bufp) 427 if err != nil { 428 return err 429 } 430 431 var ok bool 432 claims.AccessKey, ok = claims.Lookup("accessKey") 433 if !ok { 434 claims.AccessKey, ok = claims.Lookup("sub") 435 if !ok { 436 return jwtgo.NewValidationError("accessKey/sub missing", 437 jwtgo.ValidationErrorClaimsInvalid) 438 } 439 } 440 441 // Lookup key from claims, claims may not be valid and may return 442 // invalid key which is okay as the signature verification will fail. 443 key, err := fn(claims) 444 if err != nil { 445 return err 446 } 447 borrow := signer.HashBorrower() 448 hasher := hmac.New(borrow.Borrow, key) 449 hasher.Write([]byte(tokenStr[:i])) 450 if !hmac.Equal((*bufp)[:n], hasher.Sum(nil)) { 451 borrow.ReturnAll() 452 return jwtgo.ErrSignatureInvalid 453 } 454 borrow.ReturnAll() 455 456 // Signature is valid, lets validate the claims for 457 // other fields such as expiry etc. 458 return claims.Valid() 459 } 460 461 // base64DecodeBytes returns the bytes represented by the base64 string s. 462 func base64DecodeBytes(b []byte, buf []byte) (int, error) { 463 return base64.RawURLEncoding.Decode(buf, b) 464 } 465 466 // ParseUnverifiedMapClaims - WARNING: Don't use this method unless you know what you're doing 467 // 468 // This method parses the token but doesn't validate the signature. It's only 469 // ever useful in cases where you know the signature is valid (because it has 470 // been checked previously in the stack) and you want to extract values from 471 // it. 472 func ParseUnverifiedMapClaims(token []byte, claims *MapClaims, buf []byte) (*SigningMethodHMAC, error) { 473 if bytes.Count(token, []byte(".")) != 2 { 474 return nil, jwtgo.ErrSignatureInvalid 475 } 476 477 i := bytes.IndexByte(token, '.') 478 j := bytes.LastIndexByte(token, '.') 479 480 n, err := base64DecodeBytes(token[:i], buf) 481 if err != nil { 482 return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed} 483 } 484 485 headerDec := buf[:n] 486 buf = buf[n:] 487 alg, _, _, err := jsonparser.Get(headerDec, "alg") 488 if err != nil { 489 return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed} 490 } 491 492 n, err = base64DecodeBytes(token[i+1:j], buf) 493 if err != nil { 494 return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed} 495 } 496 497 json := jsoniter.ConfigCompatibleWithStandardLibrary 498 if err = json.Unmarshal(buf[:n], &claims.MapClaims); err != nil { 499 return nil, &jwtgo.ValidationError{Inner: err, Errors: jwtgo.ValidationErrorMalformed} 500 } 501 502 for _, signer := range hmacSigners { 503 if string(alg) == signer.Name { 504 return signer, nil 505 } 506 } 507 508 return nil, jwtgo.NewValidationError(fmt.Sprintf("signing method (%s) is unavailable.", string(alg)), 509 jwtgo.ValidationErrorUnverifiable) 510 }