github.com/hellofresh/janus@v0.0.0-20230925145208-ce8de8183c67/pkg/jwt/parser_test.go (about)

     1  package jwt
     2  
     3  import (
     4  	"crypto/x509"
     5  	"encoding/pem"
     6  	"errors"
     7  	"net/http"
     8  	"net/url"
     9  	"testing"
    10  	"time"
    11  
    12  	baseJWT "github.com/dgrijalva/jwt-go"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  func TestParser_ParseFromRequest_jwtFromHeader(t *testing.T) {
    18  	alg := "HS256"
    19  	key := time.Now().Format(time.RFC3339Nano)
    20  
    21  	tokenString, err := generateToken(alg, key)
    22  	require.NoError(t, err)
    23  
    24  	req := &http.Request{Header: http.Header{}}
    25  
    26  	config := NewParserConfig(0, SigningMethod{Alg: alg, Key: key})
    27  	parser := NewParser(config)
    28  
    29  	_, err = parser.ParseFromRequest(req)
    30  	assert.Error(t, err)
    31  
    32  	req.Header.Set("Authorization", "Basic "+tokenString)
    33  	_, err = parser.ParseFromRequest(req)
    34  	assert.Error(t, err)
    35  
    36  	req.Header.Set("Authorization", "Bearer "+tokenString)
    37  
    38  	assertParseToken(t, parser, req)
    39  }
    40  
    41  func TestParser_ParseFromRequest_jwtFromQuery(t *testing.T) {
    42  	alg := "HS256"
    43  	key := time.Now().Format(time.RFC3339Nano)
    44  
    45  	tokenString, err := generateToken(alg, key)
    46  	require.NoError(t, err)
    47  
    48  	config := NewParserConfig(0, SigningMethod{Alg: alg, Key: key})
    49  	config.TokenLookup = "query:token"
    50  	parser := NewParser(config)
    51  
    52  	req := &http.Request{URL: &url.URL{}}
    53  
    54  	_, err = parser.ParseFromRequest(req)
    55  	assert.Error(t, err)
    56  
    57  	req.URL.RawQuery = "asd=qwe&token=" + tokenString
    58  
    59  	assertParseToken(t, parser, req)
    60  }
    61  
    62  func TestParser_ParseFromRequest_jwtFromCookie(t *testing.T) {
    63  	alg := "HS256"
    64  	key := time.Now().Format(time.RFC3339Nano)
    65  
    66  	tokenString, err := generateToken(alg, key)
    67  	require.NoError(t, err)
    68  
    69  	config := NewParserConfig(0, SigningMethod{Alg: alg, Key: key})
    70  	config.TokenLookup = "cookie:token"
    71  	parser := NewParser(config)
    72  
    73  	req := &http.Request{Header: http.Header{}}
    74  
    75  	_, err = parser.ParseFromRequest(req)
    76  	assert.Error(t, err)
    77  
    78  	req.Header.Set("Cookie", "qwe=asd;token="+tokenString)
    79  
    80  	assertParseToken(t, parser, req)
    81  }
    82  
    83  func TestParser_Parse(t *testing.T) {
    84  	alg := "RS256"
    85  
    86  	tokenString, err := generateToken(alg, rsa2048Private)
    87  	require.NoError(t, err)
    88  
    89  	config := NewParserConfig(0, SigningMethod{Alg: "HS256", Key: time.Now().Format(time.RFC3339Nano)})
    90  	parser := NewParser(config)
    91  
    92  	req := &http.Request{Header: http.Header{"Authorization": {"Bearer " + tokenString}}}
    93  
    94  	_, err = parser.ParseFromRequest(req)
    95  	require.Error(t, err)
    96  
    97  	parser.Config.SigningMethods = append(parser.Config.SigningMethods, SigningMethod{Alg: alg, Key: rsa2048Public})
    98  
    99  	assertParseToken(t, parser, req)
   100  }
   101  
   102  func TestParser_Parse_ErrInvalidPEMBlock(t *testing.T) {
   103  	alg := "RS256"
   104  
   105  	tokenString, err := generateToken(alg, rsa2048Private)
   106  	require.NoError(t, err)
   107  
   108  	config := NewParserConfig(0, SigningMethod{Alg: alg, Key: "invalid public key"})
   109  	parser := NewParser(config)
   110  
   111  	req := &http.Request{Header: http.Header{"Authorization": {"Bearer " + tokenString}}}
   112  
   113  	_, err = parser.ParseFromRequest(req)
   114  	assert.Error(t, err)
   115  }
   116  
   117  func TestParser_Parse_ErrNotRSAPublicKey(t *testing.T) {
   118  	alg := "RS256"
   119  
   120  	tokenString, err := generateToken(alg, rsa2048Private)
   121  	require.NoError(t, err)
   122  
   123  	config := NewParserConfig(0, SigningMethod{Alg: alg, Key: rsa2048Private})
   124  	parser := NewParser(config)
   125  
   126  	req := &http.Request{Header: http.Header{"Authorization": {"Bearer " + tokenString}}}
   127  
   128  	_, err = parser.ParseFromRequest(req)
   129  	assert.Error(t, err)
   130  }
   131  
   132  func TestParser_Parse_ParsePKIXPublicKey(t *testing.T) {
   133  	alg := "RS256"
   134  
   135  	tokenString, err := generateToken(alg, rsa2048Private)
   136  	require.NoError(t, err)
   137  
   138  	config := NewParserConfig(0, SigningMethod{Alg: alg, Key: `-----BEGIN PUBLIC KEY-----
   139  AIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHA+KjSHPzVp7HIVGrQv
   140  xUNnYrfdUR5+MRn3SM/Ts7GwwfifIlTgqjRHjm+sPOhVauF+ZX5PkUmW/HBlOxsj
   141  zA55mCBFymO+nyjl/DhhFNLnKu3IWL8Q3IsnM1EgE9FHgwFqf3X5Eh5h2NdsOWPk
   142  FrOB6BKY1wkOI2E27bNJAat+F059HU9z+jgIwhcm/IciTbqts497x1can4+NFBOl
   143  VWE+yii6tREHF8olVe9a1DA8k7mtOQ2+1bK69kxm7tIde5sWnrlG3dv0gvlF25b3
   144  XhiFAMJ1RfgvQHjXbtmMaZdMxj/Kx4CvBM37eXBGUSDWt9q97g+ywIQ/NrPZWmHo
   145  ewIDAQAB
   146  -----END PUBLIC KEY-----`})
   147  	parser := NewParser(config)
   148  
   149  	req := &http.Request{Header: http.Header{"Authorization": {"Bearer " + tokenString}}}
   150  
   151  	_, err = parser.ParseFromRequest(req)
   152  	assert.Error(t, err)
   153  }
   154  
   155  func TestParser_Parse_ErrBadPublicKey(t *testing.T) {
   156  	alg := "RS256"
   157  
   158  	tokenString, err := generateToken(alg, rsa2048Private)
   159  	require.NoError(t, err)
   160  
   161  	// DSA public key
   162  	// ssh-keygen -t dsa
   163  	// openssl dsa -in ~/.ssh/id_dsa -outform pem > dsa_priv.pem
   164  	// openssl dsa -in dsa_priv.pem -pubout -out dsa_pub.pem
   165  	config := NewParserConfig(0, SigningMethod{Alg: alg, Key: `-----BEGIN PUBLIC KEY-----
   166  MIIBtjCCASsGByqGSM44BAEwggEeAoGBAIATdqQyUyprc8NtzuttJz8JahT+vwVK
   167  4d2eVufm5IHyuqUyYroPQYpjQ1AfHOTE3ntmNFJcF+KhyqCTVdnaWwrmfiH2H6+D
   168  2b+O8J50QFONKgktxy5LSBpUIIJfbhJG6vWW5GETnKOC7unoMh7yWDkDBYx+sSdg
   169  ePBlI+Lq2+V1AhUA5e2ydAoXe0xa2lmoQDq09s+YsJUCgYBAMk28rTCuPLw+a7LF
   170  ++ouIDVMfxc/r8+/L4RCX+B5IScsk8SyzPeYdFtnCSGklQMdMw6YXCPdHGcexK/F
   171  F7i9t5vxpD98aWRrJBW5fE99CPUXuMO6Gn8kV+1flRoBeBjPCd807BH/VEgcPGB4
   172  ipAeCcl1yxfxyM6xARg4Fm/L7AOBhAACgYBESpculbUlOxvLK8tnYNI55T3eKGXw
   173  9oSpxhgEzczq98PhaDu+ajjOqdD7DrM/VyvQuOwvhChPDTOlhazRZwyPCX1lUWnY
   174  gXWfkeyb1H3jXz0cOQe2iHCSSMSYr2sH/E7kOMknJClemVFjWC7KO1F1yFXAspPs
   175  V2pT9Twi0IeXmw==
   176  -----END PUBLIC KEY-----`})
   177  	parser := NewParser(config)
   178  
   179  	req := &http.Request{Header: http.Header{"Authorization": {"Bearer " + tokenString}}}
   180  
   181  	_, err = parser.ParseFromRequest(req)
   182  	assert.Error(t, err)
   183  }
   184  
   185  func TestParser_Parse_ErrUnsupportedSigningMethod(t *testing.T) {
   186  	alg := "PS256"
   187  
   188  	tokenString, err := generateToken(alg, rsa2048Private)
   189  	require.NoError(t, err)
   190  
   191  	config := NewParserConfig(0, SigningMethod{Alg: alg, Key: rsa2048Public})
   192  	parser := NewParser(config)
   193  
   194  	req := &http.Request{Header: http.Header{"Authorization": {"Bearer " + tokenString}}}
   195  
   196  	_, err = parser.ParseFromRequest(req)
   197  	assert.Error(t, err)
   198  }
   199  
   200  const (
   201  	clientID = "test-client-id"
   202  	userName = "test@hellofresh.com"
   203  
   204  	rsa2048Private = `-----BEGIN RSA PRIVATE KEY-----
   205  MIIEowIBAAKCAQEAvHA+KjSHPzVp7HIVGrQvxUNnYrfdUR5+MRn3SM/Ts7Gwwfif
   206  IlTgqjRHjm+sPOhVauF+ZX5PkUmW/HBlOxsjzA55mCBFymO+nyjl/DhhFNLnKu3I
   207  WL8Q3IsnM1EgE9FHgwFqf3X5Eh5h2NdsOWPkFrOB6BKY1wkOI2E27bNJAat+F059
   208  HU9z+jgIwhcm/IciTbqts497x1can4+NFBOlVWE+yii6tREHF8olVe9a1DA8k7mt
   209  OQ2+1bK69kxm7tIde5sWnrlG3dv0gvlF25b3XhiFAMJ1RfgvQHjXbtmMaZdMxj/K
   210  x4CvBM37eXBGUSDWt9q97g+ywIQ/NrPZWmHoewIDAQABAoIBAFLFbtj1F89Q9AUT
   211  G2gOa8lXUStQnhtKrJ1+zVsjRtdwnralMalP5Rt+OUw8i0h5uUNoZy/HqsWjsHmU
   212  GTM8OZ4hYZHL4zwCUjHxMgx261XNShNWPSGWU568VOy6nr91tta5oYD5Xf1ycQJh
   213  pb0TvpWmJdK9kHssFBTAV/NTRCdB3klSSQ0t9gIfsa7ILYylaQQMyEtO0u6mTDxT
   214  JjAeIWhYrALU12gLQD4jndF9ouzzgut0mcFnQbNt9vhXTEC1ZRghlRL95ELlrSi0
   215  8AMxgtaiMcIeRezDo4Y+SAAPkVzUprlGEts5TBWcP53/BPfo9Mf0WBBDKhjPsIcx
   216  cFKjp5ECgYEA7z691lJPdj9A0xEMZia5ZcCu2yL8DCGFLDEyDAsCfts9RpIAobb5
   217  X/jOvklwXSvgtvkaiZcfMbgeR3KallWYQFN/q28CX/KPFLm7iA+ON5/nEqgOYlTS
   218  /dLb1JQUs2qfNPjpWAzVL4KLO+fZyUXVYo15uw+M/CFMqZxmUoh3LnMCgYEAyaKf
   219  33RUYhO8vZj6oumAddAOVg3t4jqEJ7IkvrbXIyEPQT5P1DmJHWSmeca8b+Y8pvz9
   220  hIeSuygqWLDS3U5y4MfBYFBsQLQqM0KntjOItW0G/1KqM9YkNBIG7Gfk7fGxH2f8
   221  sOEIVA8V9i2HM62k7ZJ+9lxBFJ7BsCq5UBwzE9kCgYAzyVb6T3LX27VCesw+SF+V
   222  QPIYiSgZ0B+tgzCcHr35i6dl4TC10I+GUKsf0XG7GUZZFO7Dnayo7HvRZ2NC62A7
   223  fFeEWlEfR7fk+pc3Sna0X657AVmru0S4oK3pA+y/MXMo2kBYSN7Um+NbokIoKS+Z
   224  V5pj/We9I9AeXrZfYx65NQKBgGd7yjdhuckYPh7Ee6XO1zofzKvHvFYGGDtTR16F
   225  8kY6Ol0OwOO3n7JxLKuFHsMDVA+T+fzho6HgTFN2dNJV58mLW6i1vck7bgke5Xoy
   226  WrBaQ2QYpfeyqKP8uIbuD2U7TN9EfEC/TYnusCPHXANe1C2FqRmBYXlWvStP0gnW
   227  XzSJAoGBALVfBV/WXcTArvbsT2KvwJovZG9kmSiR3ba3iXIeGwvBxtuDyeodz5k8
   228  9S3m+ev58TCf1lYad+FAavr1ro8fbFyZZV6HItz4v377VcljxlvN739ST6R1RY36
   229  PX7Lwn6YrQ2gk9efgKAEmcxBenq6UKkNXEGeiv4vxGG/cuTepQme
   230  -----END RSA PRIVATE KEY-----`
   231  	rsa2048Public = `-----BEGIN PUBLIC KEY-----
   232  MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHA+KjSHPzVp7HIVGrQv
   233  xUNnYrfdUR5+MRn3SM/Ts7GwwfifIlTgqjRHjm+sPOhVauF+ZX5PkUmW/HBlOxsj
   234  zA55mCBFymO+nyjl/DhhFNLnKu3IWL8Q3IsnM1EgE9FHgwFqf3X5Eh5h2NdsOWPk
   235  FrOB6BKY1wkOI2E27bNJAat+F059HU9z+jgIwhcm/IciTbqts497x1can4+NFBOl
   236  VWE+yii6tREHF8olVe9a1DA8k7mtOQ2+1bK69kxm7tIde5sWnrlG3dv0gvlF25b3
   237  XhiFAMJ1RfgvQHjXbtmMaZdMxj/Kx4CvBM37eXBGUSDWt9q97g+ywIQ/NrPZWmHo
   238  ewIDAQAB
   239  -----END PUBLIC KEY-----`
   240  )
   241  
   242  func assertParseToken(t *testing.T, parser *Parser, r *http.Request) {
   243  	token, err := parser.ParseFromRequest(r)
   244  	require.NoError(t, err)
   245  
   246  	claims, ok := parser.GetMapClaims(token)
   247  	assert.True(t, ok)
   248  	assert.Equal(t, clientID, claims["iss"])
   249  	assert.Equal(t, userName, claims["username"])
   250  }
   251  
   252  func generateToken(alg, key string) (string, error) {
   253  	type userClaims struct {
   254  		Username string `json:"username"`
   255  		baseJWT.StandardClaims
   256  	}
   257  
   258  	token := baseJWT.NewWithClaims(baseJWT.GetSigningMethod(alg), userClaims{
   259  		userName,
   260  		baseJWT.StandardClaims{
   261  			Issuer:    clientID,
   262  			IssuedAt:  time.Now().Unix(),
   263  			ExpiresAt: time.Now().Add(time.Hour).Unix(),
   264  		},
   265  	})
   266  
   267  	var signingKey interface{}
   268  	switch token.Method.(type) {
   269  	case *baseJWT.SigningMethodHMAC:
   270  		signingKey = []byte(key)
   271  	case *baseJWT.SigningMethodRSA, *baseJWT.SigningMethodRSAPSS:
   272  		block, _ := pem.Decode([]byte(key))
   273  		if block == nil {
   274  			return "", ErrInvalidPEMBlock
   275  		}
   276  		if got, want := block.Type, "RSA PRIVATE KEY"; got != want {
   277  			return "", errors.New("invalid RSA: expected RSA PRIVATE KEY block type")
   278  		}
   279  
   280  		var err error
   281  		signingKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
   282  		if nil != err {
   283  			return "", err
   284  		}
   285  	default:
   286  		return "", ErrUnsupportedSigningMethod
   287  	}
   288  
   289  	return token.SignedString(signingKey)
   290  }