diff --git a/auth/strategies/jwt/examples_test.go b/auth/strategies/jwt/examples_test.go index ec12ddd..d62667a 100644 --- a/auth/strategies/jwt/examples_test.go +++ b/auth/strategies/jwt/examples_test.go @@ -5,15 +5,12 @@ import ( "net/http" "time" - "github.com/shaj13/go-guardian/v2/auth/strategies/token" - "github.com/shaj13/go-guardian/v2/auth" + "github.com/shaj13/go-guardian/v2/auth/strategies/jwt" + "github.com/shaj13/go-guardian/v2/auth/strategies/token" - gojwt "github.com/dgrijalva/jwt-go/v4" "github.com/shaj13/libcache" _ "github.com/shaj13/libcache/lru" - - "github.com/shaj13/go-guardian/v2/auth/strategies/jwt" ) type RotatedSecrets struct { @@ -32,21 +29,21 @@ func (r RotatedSecrets) KID() string { return r.LatestID } -func (r RotatedSecrets) Get(kid string) (key interface{}, m gojwt.SigningMethod, err error) { +func (r RotatedSecrets) Get(kid string) (key interface{}, alg string, err error) { s, ok := r.Secrtes[kid] if ok { - return s, gojwt.SigningMethodHS256, nil + return s, jwt.HS256, nil } - return nil, nil, fmt.Errorf("Invalid KID %s", kid) + return nil, "", fmt.Errorf("Invalid KID %s", kid) } func Example() { u := auth.NewUserInfo("example", "example", nil, nil) c := libcache.LRU.New(0) s := jwt.StaticSecret{ - ID: "id", - Method: gojwt.SigningMethodHS256, - Secret: []byte("your secret"), + ID: "id", + Algorithm: jwt.HS256, + Secret: []byte("your secret"), } token, err := jwt.IssueAccessToken(u, s) @@ -71,9 +68,9 @@ func Example_scope() { u := auth.NewUserInfo("example", "example", nil, nil) c := libcache.LRU.New(0) s := jwt.StaticSecret{ - ID: "id", - Method: gojwt.SigningMethodHS256, - Secret: []byte("your secret"), + ID: "id", + Algorithm: jwt.HS256, + Secret: []byte("your secret"), } token, err := jwt.IssueAccessToken(u, s, ns) diff --git a/auth/strategies/jwt/jwt.go b/auth/strategies/jwt/jwt.go index adf130d..ba3822f 100644 --- a/auth/strategies/jwt/jwt.go +++ b/auth/strategies/jwt/jwt.go @@ -24,7 +24,7 @@ func GetAuthenticateFunc(s SecretsKeeper, opts ...auth.Option) token.Authenticat if len(c.Scopes) > 0 { token.WithNamedScopes(c.UserInfo, c.Scopes...) } - return c.UserInfo, c.ExpiresAt.Time, err + return c.UserInfo, c.Expiry.Time(), err } } diff --git a/auth/strategies/jwt/options.go b/auth/strategies/jwt/options.go index 1115634..d6d960d 100644 --- a/auth/strategies/jwt/options.go +++ b/auth/strategies/jwt/options.go @@ -3,8 +3,6 @@ package jwt import ( "time" - "github.com/dgrijalva/jwt-go/v4" - "github.com/shaj13/go-guardian/v2/auth" ) @@ -13,13 +11,13 @@ import ( func SetAudience(aud string) auth.Option { return auth.OptionFunc(func(v interface{}) { if t, ok := v.(*accessToken); ok { - t.aud = jwt.ClaimStrings{aud} + t.aud = aud } }) } // SetIssuer sets token issuer(iss), -// Default Value "go-guardian". +// no default value. func SetIssuer(iss string) auth.Option { return auth.OptionFunc(func(v interface{}) { if t, ok := v.(*accessToken); ok { @@ -33,7 +31,7 @@ func SetIssuer(iss string) auth.Option { func SetExpDuration(d time.Duration) auth.Option { return auth.OptionFunc(func(v interface{}) { if t, ok := v.(*accessToken); ok { - t.d = d + t.dur = d } }) } diff --git a/auth/strategies/jwt/options_test.go b/auth/strategies/jwt/options_test.go index b977786..17d1c1f 100644 --- a/auth/strategies/jwt/options_test.go +++ b/auth/strategies/jwt/options_test.go @@ -8,9 +8,10 @@ import ( ) func TestSetAudience(t *testing.T) { - opt := SetAudience("test") + aud := "test" + opt := SetAudience(aud) tk := newAccessToken(nil, opt) - assert.Equal(t, "test", tk.aud[0]) + assert.Equal(t, aud, tk.aud) } func TestSetIssuer(t *testing.T) { @@ -22,5 +23,5 @@ func TestSetIssuer(t *testing.T) { func TestSetExpDuration(t *testing.T) { opt := SetExpDuration(time.Hour) tk := newAccessToken(nil, opt) - assert.Equal(t, time.Hour, tk.d) + assert.Equal(t, time.Hour, tk.dur) } diff --git a/auth/strategies/jwt/secrets_keeper.go b/auth/strategies/jwt/secrets_keeper.go index b1c526f..7aaca4f 100644 --- a/auth/strategies/jwt/secrets_keeper.go +++ b/auth/strategies/jwt/secrets_keeper.go @@ -2,8 +2,6 @@ package jwt import ( "errors" - - "github.com/dgrijalva/jwt-go/v4" ) // SecretsKeeper hold all secrets/keys to sign and parse JWT token @@ -12,15 +10,15 @@ type SecretsKeeper interface { // KID must return the most recently used id if more than one secret/key exists. // https://tools.ietf.org/html/rfc7515#section-4.1.4 KID() string - // Get return's secret/key and the corresponding sign method. - Get(kid string) (key interface{}, m jwt.SigningMethod, err error) + // Get return's secret/key and the corresponding sign algorithm. + Get(kid string) (key interface{}, algorithm string, err error) } // StaticSecret implements the SecretsKeeper and holds only a single secret. type StaticSecret struct { - Secret interface{} - ID string - Method jwt.SigningMethod + Secret interface{} + ID string + Algorithm string } // KID return's secret/key id. @@ -28,12 +26,12 @@ func (s StaticSecret) KID() string { return s.ID } -// Get return's secret/key and the corresponding sign method. -func (s StaticSecret) Get(kid string) (key interface{}, m jwt.SigningMethod, err error) { +// Get return's secret/key and the corresponding sign algorithm. +func (s StaticSecret) Get(kid string) (key interface{}, algorithm string, err error) { if kid != s.ID { msg := "strategies/jwt: Invalid " + kid + " KID" - return nil, nil, errors.New(msg) + return nil, "", errors.New(msg) } - return s.Secret, s.Method, nil + return s.Secret, s.Algorithm, nil } diff --git a/auth/strategies/jwt/secrets_keeper_test.go b/auth/strategies/jwt/secrets_keeper_test.go index 2db3da1..d57ab59 100644 --- a/auth/strategies/jwt/secrets_keeper_test.go +++ b/auth/strategies/jwt/secrets_keeper_test.go @@ -3,33 +3,31 @@ package jwt import ( "testing" - "github.com/dgrijalva/jwt-go/v4" "github.com/stretchr/testify/assert" ) func TestStaticSecretGet(t *testing.T) { t.Run("StaticSecretGet always return same secret", func(t *testing.T) { - method := jwt.SigningMethodHS256 kid := "test-kid" secret := []byte("test-secret") s := StaticSecret{ - ID: kid, - Method: method, - Secret: secret, + ID: kid, + Algorithm: HS256, + Secret: secret, } for i := 0; i < 10; i++ { - gotSecret, gotMethod, err := s.Get(kid) + gotSecret, gotAlg, err := s.Get(kid) assert.NoError(t, err) assert.Equal(t, secret, gotSecret) - assert.Equal(t, method, gotMethod) + assert.Equal(t, HS256, gotAlg) } }) t.Run("StaticSecretGet return error when kid invalid", func(t *testing.T) { s := StaticSecret{} - secret, method, err := s.Get("kid") + secret, alg, err := s.Get("kid") assert.Error(t, err) assert.Nil(t, secret) - assert.Nil(t, method) + assert.Empty(t, alg) }) } diff --git a/auth/strategies/jwt/token.go b/auth/strategies/jwt/token.go index 9382630..3ebefd8 100644 --- a/auth/strategies/jwt/token.go +++ b/auth/strategies/jwt/token.go @@ -4,13 +4,43 @@ import ( "errors" "time" - "github.com/dgrijalva/jwt-go/v4" + "gopkg.in/square/go-jose.v2" + "gopkg.in/square/go-jose.v2/jwt" "github.com/shaj13/go-guardian/v2/auth" ) const headerKID = "kid" +const ( + // EdDSA signature algorithm. + EdDSA = "EdDSA" + // HS256 signature algorithm -- HMAC using SHA-256. + HS256 = "HS256" + // HS384 signature algorithm -- HMAC using SHA-384. + HS384 = "HS384" + // HS512 signature algorithm -- HMAC using SHA-512. + HS512 = "HS512" + // RS256 signature algorithm -- RSASSA-PKCS-v1.5 using SHA-256. + RS256 = "RS256" + // RS384 signature algorithm -- RSASSA-PKCS-v1.5 using SHA-384. + RS384 = "RS384" + // RS512 signature algorithm -- RSASSA-PKCS-v1.5 using SHA-512. + RS512 = "RS512" + // ES256 signature algorithm -- ECDSA using P-256 and SHA-256. + ES256 = "ES256" + // ES384 signature algorithm -- ECDSA using P-384 and SHA-384. + ES384 = "ES384" + // ES512 signature algorithm -- ECDSA using P-521 and SHA-512. + ES512 = "ES512" + // PS256 signature algorithm -- RSASSA-PSS using SHA256 and MGF1-SHA256. + PS256 = "PS256" + // PS384 signature algorithm -- RSASSA-PSS using SHA384 and MGF1-SHA384. + PS384 = "PS384" + // PS512 signature algorithm -- RSASSA-PSS using SHA512 and MGF1-SHA512. + PS512 = "PS512" +) + var ( // ErrMissingKID is returned by Authenticate Strategy method, // when failed to retrieve kid from token header. @@ -29,99 +59,96 @@ func IssueAccessToken(info auth.Info, s SecretsKeeper, opts ...auth.Option) (str type claims struct { UserInfo auth.Info `json:"info"` Scopes []string `json:"scp"` - jwt.StandardClaims + jwt.Claims } type accessToken struct { - s SecretsKeeper - d time.Duration - aud jwt.ClaimStrings - iss string - scp []string + keeper SecretsKeeper + dur time.Duration + aud string + iss string + scp []string } func (at accessToken) issue(info auth.Info) (string, error) { - kid := at.s.KID() - secret, method, err := at.s.Get(kid) + kid := at.keeper.KID() + secret, alg, err := at.keeper.Get(kid) + if err != nil { + return "", err + } + + opt := (&jose.SignerOptions{}).WithType("JWT").WithHeader(headerKID, kid) + key := jose.SigningKey{Algorithm: jose.SignatureAlgorithm(alg), Key: secret} + sig, err := jose.NewSigner(key, opt) + if err != nil { return "", err } now := time.Now().UTC() - exp := now.Add(at.d) + exp := now.Add(at.dur) c := claims{ UserInfo: info, Scopes: at.scp, - StandardClaims: jwt.StandardClaims{ - Subject: info.GetUserName(), + Claims: jwt.Claims{ + Subject: info.GetID(), Issuer: at.iss, - Audience: at.aud, - ExpiresAt: jwt.At(exp), - IssuedAt: jwt.At(now), - NotBefore: jwt.At(now), + Audience: jwt.Audience{at.aud}, + Expiry: jwt.NewNumericDate(exp), + IssuedAt: jwt.NewNumericDate(now), + NotBefore: jwt.NewNumericDate(now), }, } - jt := jwt.NewWithClaims(method, c) - jt.Header[headerKID] = kid - - return jt.SignedString(secret) + return jwt.Signed(sig).Claims(c).CompactSerialize() } func (at accessToken) parse(tstr string) (*claims, error) { - var keyErr error - c := &claims{ UserInfo: auth.NewUserInfo("", "", nil, nil), } - keyFunc := func(jt *jwt.Token) (key interface{}, err error) { - defer func() { - keyErr = err - }() - - v, ok := jt.Header[headerKID] - if !ok { - return nil, ErrMissingKID - } - - kid, ok := v.(string) - if !ok { - return nil, auth.NewTypeError("strategies/jwt: kid", "str", v) - } + jt, err := jwt.ParseSigned(tstr) + if err != nil { + return nil, err + } - secret, method, err := at.s.Get(kid) + if len(jt.Headers) == 0 { + return nil, errors.New("strategies/jwt: : No headers found in JWT token") + } - if err != nil { - return nil, err - } + if len(jt.Headers[0].KeyID) == 0 { + return nil, ErrMissingKID + } - if jt.Header["alg"] != method.Alg() { - return nil, ErrInvalidAlg - } + secret, alg, err := at.keeper.Get(jt.Headers[0].KeyID) - return secret, nil + if err != nil { + return nil, err } - aud := jwt.WithAudience(at.aud[0]) - iss := jwt.WithIssuer(at.iss) - - _, err := jwt.ParseWithClaims(tstr, c, keyFunc, aud, iss) + if jt.Headers[0].Algorithm != alg { + return nil, ErrInvalidAlg + } - if keyErr != nil { - err = keyErr + if err := jt.Claims(secret, c); err != nil { + return nil, err } - return c, err + return c, c.Validate(jwt.Expected{ + Time: time.Now().UTC(), + Issuer: at.iss, + Audience: jwt.Audience{at.aud}, + }) } func newAccessToken(s SecretsKeeper, opts ...auth.Option) *accessToken { t := new(accessToken) - t.s = s - t.aud = jwt.ClaimStrings{""} - t.iss = "go-guardian" - t.d = time.Minute * 5 + t.keeper = s + t.aud = "" + t.iss = "" + t.dur = time.Minute * 5 for _, opt := range opts { opt.Apply(t) } diff --git a/auth/strategies/jwt/token_test.go b/auth/strategies/jwt/token_test.go index bb29284..db6ead3 100644 --- a/auth/strategies/jwt/token_test.go +++ b/auth/strategies/jwt/token_test.go @@ -6,7 +6,6 @@ import ( "github.com/shaj13/go-guardian/v2/auth" - "github.com/dgrijalva/jwt-go/v4" "github.com/stretchr/testify/assert" ) @@ -31,14 +30,14 @@ func TestToken(t *testing.T) { }) tk := new(accessToken) - tk.s = StaticSecret{ - ID: "kid", - Secret: []byte("test-secret"), - Method: jwt.SigningMethodHS256, + tk.keeper = StaticSecret{ + ID: "kid", + Secret: []byte("test-secret"), + Algorithm: HS256, } - tk.d = time.Hour + tk.dur = time.Hour tk.iss = "test-iss" - tk.aud = jwt.ClaimStrings{"test-aud"} + tk.aud = "test-aud" str, err := tk.issue(info) assert.NoError(t, err) @@ -46,22 +45,21 @@ func TestToken(t *testing.T) { c, err := tk.parse(str) assert.NoError(t, err) assert.Equal(t, c.UserInfo, info) - } func TestTokenAlg(t *testing.T) { info := auth.NewDefaultUser("test", "test", nil, nil) hs512 := StaticSecret{ - ID: "kid", - Secret: []byte("test-secret"), - Method: jwt.SigningMethodHS512, + ID: "kid", + Secret: []byte("test-secret"), + Algorithm: HS512, } hs256 := StaticSecret{ - ID: "kid", - Secret: []byte("test-secret"), - Method: jwt.SigningMethodHS256, + ID: "kid", + Secret: []byte("test-secret"), + Algorithm: HS256, } tk := newAccessToken(hs512) @@ -69,27 +67,24 @@ func TestTokenAlg(t *testing.T) { str, err := tk.issue(info) assert.NoError(t, err) - tk.s = hs256 + tk.keeper = hs256 _, err = tk.parse(str) assert.Equal(t, ErrInvalidAlg, err) } func TestTokenKID(t *testing.T) { - str, err := jwt.New(jwt.SigningMethodHS256).SignedString([]byte("test")) - assert.NoError(t, err) - + str := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.P4Lqll22jQQJ1eMJikvNg5HKG-cKB0hUZA9BZFIG7Jk" tk := newAccessToken(nil) - _, err = tk.parse(str) + _, err := tk.parse(str) assert.Equal(t, ErrMissingKID, err) } func TestNewToken(t *testing.T) { tk := newAccessToken(nil) if assert.NotNil(t, tk) { - assert.True(t, len(tk.aud) == 1) - assert.True(t, len(tk.aud[0]) == 0) - assert.True(t, len(tk.iss) > 0) - assert.True(t, tk.d > 0) + assert.True(t, len(tk.aud) == 0) + assert.True(t, len(tk.iss) == 0) + assert.True(t, tk.dur > 0) } } diff --git a/go.mod b/go.mod index 30a5766..c3844d5 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ module github.com/shaj13/go-guardian/v2 go 1.13 require ( - github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 github.com/shaj13/libcache v1.0.0 github.com/stretchr/testify v1.6.1 gopkg.in/ldap.v3 v3.1.0 + gopkg.in/square/go-jose.v2 v2.5.1 k8s.io/api v0.18.8 k8s.io/apimachinery v0.18.8 ) diff --git a/go.sum b/go.sum index acf5ff4..bbcc60c 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,6 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU= -github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= @@ -74,6 +72,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -91,8 +90,6 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= @@ -104,6 +101,8 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ldap.v3 v3.1.0 h1:DIDWEjI7vQWREh0S8X5/NFPCZ3MCVd55LmXKPW4XLGE= gopkg.in/ldap.v3 v3.1.0/go.mod h1:dQjCc0R0kfyFjIlWNMH1DORwUASZyDxo2Ry1B51dXaQ= +gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=