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  }