Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update JWT lib to v5 #4157

Merged
merged 1 commit into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ require (
github.com/dustin/go-humanize v1.0.1
github.com/gavv/httpexpect/v2 v2.16.0
github.com/gofrs/uuid v4.4.0+incompatible
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang-jwt/jwt/v5 v5.0.0
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f
github.com/goodsign/monday v1.0.1
github.com/google/go-querystring v1.1.0
Expand Down Expand Up @@ -67,6 +67,7 @@ require (
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzq
github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f h1:16RtHeWGkJMc80Etb8RPCcKevXGldr57+LOyZt8zOlg=
Expand Down
20 changes: 11 additions & 9 deletions model/bitwarden/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package bitwarden
import (
"strconv"
"strings"
"time"

"github.com/cozy/cozy-stack/model/bitwarden/settings"
"github.com/cozy/cozy-stack/model/instance"
"github.com/cozy/cozy-stack/model/oauth"
"github.com/cozy/cozy-stack/model/permission"
"github.com/cozy/cozy-stack/pkg/consts"
"github.com/cozy/cozy-stack/pkg/crypto"
"github.com/golang-jwt/jwt/v5"
)

// BitwardenScope is the OAuth scope, and it is hard-coded with the doctypes
Expand Down Expand Up @@ -93,7 +95,7 @@ func ParseBitwardenDeviceType(deviceType string) (string, string) {
// apps. It is an access token, with some additional custom fields.
// See https://github.com/bitwarden/jslib/blob/master/common/src/services/token.service.ts
func CreateAccessJWT(i *instance.Instance, c *oauth.Client) (string, error) {
now := crypto.Timestamp()
now := time.Now()
name, err := i.SettingsPublicName()
if err != nil || name == "" {
name = "Anonymous"
Expand All @@ -104,12 +106,12 @@ func CreateAccessJWT(i *instance.Instance, c *oauth.Client) (string, error) {
}
token, err := crypto.NewJWT(i.OAuthSecret, permission.BitwardenClaims{
Claims: permission.Claims{
StandardClaims: crypto.StandardClaims{
Audience: consts.AccessTokenAudience,
RegisteredClaims: jwt.RegisteredClaims{
Audience: jwt.ClaimStrings{consts.AccessTokenAudience},
Issuer: i.Domain,
NotBefore: now - 60,
IssuedAt: now,
ExpiresAt: now + int64(consts.AccessTokenValidityDuration.Seconds()),
NotBefore: jwt.NewNumericDate(now.Add(-60 * time.Second)),
IssuedAt: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(now.Add(consts.AccessTokenValidityDuration)),
Subject: i.ID(),
},
SStamp: stamp,
Expand All @@ -136,10 +138,10 @@ func CreateRefreshJWT(i *instance.Instance, c *oauth.Client) (string, error) {
stamp = settings.SecurityStamp
}
token, err := crypto.NewJWT(i.OAuthSecret, permission.Claims{
StandardClaims: crypto.StandardClaims{
Audience: consts.RefreshTokenAudience,
RegisteredClaims: jwt.RegisteredClaims{
Audience: jwt.ClaimStrings{consts.RefreshTokenAudience},
Issuer: i.Domain,
IssuedAt: crypto.Timestamp(),
IssuedAt: jwt.NewNumericDate(time.Now()),
Subject: c.CouchID,
},
SStamp: stamp,
Expand Down
7 changes: 4 additions & 3 deletions model/instance/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/cozy/cozy-stack/pkg/logger"
"github.com/cozy/cozy-stack/pkg/prefixer"
"github.com/cozy/cozy-stack/pkg/realtime"
"github.com/golang-jwt/jwt/v5"
"github.com/spf13/afero"
)

Expand Down Expand Up @@ -667,10 +668,10 @@ func (i *Instance) MakeJWT(audience, subject, scope, sessionID string, issuedAt
return "", err
}
return crypto.NewJWT(secret, permission.Claims{
StandardClaims: crypto.StandardClaims{
Audience: audience,
RegisteredClaims: jwt.RegisteredClaims{
Audience: jwt.ClaimStrings{audience},
Issuer: i.Domain,
IssuedAt: issuedAt.Unix(),
IssuedAt: jwt.NewNumericDate(issuedAt),
Subject: subject,
},
Scope: scope,
Expand Down
4 changes: 2 additions & 2 deletions model/instance/instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"github.com/cozy/cozy-stack/model/instance"
"github.com/cozy/cozy-stack/pkg/config/config"
"github.com/cozy/cozy-stack/pkg/crypto"
jwt "github.com/golang-jwt/jwt/v4"
jwt "github.com/golang-jwt/jwt/v5"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -47,7 +47,7 @@ func TestInstance(t *testing.T) {

claims, ok := token.Claims.(jwt.MapClaims)
assert.True(t, ok, "Claims can be parsed as standard claims")
assert.Equal(t, "app", claims["aud"])
assert.Equal(t, []interface{}{"app"}, claims["aud"])
assert.Equal(t, "test-ctx-token.example.com", claims["iss"])
assert.Equal(t, "my-app", claims["sub"])
})
Expand Down
2 changes: 1 addition & 1 deletion model/move/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
"github.com/cozy/cozy-stack/pkg/couchdb"
"github.com/cozy/cozy-stack/pkg/crypto"
"github.com/cozy/cozy-stack/pkg/safehttp"
jwt "github.com/golang-jwt/jwt/v4"
jwt "github.com/golang-jwt/jwt/v5"
multierror "github.com/hashicorp/go-multierror"
"github.com/labstack/echo/v4"
)
Expand Down
2 changes: 1 addition & 1 deletion model/oauth/android.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/cozy/cozy-stack/model/instance"
"github.com/cozy/cozy-stack/pkg/config/config"
"github.com/cozy/cozy-stack/pkg/logger"
jwt "github.com/golang-jwt/jwt/v4"
jwt "github.com/golang-jwt/jwt/v5"
)

// checkAndroidAttestation will check an attestation made by the SafetyNet API.
Expand Down
18 changes: 9 additions & 9 deletions model/oauth/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
"github.com/cozy/cozy-stack/pkg/metadata"
"github.com/cozy/cozy-stack/pkg/registry"

jwt "github.com/golang-jwt/jwt/v4"
jwt "github.com/golang-jwt/jwt/v5"
)

const (
Expand Down Expand Up @@ -500,10 +500,10 @@ func (c *Client) Create(i *instance.Instance, opts ...CreateOptions) *ClientRegi
}

var err error
c.RegistrationToken, err = crypto.NewJWT(i.OAuthSecret, crypto.StandardClaims{
Audience: consts.RegistrationTokenAudience,
c.RegistrationToken, err = crypto.NewJWT(i.OAuthSecret, jwt.RegisteredClaims{
Audience: jwt.ClaimStrings{consts.RegistrationTokenAudience},
Issuer: i.Domain,
IssuedAt: time.Now().Unix(),
IssuedAt: jwt.NewNumericDate(time.Now()),
Subject: c.CouchID,
})
if err != nil {
Expand Down Expand Up @@ -730,10 +730,10 @@ func (c *Client) AcceptRedirectURI(u string) bool {
// CreateJWT returns a new JSON Web Token for the given instance and audience
func (c *Client) CreateJWT(i *instance.Instance, audience, scope string) (string, error) {
token, err := crypto.NewJWT(i.OAuthSecret, permission.Claims{
StandardClaims: crypto.StandardClaims{
Audience: audience,
RegisteredClaims: jwt.RegisteredClaims{
Audience: jwt.ClaimStrings{audience},
Issuer: i.Domain,
IssuedAt: crypto.Timestamp(),
IssuedAt: jwt.NewNumericDate(time.Now()),
Subject: c.CouchID,
},
Scope: scope,
Expand Down Expand Up @@ -764,9 +764,9 @@ func validToken(i *instance.Instance, audience, token string) (permission.Claims
return claims, false
}
// Note: the refresh and registration tokens don't expire, no need to check its issue date
if claims.Audience != audience {
if claims.AudienceString() != audience {
i.Logger().WithNamespace("oauth").
Errorf("Unexpected audience for %s token: %s", audience, claims.Audience)
Errorf("Unexpected audience for %s token: %v", audience, claims.Audience)
return claims, false
}
if claims.Issuer != i.Domain {
Expand Down
6 changes: 3 additions & 3 deletions model/oauth/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"github.com/cozy/cozy-stack/pkg/couchdb/mango"
"github.com/cozy/cozy-stack/pkg/metadata"
"github.com/cozy/cozy-stack/tests/testutils"
jwt "github.com/golang-jwt/jwt/v4"
jwt "github.com/golang-jwt/jwt/v5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -51,7 +51,7 @@ func TestClient(t *testing.T) {

claims, ok := token.Claims.(jwt.MapClaims)
assert.True(t, ok, "Claims can be parsed as standard claims")
assert.Equal(t, "test", claims["aud"])
assert.Equal(t, []interface{}{"test"}, claims["aud"])
assert.Equal(t, testInstance.Domain, claims["iss"])
assert.Equal(t, "my-client-id", claims["sub"])
assert.Equal(t, "foo:read", claims["scope"])
Expand All @@ -63,7 +63,7 @@ func TestClient(t *testing.T) {

claims, ok := c.ValidToken(testInstance, consts.RefreshTokenAudience, tokenString)
assert.True(t, ok, "The token must be valid")
assert.Equal(t, "refresh", claims.Audience)
assert.Equal(t, jwt.ClaimStrings{"refresh"}, claims.Audience)
assert.Equal(t, testInstance.Domain, claims.Issuer)
assert.Equal(t, "my-client-id", claims.Subject)
assert.Equal(t, "foo:read", claims.Scope)
Expand Down
10 changes: 7 additions & 3 deletions model/office/callback.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
"github.com/cozy/cozy-stack/pkg/crypto"
"github.com/cozy/cozy-stack/pkg/metadata"

jwt "github.com/golang-jwt/jwt/v4"
jwt "github.com/golang-jwt/jwt/v5"
)

// Status list is described on https://api.onlyoffice.com/editors/callback#status
Expand Down Expand Up @@ -58,8 +58,12 @@ type callbackClaims struct {
} `json:"payload"`
}

// Valid is part of the jwt.Claims interface
func (c *callbackClaims) Valid() error { return nil }
func (c *callbackClaims) GetExpirationTime() (*jwt.NumericDate, error) { return nil, nil }
func (c *callbackClaims) GetIssuedAt() (*jwt.NumericDate, error) { return nil, nil }
func (c *callbackClaims) GetNotBefore() (*jwt.NumericDate, error) { return nil, nil }
func (c *callbackClaims) GetIssuer() (string, error) { return "", nil }
func (c *callbackClaims) GetSubject() (string, error) { return "", nil }
func (c *callbackClaims) GetAudience() (jwt.ClaimStrings, error) { return nil, nil }

// Callback will manage the callback from the document server.
func Callback(inst *instance.Instance, params CallbackParameters) error {
Expand Down
10 changes: 7 additions & 3 deletions model/office/open.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/cozy/cozy-stack/pkg/consts"
"github.com/cozy/cozy-stack/pkg/couchdb"
"github.com/cozy/cozy-stack/pkg/jsonapi"
jwt "github.com/golang-jwt/jwt/v4"
jwt "github.com/golang-jwt/jwt/v5"
)

type apiOfficeURL struct {
Expand Down Expand Up @@ -93,8 +93,12 @@ func (o *apiOfficeURL) sign(cfg *config.Office) (string, error) {
return token.SignedString([]byte(cfg.InboxSecret))
}

// Valid is a method of the jwt.Claims interface
func (o *onlyOffice) Valid() error { return nil }
func (o *onlyOffice) GetExpirationTime() (*jwt.NumericDate, error) { return nil, nil }
func (o *onlyOffice) GetIssuedAt() (*jwt.NumericDate, error) { return nil, nil }
func (o *onlyOffice) GetNotBefore() (*jwt.NumericDate, error) { return nil, nil }
func (o *onlyOffice) GetIssuer() (string, error) { return "", nil }
func (o *onlyOffice) GetSubject() (string, error) { return "", nil }
func (o *onlyOffice) GetAudience() (jwt.ClaimStrings, error) { return nil, nil }

// Opener can be used to find the parameters for opening an office document.
type Opener struct {
Expand Down
20 changes: 16 additions & 4 deletions model/permission/claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import (
"time"

"github.com/cozy/cozy-stack/pkg/consts"
"github.com/cozy/cozy-stack/pkg/crypto"
"github.com/golang-jwt/jwt/v5"
)

// Claims is used for JWT used in OAuth2 flow and applications token
type Claims struct {
crypto.StandardClaims
jwt.RegisteredClaims
Scope string `json:"scope,omitempty"`
SessionID string `json:"session_id,omitempty"`
SStamp string `json:"stamp,omitempty"`
Expand All @@ -18,13 +18,25 @@ type Claims struct {
// IssuedAtUTC returns a time.Time struct of the IssuedAt field in UTC
// location.
func (claims *Claims) IssuedAtUTC() time.Time {
return time.Unix(claims.IssuedAt, 0).UTC()
return claims.IssuedAt.Time.UTC()
}

// AudienceString returns the audience as a string.
func (claims *Claims) AudienceString() string {
if len(claims.Audience) == 0 {
return ""
}
return claims.Audience[0]
}

// Expired returns true if a Claim is expired
func (claims *Claims) Expired() bool {
if len(claims.Audience) != 1 {
return true
}

var validityDuration time.Duration
switch claims.Audience {
switch claims.Audience[0] {
case consts.AppAudience:
if claims.SessionID == "" {
// an app token with no session association is used for services which
Expand Down
2 changes: 1 addition & 1 deletion model/session/delegated.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"errors"
"time"

jwt "github.com/golang-jwt/jwt/v4"
jwt "github.com/golang-jwt/jwt/v5"

"github.com/cozy/cozy-stack/model/instance"
"github.com/cozy/cozy-stack/pkg/config/config"
Expand Down
2 changes: 1 addition & 1 deletion model/session/delegated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/cozy/cozy-stack/model/instance"
"github.com/cozy/cozy-stack/pkg/config/config"

jwt "github.com/golang-jwt/jwt/v4"
jwt "github.com/golang-jwt/jwt/v5"
"github.com/stretchr/testify/assert"
)

Expand Down
2 changes: 1 addition & 1 deletion model/sharing/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
"github.com/cozy/cozy-stack/pkg/couchdb/mango"
"github.com/cozy/cozy-stack/pkg/jsonapi"
"github.com/cozy/cozy-stack/pkg/safehttp"
jwt "github.com/golang-jwt/jwt/v4"
jwt "github.com/golang-jwt/jwt/v5"
"github.com/labstack/echo/v4"
)

Expand Down
39 changes: 1 addition & 38 deletions pkg/crypto/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,10 @@ package crypto
import (
"errors"
"fmt"
"time"

jwt "github.com/golang-jwt/jwt/v4"
jwt "github.com/golang-jwt/jwt/v5"
)

// StandardClaims are a structured version of the JWT Claims Set, as referenced at
// https://datatracker.ietf.org/doc/html/rfc7519#section-4. They do not follow the
// specification exactly, since they were based on an earlier draft of the
// specification and not updated. The main difference is that they only
// support integer-based date fields and singular audiences.
type StandardClaims struct {
Audience string `json:"aud,omitempty"`
ExpiresAt int64 `json:"exp,omitempty"`
IssuedAt int64 `json:"iat,omitempty"`
Issuer string `json:"iss,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
Subject string `json:"sub,omitempty"`
}

// Valid validates time based claims "exp, iat, nbf". There is no accounting
// for clock skew. As well, if any of the above claims are not in the token, it
// will still be considered a valid claim.
func (claims StandardClaims) Valid() error {
now := time.Now().Unix()

if claims.IssuedAt > now {
return fmt.Errorf("token used before issued")
}

// The claims below are optional, by default, so if they are set to the
// default value in Go, let's not fail the verification for them.
if claims.ExpiresAt > 0 && claims.ExpiresAt < now {
return fmt.Errorf("token is expired by %v", now-claims.ExpiresAt)
}
if claims.NotBefore > 0 && claims.NotBefore > now {
return fmt.Errorf("token is not valid yet")
}

return nil
}

// SigningMethod is the algorithm choosed for signing JWT.
// Currently, it is HMAC-SHA-512
var SigningMethod = jwt.SigningMethodHS512
Expand Down
Loading
Loading