feature: Bump go-jose and require signing algorithms in auth (#4349)

This commit is contained in:
Milos Gajdos 2024-05-30 20:54:20 +01:00 committed by GitHub
commit 675d7e27f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
46 changed files with 628 additions and 319 deletions

View file

@ -168,6 +168,9 @@ auth:
service: token-service
issuer: registry-token-issuer
rootcertbundle: /root/certs/bundle
signingalgorithms:
- EdDSA
- HS256
htpasswd:
realm: basic-realm
path: /path/to/htpasswd
@ -577,6 +580,10 @@ auth:
service: token-service
issuer: registry-token-issuer
rootcertbundle: /root/certs/bundle
signingalgorithms:
- EdDSA
- HS256
- ES512
htpasswd:
realm: basic-realm
path: /path/to/htpasswd
@ -620,7 +627,37 @@ security.
| `rootcertbundle` | yes | The absolute path to the root certificate bundle. This bundle contains the public part of the certificates used to sign authentication tokens. |
| `autoredirect` | no | When set to `true`, `realm` will automatically be set using the Host header of the request as the domain and a path of `/auth/token/`(or specified by `autoredirectpath`), the `realm` URL Scheme will use `X-Forwarded-Proto` header if set, otherwise it will be set to `https`. |
| `autoredirectpath` | no | The path to redirect to if `autoredirect` is set to `true`, default: `/auth/token/`. |
| `signingalgorithms` | no | A list of token signing algorithms to use for verifying token signatures. If left empty the default list of signing algorithms is used. Please see below for allowed values and default. |
Available `signingalgorithms`:
- EdDSA
- HS256
- HS384
- HS512
- RS256
- RS384
- RS512
- ES256
- ES384
- ES512
- PS256
- PS384
- PS512
Default `signingalgorithms`:
- EdDSA
- HS256
- HS384
- HS512
- RS256
- RS384
- RS512
- ES256
- ES384
- ES512
- PS256
- PS384
- PS512
For more information about Token based authentication configuration, see the
[specification](../spec/auth/token.md).

2
go.mod
View file

@ -14,7 +14,7 @@ require (
github.com/distribution/reference v0.6.0
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c
github.com/docker/go-metrics v0.0.1
github.com/go-jose/go-jose/v3 v3.0.3
github.com/go-jose/go-jose/v4 v4.0.2
github.com/google/uuid v1.3.1
github.com/gorilla/handlers v1.5.2
github.com/gorilla/mux v1.8.1

19
go.sum
View file

@ -81,8 +81,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
@ -126,7 +126,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@ -287,7 +286,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -295,7 +293,6 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -311,8 +308,6 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -324,7 +319,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -344,24 +338,16 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -371,7 +357,6 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View file

@ -14,7 +14,7 @@ import (
"strings"
"github.com/distribution/distribution/v3/registry/auth"
"github.com/go-jose/go-jose/v3"
"github.com/go-jose/go-jose/v4"
"github.com/sirupsen/logrus"
)
@ -151,13 +151,14 @@ func (ac authChallenge) SetHeaders(r *http.Request, w http.ResponseWriter) {
// accessController implements the auth.AccessController interface.
type accessController struct {
realm string
autoRedirect bool
autoRedirectPath string
issuer string
service string
rootCerts *x509.CertPool
trustedKeys map[string]crypto.PublicKey
realm string
autoRedirect bool
autoRedirectPath string
issuer string
service string
rootCerts *x509.CertPool
trustedKeys map[string]crypto.PublicKey
signingAlgorithms []jose.SignatureAlgorithm
}
const (
@ -167,13 +168,14 @@ const (
// tokenAccessOptions is a convenience type for handling
// options to the constructor of an accessController.
type tokenAccessOptions struct {
realm string
autoRedirect bool
autoRedirectPath string
issuer string
service string
rootCertBundle string
jwks string
realm string
autoRedirect bool
autoRedirectPath string
issuer string
service string
rootCertBundle string
jwks string
signingAlgorithms []string
}
// checkOptions gathers the necessary options
@ -206,7 +208,7 @@ func checkOptions(options map[string]interface{}) (tokenAccessOptions, error) {
if ok {
autoRedirect, ok := autoRedirectVal.(bool)
if !ok {
return opts, fmt.Errorf("token auth requires a valid option bool: autoredirect")
return opts, errors.New("token auth requires a valid option bool: autoredirect")
}
opts.autoRedirect = autoRedirect
}
@ -215,7 +217,7 @@ func checkOptions(options map[string]interface{}) (tokenAccessOptions, error) {
if ok {
autoRedirectPath, ok := autoRedirectPathVal.(string)
if !ok {
return opts, fmt.Errorf("token auth requires a valid option string: autoredirectpath")
return opts, errors.New("token auth requires a valid option string: autoredirectpath")
}
opts.autoRedirectPath = autoRedirectPath
}
@ -224,6 +226,15 @@ func checkOptions(options map[string]interface{}) (tokenAccessOptions, error) {
}
}
signingAlgos, ok := options["signingalgorithms"]
if ok {
signingAlgorithmsVals, ok := signingAlgos.([]string)
if !ok {
return opts, errors.New("signingalgorithms must be a list of signing algorithms")
}
opts.signingAlgorithms = signingAlgorithmsVals
}
return opts, nil
}
@ -279,6 +290,18 @@ func getJwks(path string) (*jose.JSONWebKeySet, error) {
return &jwks, nil
}
func getSigningAlgorithms(algos []string) ([]jose.SignatureAlgorithm, error) {
signAlgVals := make([]jose.SignatureAlgorithm, 0, len(algos))
for _, alg := range algos {
alg, ok := signingAlgorithms[alg]
if !ok {
return nil, fmt.Errorf("unsupported signing algorithm: %s", alg)
}
signAlgVals = append(signAlgVals, alg)
}
return signAlgVals, nil
}
// newAccessController creates an accessController using the given options.
func newAccessController(options map[string]interface{}) (auth.AccessController, error) {
config, err := checkOptions(options)
@ -289,6 +312,7 @@ func newAccessController(options map[string]interface{}) (auth.AccessController,
var (
rootCerts []*x509.Certificate
jwks *jose.JSONWebKeySet
signAlgos []jose.SignatureAlgorithm
)
if config.rootCertBundle != "" {
@ -322,14 +346,25 @@ func newAccessController(options map[string]interface{}) (auth.AccessController,
}
}
signAlgos, err = getSigningAlgorithms(config.signingAlgorithms)
if err != nil {
return nil, err
}
if len(signAlgos) == 0 {
// NOTE: this is to maintain backwards compat
// with existing registry deployments
signAlgos = defaultSigningAlgorithms
}
return &accessController{
realm: config.realm,
autoRedirect: config.autoRedirect,
autoRedirectPath: config.autoRedirectPath,
issuer: config.issuer,
service: config.service,
rootCerts: rootPool,
trustedKeys: trustedKeys,
realm: config.realm,
autoRedirect: config.autoRedirect,
autoRedirectPath: config.autoRedirectPath,
issuer: config.issuer,
service: config.service,
rootCerts: rootPool,
trustedKeys: trustedKeys,
signingAlgorithms: signAlgos,
}, nil
}
@ -350,7 +385,7 @@ func (ac *accessController) Authorized(req *http.Request, accessItems ...auth.Ac
return nil, challenge
}
token, err := NewToken(rawToken)
token, err := NewToken(rawToken, ac.signingAlgorithms)
if err != nil {
challenge.err = err
return nil, challenge

View file

@ -4,6 +4,7 @@ import (
"testing"
fuzz "github.com/AdaLogics/go-fuzz-headers"
"github.com/go-jose/go-jose/v4"
)
func FuzzToken1(f *testing.F) {
@ -18,7 +19,7 @@ func FuzzToken1(f *testing.F) {
if err != nil {
return
}
token, err := NewToken(rawToken)
token, err := NewToken(rawToken, []jose.SignatureAlgorithm{jose.EdDSA, jose.RS384})
if err != nil {
return
}

View file

@ -7,8 +7,8 @@ import (
"fmt"
"time"
"github.com/go-jose/go-jose/v3"
"github.com/go-jose/go-jose/v3/jwt"
"github.com/go-jose/go-jose/v4"
"github.com/go-jose/go-jose/v4/jwt"
log "github.com/sirupsen/logrus"
"github.com/distribution/distribution/v3/registry/auth"
@ -23,6 +23,38 @@ const (
Leeway = 60 * time.Second
)
var signingAlgorithms = map[string]jose.SignatureAlgorithm{
"EdDSA": jose.EdDSA,
"HS256": jose.HS256,
"HS384": jose.HS384,
"HS512": jose.HS512,
"RS256": jose.RS256,
"RS384": jose.RS384,
"RS512": jose.RS512,
"ES256": jose.ES256,
"ES384": jose.ES384,
"ES512": jose.ES512,
"PS256": jose.PS256,
"PS384": jose.PS384,
"PS512": jose.PS512,
}
var defaultSigningAlgorithms = []jose.SignatureAlgorithm{
jose.EdDSA,
jose.HS256,
jose.HS384,
jose.HS512,
jose.RS256,
jose.RS384,
jose.RS512,
jose.ES256,
jose.ES384,
jose.ES512,
jose.PS256,
jose.PS384,
jose.PS512,
}
// Errors used by token parsing and verification.
var (
ErrMalformedToken = errors.New("malformed token")
@ -69,8 +101,8 @@ type VerifyOptions struct {
// NewToken parses the given raw token string
// and constructs an unverified JSON Web Token.
func NewToken(rawToken string) (*Token, error) {
token, err := jwt.ParseSigned(rawToken)
func NewToken(rawToken string, signingAlgs []jose.SignatureAlgorithm) (*Token, error) {
token, err := jwt.ParseSigned(rawToken, signingAlgs)
if err != nil {
return nil, ErrMalformedToken
}
@ -140,6 +172,13 @@ func (t *Token) VerifySigningKey(verifyOpts VerifyOptions) (signingKey crypto.Pu
// verifying the first one in the list only at the moment.
header := t.JWT.Headers[0]
signingKey, err = verifyCertChain(header, verifyOpts.Roots)
// NOTE(milosgajdos): if the x5c header is missing
// the token may have been signed by a JWKS.
if err != nil && err != jose.ErrMissingX5cHeader {
return
}
switch {
case header.JSONWebKey != nil:
signingKey, err = verifyJWK(header, verifyOpts)
@ -149,7 +188,7 @@ func (t *Token) VerifySigningKey(verifyOpts VerifyOptions) (signingKey crypto.Pu
err = fmt.Errorf("token signed by untrusted key with ID: %q", header.KeyID)
}
default:
signingKey, err = verifyCertChain(header, verifyOpts.Roots)
err = ErrInvalidToken
}
return

View file

@ -19,8 +19,8 @@ import (
"time"
"github.com/distribution/distribution/v3/registry/auth"
"github.com/go-jose/go-jose/v3"
"github.com/go-jose/go-jose/v3/jwt"
"github.com/go-jose/go-jose/v4"
"github.com/go-jose/go-jose/v4/jwt"
)
func makeRootKeys(numKeys int) ([]*ecdsa.PrivateKey, error) {
@ -123,12 +123,12 @@ func makeTestToken(jwk *jose.JSONWebKey, issuer, audience string, access []*Reso
Access: access,
}
tokenString, err := jwt.Signed(signer).Claims(claimSet).CompactSerialize()
tokenString, err := jwt.Signed(signer).Claims(claimSet).Serialize()
if err != nil {
return nil, fmt.Errorf("unable to build token string: %v", err)
}
return NewToken(tokenString)
return NewToken(tokenString, []jose.SignatureAlgorithm{signingKey.Algorithm})
}
// NOTE(milosgajdos): certTemplateInfo type as well

View file

@ -1,133 +0,0 @@
/*-
* Copyright 2016 Zbigniew Mandziejewicz
* Copyright 2016 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jwt
import (
"fmt"
"strings"
jose "github.com/go-jose/go-jose/v3"
"github.com/go-jose/go-jose/v3/json"
)
// JSONWebToken represents a JSON Web Token (as specified in RFC7519).
type JSONWebToken struct {
payload func(k interface{}) ([]byte, error)
unverifiedPayload func() []byte
Headers []jose.Header
}
type NestedJSONWebToken struct {
enc *jose.JSONWebEncryption
Headers []jose.Header
}
// Claims deserializes a JSONWebToken into dest using the provided key.
func (t *JSONWebToken) Claims(key interface{}, dest ...interface{}) error {
b, err := t.payload(key)
if err != nil {
return err
}
for _, d := range dest {
if err := json.Unmarshal(b, d); err != nil {
return err
}
}
return nil
}
// UnsafeClaimsWithoutVerification deserializes the claims of a
// JSONWebToken into the dests. For signed JWTs, the claims are not
// verified. This function won't work for encrypted JWTs.
func (t *JSONWebToken) UnsafeClaimsWithoutVerification(dest ...interface{}) error {
if t.unverifiedPayload == nil {
return fmt.Errorf("go-jose/go-jose: Cannot get unverified claims")
}
claims := t.unverifiedPayload()
for _, d := range dest {
if err := json.Unmarshal(claims, d); err != nil {
return err
}
}
return nil
}
func (t *NestedJSONWebToken) Decrypt(decryptionKey interface{}) (*JSONWebToken, error) {
b, err := t.enc.Decrypt(decryptionKey)
if err != nil {
return nil, err
}
sig, err := ParseSigned(string(b))
if err != nil {
return nil, err
}
return sig, nil
}
// ParseSigned parses token from JWS form.
func ParseSigned(s string) (*JSONWebToken, error) {
sig, err := jose.ParseSigned(s)
if err != nil {
return nil, err
}
headers := make([]jose.Header, len(sig.Signatures))
for i, signature := range sig.Signatures {
headers[i] = signature.Header
}
return &JSONWebToken{
payload: sig.Verify,
unverifiedPayload: sig.UnsafePayloadWithoutVerification,
Headers: headers,
}, nil
}
// ParseEncrypted parses token from JWE form.
func ParseEncrypted(s string) (*JSONWebToken, error) {
enc, err := jose.ParseEncrypted(s)
if err != nil {
return nil, err
}
return &JSONWebToken{
payload: enc.Decrypt,
Headers: []jose.Header{enc.Header},
}, nil
}
// ParseSignedAndEncrypted parses signed-then-encrypted token from JWE form.
func ParseSignedAndEncrypted(s string) (*NestedJSONWebToken, error) {
enc, err := jose.ParseEncrypted(s)
if err != nil {
return nil, err
}
contentType, _ := enc.Header.ExtraHeaders[jose.HeaderContentType].(string)
if strings.ToUpper(contentType) != "JWT" {
return nil, ErrInvalidContentType
}
return &NestedJSONWebToken{
enc: enc,
Headers: []jose.Header{enc.Header},
}, nil
}

View file

@ -45,12 +45,6 @@ token".
[1]: https://i.blackhat.com/BH-US-23/Presentations/US-23-Tervoort-Three-New-Attacks-Against-JSON-Web-Tokens.pdf
# v3.0.3
## Fixed
- Limit decompression output size to prevent a DoS. Backport from v4.0.1.
# v3.0.2
## Fixed

View file

@ -1,17 +1,9 @@
# Go JOSE
### Versions
[Version 4](https://github.com/go-jose/go-jose)
([branch](https://github.com/go-jose/go-jose/),
[doc](https://pkg.go.dev/github.com/go-jose/go-jose/v4), [releases](https://github.com/go-jose/go-jose/releases)) is the current stable version:
import "github.com/go-jose/go-jose/v4"
The old [square/go-jose](https://github.com/square/go-jose) repo contains the prior v1 and v2 versions, which
are deprecated.
### Summary
[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4)
[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4/jwt.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4/jwt)
[![license](https://img.shields.io/badge/license-apache_2.0-blue.svg?style=flat)](https://raw.githubusercontent.com/go-jose/go-jose/master/LICENSE)
[![test](https://img.shields.io/github/checks-status/go-jose/go-jose/v4)](https://github.com/go-jose/go-jose/actions)
Package jose aims to provide an implementation of the Javascript Object Signing
and Encryption set of standards. This includes support for JSON Web Encryption,
@ -43,6 +35,20 @@ of [case-insensitive matching](https://www.ietf.org/mail-archive/web/json/curren
This is to avoid differences in interpretation of messages between go-jose and
libraries in other languages.
### Versions
[Version 4](https://github.com/go-jose/go-jose)
([branch](https://github.com/go-jose/go-jose/tree/main),
[doc](https://pkg.go.dev/github.com/go-jose/go-jose/v4), [releases](https://github.com/go-jose/go-jose/releases)) is the current stable version:
import "github.com/go-jose/go-jose/v4"
The old [square/go-jose](https://github.com/square/go-jose) repo contains the prior v1 and v2 versions, which
are still useable but not actively developed anymore.
Version 3, in this repo, is still receiving security fixes but not functionality
updates.
### Supported algorithms
See below for a table of supported algorithms. Algorithm identifiers match
@ -98,11 +104,11 @@ allows attaching a key id.
## Examples
[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v3.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v3)
[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v3/jwt.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v3/jwt)
[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4)
[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4/jwt.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4/jwt)
Examples can be found in the Godoc
reference for this package. The
[`jose-util`](https://github.com/go-jose/go-jose/tree/v3/jose-util)
[`jose-util`](https://github.com/go-jose/go-jose/tree/v4/jose-util)
subdirectory also contains a small command-line utility which might be useful
as an example as well.

View file

@ -29,8 +29,8 @@ import (
"fmt"
"math/big"
josecipher "github.com/go-jose/go-jose/v3/cipher"
"github.com/go-jose/go-jose/v3/json"
josecipher "github.com/go-jose/go-jose/v4/cipher"
"github.com/go-jose/go-jose/v4/json"
)
// A generic RSA-based encrypter/verifier

View file

@ -22,7 +22,7 @@ import (
"errors"
"fmt"
"github.com/go-jose/go-jose/v3/json"
"github.com/go-jose/go-jose/v4/json"
)
// Encrypter represents an encrypter which produces an encrypted JWE object.

View file

@ -27,7 +27,7 @@ import (
"strings"
"unicode"
"github.com/go-jose/go-jose/v3/json"
"github.com/go-jose/go-jose/v4/json"
)
// Helper function to serialize known-good objects.
@ -106,10 +106,7 @@ func inflate(input []byte) ([]byte, error) {
output := new(bytes.Buffer)
reader := flate.NewReader(bytes.NewBuffer(input))
maxCompressedSize := 10 * int64(len(input))
if maxCompressedSize < 250000 {
maxCompressedSize = 250000
}
maxCompressedSize := max(250_000, 10*int64(len(input)))
limit := maxCompressedSize + 1
n, err := io.CopyN(output, reader, limit)
@ -167,7 +164,7 @@ func (b *byteBuffer) UnmarshalJSON(data []byte) error {
return nil
}
decoded, err := base64URLDecode(encoded)
decoded, err := base64.RawURLEncoding.DecodeString(encoded)
if err != nil {
return err
}
@ -197,12 +194,6 @@ func (b byteBuffer) toInt() int {
return int(b.bigInt().Int64())
}
// base64URLDecode is implemented as defined in https://www.rfc-editor.org/rfc/rfc7515.html#appendix-C
func base64URLDecode(value string) ([]byte, error) {
value = strings.TrimRight(value, "=")
return base64.RawURLEncoding.DecodeString(value)
}
func base64EncodeLen(sl []byte) int {
return base64.RawURLEncoding.EncodedLen(len(sl))
}

View file

@ -18,10 +18,11 @@ package jose
import (
"encoding/base64"
"errors"
"fmt"
"strings"
"github.com/go-jose/go-jose/v3/json"
"github.com/go-jose/go-jose/v4/json"
)
// rawJSONWebEncryption represents a raw JWE JSON object. Used for parsing/serializing.
@ -104,29 +105,75 @@ func (obj JSONWebEncryption) computeAuthData() []byte {
return output
}
// ParseEncrypted parses an encrypted message in compact or JWE JSON Serialization format.
func ParseEncrypted(input string) (*JSONWebEncryption, error) {
input = stripWhitespace(input)
if strings.HasPrefix(input, "{") {
return parseEncryptedFull(input)
func containsKeyAlgorithm(haystack []KeyAlgorithm, needle KeyAlgorithm) bool {
for _, algorithm := range haystack {
if algorithm == needle {
return true
}
}
return parseEncryptedCompact(input)
return false
}
// parseEncryptedFull parses a message in compact format.
func parseEncryptedFull(input string) (*JSONWebEncryption, error) {
func containsContentEncryption(haystack []ContentEncryption, needle ContentEncryption) bool {
for _, algorithm := range haystack {
if algorithm == needle {
return true
}
}
return false
}
// ParseEncrypted parses an encrypted message in JWE Compact or JWE JSON Serialization.
//
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.1
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.2
//
// The keyAlgorithms and contentEncryption parameters are used to validate the "alg" and "enc"
// header parameters respectively. They must be nonempty, and each "alg" or "enc" header in
// parsed data must contain a value that is present in the corresponding parameter. That
// includes the protected and unprotected headers as well as all recipients. To accept
// multiple algorithms, pass a slice of all the algorithms you want to accept.
func ParseEncrypted(input string,
keyEncryptionAlgorithms []KeyAlgorithm,
contentEncryption []ContentEncryption,
) (*JSONWebEncryption, error) {
input = stripWhitespace(input)
if strings.HasPrefix(input, "{") {
return ParseEncryptedJSON(input, keyEncryptionAlgorithms, contentEncryption)
}
return ParseEncryptedCompact(input, keyEncryptionAlgorithms, contentEncryption)
}
// ParseEncryptedJSON parses a message in JWE JSON Serialization.
//
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.2
func ParseEncryptedJSON(
input string,
keyEncryptionAlgorithms []KeyAlgorithm,
contentEncryption []ContentEncryption,
) (*JSONWebEncryption, error) {
var parsed rawJSONWebEncryption
err := json.Unmarshal([]byte(input), &parsed)
if err != nil {
return nil, err
}
return parsed.sanitized()
return parsed.sanitized(keyEncryptionAlgorithms, contentEncryption)
}
// sanitized produces a cleaned-up JWE object from the raw JSON.
func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
func (parsed *rawJSONWebEncryption) sanitized(
keyEncryptionAlgorithms []KeyAlgorithm,
contentEncryption []ContentEncryption,
) (*JSONWebEncryption, error) {
if len(keyEncryptionAlgorithms) == 0 {
return nil, errors.New("go-jose/go-jose: no key algorithms provided")
}
if len(contentEncryption) == 0 {
return nil, errors.New("go-jose/go-jose: no content encryption algorithms provided")
}
obj := &JSONWebEncryption{
original: parsed,
unprotected: parsed.Unprotected,
@ -170,7 +217,7 @@ func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
} else {
obj.recipients = make([]recipientInfo, len(parsed.Recipients))
for r := range parsed.Recipients {
encryptedKey, err := base64URLDecode(parsed.Recipients[r].EncryptedKey)
encryptedKey, err := base64.RawURLEncoding.DecodeString(parsed.Recipients[r].EncryptedKey)
if err != nil {
return nil, err
}
@ -185,10 +232,31 @@ func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
}
}
for _, recipient := range obj.recipients {
for i, recipient := range obj.recipients {
headers := obj.mergedHeaders(&recipient)
if headers.getAlgorithm() == "" || headers.getEncryption() == "" {
return nil, fmt.Errorf("go-jose/go-jose: message is missing alg/enc headers")
if headers.getAlgorithm() == "" {
return nil, fmt.Errorf(`go-jose/go-jose: recipient %d: missing header "alg"`, i)
}
if headers.getEncryption() == "" {
return nil, fmt.Errorf(`go-jose/go-jose: recipient %d: missing header "enc"`, i)
}
err := validateAlgEnc(headers, keyEncryptionAlgorithms, contentEncryption)
if err != nil {
return nil, fmt.Errorf("go-jose/go-jose: recipient %d: %s", i, err)
}
}
if obj.protected != nil {
err := validateAlgEnc(*obj.protected, keyEncryptionAlgorithms, contentEncryption)
if err != nil {
return nil, fmt.Errorf("go-jose/go-jose: protected header: %s", err)
}
}
if obj.unprotected != nil {
err := validateAlgEnc(*obj.unprotected, keyEncryptionAlgorithms, contentEncryption)
if err != nil {
return nil, fmt.Errorf("go-jose/go-jose: unprotected header: %s", err)
}
}
@ -200,34 +268,52 @@ func (parsed *rawJSONWebEncryption) sanitized() (*JSONWebEncryption, error) {
return obj, nil
}
// parseEncryptedCompact parses a message in compact format.
func parseEncryptedCompact(input string) (*JSONWebEncryption, error) {
func validateAlgEnc(headers rawHeader, keyAlgorithms []KeyAlgorithm, contentEncryption []ContentEncryption) error {
alg := headers.getAlgorithm()
enc := headers.getEncryption()
if alg != "" && !containsKeyAlgorithm(keyAlgorithms, alg) {
return fmt.Errorf("unexpected key algorithm %q; expected %q", alg, keyAlgorithms)
}
if alg != "" && !containsContentEncryption(contentEncryption, enc) {
return fmt.Errorf("unexpected content encryption algorithm %q; expected %q", enc, contentEncryption)
}
return nil
}
// ParseEncryptedCompact parses a message in JWE Compact Serialization.
//
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.1
func ParseEncryptedCompact(
input string,
keyAlgorithms []KeyAlgorithm,
contentEncryption []ContentEncryption,
) (*JSONWebEncryption, error) {
parts := strings.Split(input, ".")
if len(parts) != 5 {
return nil, fmt.Errorf("go-jose/go-jose: compact JWE format must have five parts")
}
rawProtected, err := base64URLDecode(parts[0])
rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0])
if err != nil {
return nil, err
}
encryptedKey, err := base64URLDecode(parts[1])
encryptedKey, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return nil, err
}
iv, err := base64URLDecode(parts[2])
iv, err := base64.RawURLEncoding.DecodeString(parts[2])
if err != nil {
return nil, err
}
ciphertext, err := base64URLDecode(parts[3])
ciphertext, err := base64.RawURLEncoding.DecodeString(parts[3])
if err != nil {
return nil, err
}
tag, err := base64URLDecode(parts[4])
tag, err := base64.RawURLEncoding.DecodeString(parts[4])
if err != nil {
return nil, err
}
@ -240,7 +326,7 @@ func parseEncryptedCompact(input string) (*JSONWebEncryption, error) {
Tag: newBuffer(tag),
}
return raw.sanitized()
return raw.sanitized(keyAlgorithms, contentEncryption)
}
// CompactSerialize serializes an object using the compact serialization format.

View file

@ -35,7 +35,7 @@ import (
"reflect"
"strings"
"github.com/go-jose/go-jose/v3/json"
"github.com/go-jose/go-jose/v4/json"
)
// rawJSONWebKey represents a public or private key in JWK format, used for parsing/serializing.
@ -266,7 +266,7 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
// x5t parameters are base64url-encoded SHA thumbprints
// See RFC 7517, Section 4.8, https://tools.ietf.org/html/rfc7517#section-4.8
x5tSHA1bytes, err := base64URLDecode(raw.X5tSHA1)
x5tSHA1bytes, err := base64.RawURLEncoding.DecodeString(raw.X5tSHA1)
if err != nil {
return errors.New("go-jose/go-jose: invalid JWK, x5t header has invalid encoding")
}
@ -286,7 +286,7 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
k.CertificateThumbprintSHA1 = x5tSHA1bytes
x5tSHA256bytes, err := base64URLDecode(raw.X5tSHA256)
x5tSHA256bytes, err := base64.RawURLEncoding.DecodeString(raw.X5tSHA256)
if err != nil {
return errors.New("go-jose/go-jose: invalid JWK, x5t#S256 header has invalid encoding")
}

View file

@ -23,7 +23,7 @@ import (
"fmt"
"strings"
"github.com/go-jose/go-jose/v3/json"
"github.com/go-jose/go-jose/v4/json"
)
// rawJSONWebSignature represents a raw JWS JSON object. Used for parsing/serializing.
@ -75,22 +75,41 @@ type Signature struct {
original *rawSignatureInfo
}
// ParseSigned parses a signed message in compact or JWS JSON Serialization format.
func ParseSigned(signature string) (*JSONWebSignature, error) {
// ParseSigned parses a signed message in JWS Compact or JWS JSON Serialization.
//
// https://datatracker.ietf.org/doc/html/rfc7515#section-7
func ParseSigned(
signature string,
signatureAlgorithms []SignatureAlgorithm,
) (*JSONWebSignature, error) {
signature = stripWhitespace(signature)
if strings.HasPrefix(signature, "{") {
return parseSignedFull(signature)
return ParseSignedJSON(signature, signatureAlgorithms)
}
return parseSignedCompact(signature, nil)
return parseSignedCompact(signature, nil, signatureAlgorithms)
}
// ParseSignedCompact parses a message in JWS Compact Serialization.
//
// https://datatracker.ietf.org/doc/html/rfc7515#section-7.1
func ParseSignedCompact(
signature string,
signatureAlgorithms []SignatureAlgorithm,
) (*JSONWebSignature, error) {
return parseSignedCompact(signature, nil, signatureAlgorithms)
}
// ParseDetached parses a signed message in compact serialization format with detached payload.
func ParseDetached(signature string, payload []byte) (*JSONWebSignature, error) {
func ParseDetached(
signature string,
payload []byte,
signatureAlgorithms []SignatureAlgorithm,
) (*JSONWebSignature, error) {
if payload == nil {
return nil, errors.New("go-jose/go-jose: nil payload")
}
return parseSignedCompact(stripWhitespace(signature), payload)
return parseSignedCompact(stripWhitespace(signature), payload, signatureAlgorithms)
}
// Get a header value
@ -137,19 +156,36 @@ func (obj JSONWebSignature) computeAuthData(payload []byte, signature *Signature
return authData.Bytes(), nil
}
// parseSignedFull parses a message in full format.
func parseSignedFull(input string) (*JSONWebSignature, error) {
// ParseSignedJSON parses a message in JWS JSON Serialization.
//
// https://datatracker.ietf.org/doc/html/rfc7515#section-7.2
func ParseSignedJSON(
input string,
signatureAlgorithms []SignatureAlgorithm,
) (*JSONWebSignature, error) {
var parsed rawJSONWebSignature
err := json.Unmarshal([]byte(input), &parsed)
if err != nil {
return nil, err
}
return parsed.sanitized()
return parsed.sanitized(signatureAlgorithms)
}
func containsSignatureAlgorithm(haystack []SignatureAlgorithm, needle SignatureAlgorithm) bool {
for _, algorithm := range haystack {
if algorithm == needle {
return true
}
}
return false
}
// sanitized produces a cleaned-up JWS object from the raw JSON.
func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
func (parsed *rawJSONWebSignature) sanitized(signatureAlgorithms []SignatureAlgorithm) (*JSONWebSignature, error) {
if len(signatureAlgorithms) == 0 {
return nil, errors.New("go-jose/go-jose: no signature algorithms specified")
}
if parsed.Payload == nil {
return nil, fmt.Errorf("go-jose/go-jose: missing payload in JWS message")
}
@ -198,6 +234,12 @@ func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
return nil, err
}
alg := SignatureAlgorithm(signature.Header.Algorithm)
if !containsSignatureAlgorithm(signatureAlgorithms, alg) {
return nil, fmt.Errorf("go-jose/go-jose: unexpected signature algorithm %q; expected %q",
alg, signatureAlgorithms)
}
if signature.header != nil {
signature.Unprotected, err = signature.header.sanitized()
if err != nil {
@ -241,6 +283,12 @@ func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
return nil, err
}
alg := SignatureAlgorithm(obj.Signatures[i].Header.Algorithm)
if !containsSignatureAlgorithm(signatureAlgorithms, alg) {
return nil, fmt.Errorf("go-jose/go-jose: unexpected signature algorithm %q; expected %q",
alg, signatureAlgorithms)
}
if obj.Signatures[i].header != nil {
obj.Signatures[i].Unprotected, err = obj.Signatures[i].header.sanitized()
if err != nil {
@ -274,7 +322,11 @@ func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) {
}
// parseSignedCompact parses a message in compact format.
func parseSignedCompact(input string, payload []byte) (*JSONWebSignature, error) {
func parseSignedCompact(
input string,
payload []byte,
signatureAlgorithms []SignatureAlgorithm,
) (*JSONWebSignature, error) {
parts := strings.Split(input, ".")
if len(parts) != 3 {
return nil, fmt.Errorf("go-jose/go-jose: compact JWS format must have three parts")
@ -284,19 +336,19 @@ func parseSignedCompact(input string, payload []byte) (*JSONWebSignature, error)
return nil, fmt.Errorf("go-jose/go-jose: payload is not detached")
}
rawProtected, err := base64URLDecode(parts[0])
rawProtected, err := base64.RawURLEncoding.DecodeString(parts[0])
if err != nil {
return nil, err
}
if payload == nil {
payload, err = base64URLDecode(parts[1])
payload, err = base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return nil, err
}
}
signature, err := base64URLDecode(parts[2])
signature, err := base64.RawURLEncoding.DecodeString(parts[2])
if err != nil {
return nil, err
}
@ -306,7 +358,7 @@ func parseSignedCompact(input string, payload []byte) (*JSONWebSignature, error)
Protected: newBuffer(rawProtected),
Signature: newBuffer(signature),
}
return raw.sanitized()
return raw.sanitized(signatureAlgorithms)
}
func (obj JSONWebSignature) compactSerialize(detached bool) (string, error) {

View file

@ -21,13 +21,13 @@ import (
"bytes"
"reflect"
"github.com/go-jose/go-jose/v3/json"
"github.com/go-jose/go-jose/v4/json"
"github.com/go-jose/go-jose/v3"
"github.com/go-jose/go-jose/v4"
)
// Builder is a utility for making JSON Web Tokens. Calls can be chained, and
// errors are accumulated until the final call to CompactSerialize/FullSerialize.
// errors are accumulated until the final call to Serialize.
type Builder interface {
// Claims encodes claims into JWE/JWS form. Multiple calls will merge claims
// into single JSON object. If you are passing private claims, make sure to set
@ -36,15 +36,13 @@ type Builder interface {
Claims(i interface{}) Builder
// Token builds a JSONWebToken from provided data.
Token() (*JSONWebToken, error)
// FullSerialize serializes a token using the JWS/JWE JSON Serialization format.
FullSerialize() (string, error)
// CompactSerialize serializes a token using the compact serialization format.
CompactSerialize() (string, error)
// Serialize serializes a token.
Serialize() (string, error)
}
// NestedBuilder is a utility for making Signed-Then-Encrypted JSON Web Tokens.
// Calls can be chained, and errors are accumulated until final call to
// CompactSerialize/FullSerialize.
// Serialize.
type NestedBuilder interface {
// Claims encodes claims into JWE/JWS form. Multiple calls will merge claims
// into single JSON object. If you are passing private claims, make sure to set
@ -53,10 +51,8 @@ type NestedBuilder interface {
Claims(i interface{}) NestedBuilder
// Token builds a NestedJSONWebToken from provided data.
Token() (*NestedJSONWebToken, error)
// FullSerialize serializes a token using the JSON Serialization format.
FullSerialize() (string, error)
// CompactSerialize serializes a token using the compact serialization format.
CompactSerialize() (string, error)
// Serialize serializes a token.
Serialize() (string, error)
}
type builder struct {
@ -194,7 +190,7 @@ func (b *signedBuilder) Token() (*JSONWebToken, error) {
return b.builder.token(sig.Verify, h)
}
func (b *signedBuilder) CompactSerialize() (string, error) {
func (b *signedBuilder) Serialize() (string, error) {
sig, err := b.sign()
if err != nil {
return "", err
@ -203,15 +199,6 @@ func (b *signedBuilder) CompactSerialize() (string, error) {
return sig.CompactSerialize()
}
func (b *signedBuilder) FullSerialize() (string, error) {
sig, err := b.sign()
if err != nil {
return "", err
}
return sig.FullSerialize(), nil
}
func (b *signedBuilder) sign() (*jose.JSONWebSignature, error) {
if b.err != nil {
return nil, b.err
@ -232,7 +219,7 @@ func (b *encryptedBuilder) Claims(i interface{}) Builder {
}
}
func (b *encryptedBuilder) CompactSerialize() (string, error) {
func (b *encryptedBuilder) Serialize() (string, error) {
enc, err := b.encrypt()
if err != nil {
return "", err
@ -241,15 +228,6 @@ func (b *encryptedBuilder) CompactSerialize() (string, error) {
return enc.CompactSerialize()
}
func (b *encryptedBuilder) FullSerialize() (string, error) {
enc, err := b.encrypt()
if err != nil {
return "", err
}
return enc.FullSerialize(), nil
}
func (b *encryptedBuilder) Token() (*JSONWebToken, error) {
enc, err := b.encrypt()
if err != nil {
@ -280,6 +258,8 @@ func (b *nestedBuilder) Claims(i interface{}) NestedBuilder {
}
}
// Token produced a token suitable for serialization. It cannot be decrypted
// without serializing and then deserializing.
func (b *nestedBuilder) Token() (*NestedJSONWebToken, error) {
enc, err := b.signAndEncrypt()
if err != nil {
@ -287,12 +267,13 @@ func (b *nestedBuilder) Token() (*NestedJSONWebToken, error) {
}
return &NestedJSONWebToken{
enc: enc,
Headers: []jose.Header{enc.Header},
allowedSignatureAlgorithms: nil,
enc: enc,
Headers: []jose.Header{enc.Header},
}, nil
}
func (b *nestedBuilder) CompactSerialize() (string, error) {
func (b *nestedBuilder) Serialize() (string, error) {
enc, err := b.signAndEncrypt()
if err != nil {
return "", err

View file

@ -21,7 +21,7 @@ import (
"strconv"
"time"
"github.com/go-jose/go-jose/v3/json"
"github.com/go-jose/go-jose/v4/json"
)
// Claims represents public claim values (as specified in RFC 7519).

198
vendor/github.com/go-jose/go-jose/v4/jwt/jwt.go generated vendored Normal file
View file

@ -0,0 +1,198 @@
/*-
* Copyright 2016 Zbigniew Mandziejewicz
* Copyright 2016 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jwt
import (
"fmt"
"strings"
jose "github.com/go-jose/go-jose/v4"
"github.com/go-jose/go-jose/v4/json"
)
// JSONWebToken represents a JSON Web Token (as specified in RFC7519).
type JSONWebToken struct {
payload func(k interface{}) ([]byte, error)
unverifiedPayload func() []byte
Headers []jose.Header
}
type NestedJSONWebToken struct {
enc *jose.JSONWebEncryption
Headers []jose.Header
// Used when parsing and decrypting an input
allowedSignatureAlgorithms []jose.SignatureAlgorithm
}
// Claims deserializes a JSONWebToken into dest using the provided key.
func (t *JSONWebToken) Claims(key interface{}, dest ...interface{}) error {
b, err := t.payload(key)
if err != nil {
return err
}
for _, d := range dest {
if err := json.Unmarshal(b, d); err != nil {
return err
}
}
return nil
}
// UnsafeClaimsWithoutVerification deserializes the claims of a
// JSONWebToken into the dests. For signed JWTs, the claims are not
// verified. This function won't work for encrypted JWTs.
func (t *JSONWebToken) UnsafeClaimsWithoutVerification(dest ...interface{}) error {
if t.unverifiedPayload == nil {
return fmt.Errorf("go-jose/go-jose: Cannot get unverified claims")
}
claims := t.unverifiedPayload()
for _, d := range dest {
if err := json.Unmarshal(claims, d); err != nil {
return err
}
}
return nil
}
func (t *NestedJSONWebToken) Decrypt(decryptionKey interface{}) (*JSONWebToken, error) {
b, err := t.enc.Decrypt(decryptionKey)
if err != nil {
return nil, err
}
sig, err := ParseSigned(string(b), t.allowedSignatureAlgorithms)
if err != nil {
return nil, err
}
return sig, nil
}
// ParseSigned parses token from JWS form.
func ParseSigned(s string, signatureAlgorithms []jose.SignatureAlgorithm) (*JSONWebToken, error) {
sig, err := jose.ParseSignedCompact(s, signatureAlgorithms)
if err != nil {
return nil, err
}
headers := make([]jose.Header, len(sig.Signatures))
for i, signature := range sig.Signatures {
headers[i] = signature.Header
}
return &JSONWebToken{
payload: sig.Verify,
unverifiedPayload: sig.UnsafePayloadWithoutVerification,
Headers: headers,
}, nil
}
func validateKeyEncryptionAlgorithm(algs []jose.KeyAlgorithm) error {
for _, alg := range algs {
switch alg {
case jose.ED25519,
jose.RSA1_5,
jose.RSA_OAEP,
jose.RSA_OAEP_256,
jose.ECDH_ES,
jose.ECDH_ES_A128KW,
jose.ECDH_ES_A192KW,
jose.ECDH_ES_A256KW:
return fmt.Errorf("asymmetric encryption algorithms not supported for JWT: "+
"invalid key encryption algorithm: %s", alg)
case jose.PBES2_HS256_A128KW,
jose.PBES2_HS384_A192KW,
jose.PBES2_HS512_A256KW:
return fmt.Errorf("password-based encryption not supported for JWT: "+
"invalid key encryption algorithm: %s", alg)
}
}
return nil
}
func parseEncryptedCompact(
s string,
keyAlgorithms []jose.KeyAlgorithm,
contentEncryption []jose.ContentEncryption,
) (*jose.JSONWebEncryption, error) {
err := validateKeyEncryptionAlgorithm(keyAlgorithms)
if err != nil {
return nil, err
}
enc, err := jose.ParseEncryptedCompact(s, keyAlgorithms, contentEncryption)
if err != nil {
return nil, err
}
return enc, nil
}
// ParseEncrypted parses token from JWE form.
//
// The keyAlgorithms and contentEncryption parameters are used to validate the "alg" and "enc"
// header parameters respectively. They must be nonempty, and each "alg" or "enc" header in
// parsed data must contain a value that is present in the corresponding parameter. That
// includes the protected and unprotected headers as well as all recipients. To accept
// multiple algorithms, pass a slice of all the algorithms you want to accept.
func ParseEncrypted(s string,
keyAlgorithms []jose.KeyAlgorithm,
contentEncryption []jose.ContentEncryption,
) (*JSONWebToken, error) {
enc, err := parseEncryptedCompact(s, keyAlgorithms, contentEncryption)
if err != nil {
return nil, err
}
return &JSONWebToken{
payload: enc.Decrypt,
Headers: []jose.Header{enc.Header},
}, nil
}
// ParseSignedAndEncrypted parses signed-then-encrypted token from JWE form.
//
// The encryptionKeyAlgorithms and contentEncryption parameters are used to validate the "alg" and "enc"
// header parameters, respectively, of the outer JWE. They must be nonempty, and each "alg" or "enc"
// header in parsed data must contain a value that is present in the corresponding parameter. That
// includes the protected and unprotected headers as well as all recipients. To accept
// multiple algorithms, pass a slice of all the algorithms you want to accept.
//
// The signatureAlgorithms parameter is used to validate the "alg" header parameter of the
// inner JWS. It must be nonempty, and the "alg" header in the inner JWS must contain a value
// that is present in the parameter.
func ParseSignedAndEncrypted(s string,
encryptionKeyAlgorithms []jose.KeyAlgorithm,
contentEncryption []jose.ContentEncryption,
signatureAlgorithms []jose.SignatureAlgorithm,
) (*NestedJSONWebToken, error) {
enc, err := parseEncryptedCompact(s, encryptionKeyAlgorithms, contentEncryption)
if err != nil {
return nil, err
}
contentType, _ := enc.Header.ExtraHeaders[jose.HeaderContentType].(string)
if strings.ToUpper(contentType) != "JWT" {
return nil, ErrInvalidContentType
}
return &NestedJSONWebToken{
allowedSignatureAlgorithms: signatureAlgorithms,
enc: enc,
Headers: []jose.Header{enc.Header},
}, nil
}

View file

@ -33,8 +33,9 @@ type Expected struct {
Issuer string
// Subject matches the "sub" claim exactly.
Subject string
// Audience matches the values in "aud" claim, regardless of their order.
Audience Audience
// AnyAudience matches if there is a non-empty intersection between
// its values and the values in the "aud" claim.
AnyAudience Audience
// ID matches the "jti" claim exactly.
ID string
// Time matches the "exp", "nbf" and "iat" claims with leeway.
@ -88,12 +89,18 @@ func (c Claims) ValidateWithLeeway(e Expected, leeway time.Duration) error {
return ErrInvalidID
}
if len(e.Audience) != 0 {
for _, v := range e.Audience {
if !c.Audience.Contains(v) {
return ErrInvalidAudience
if len(e.AnyAudience) != 0 {
var intersection bool
for _, v := range e.AnyAudience {
if c.Audience.Contains(v) {
intersection = true
break
}
}
if !intersection {
return ErrInvalidAudience
}
}
// validate using the e.Time, or time.Now if not provided

View file

@ -23,7 +23,7 @@ import (
"errors"
"fmt"
"github.com/go-jose/go-jose/v3/json"
"github.com/go-jose/go-jose/v4/json"
)
// KeyAlgorithm represents a key management algorithm.
@ -71,6 +71,12 @@ var (
// ErrUnprotectedNonce indicates that while parsing a JWS or JWE object, a
// nonce header parameter was included in an unprotected header object.
ErrUnprotectedNonce = errors.New("go-jose/go-jose: Nonce parameter included in unprotected header")
// ErrMissingX5cHeader indicates that the JWT header is missing x5c headers.
ErrMissingX5cHeader = errors.New("go-jose/go-jose: no x5c header present in message")
// ErrUnsupportedEllipticCurve indicates unsupported or unknown elliptic curve has been found.
ErrUnsupportedEllipticCurve = errors.New("go-jose/go-jose: unsupported/unknown elliptic curve")
)
// Key management algorithms
@ -199,7 +205,7 @@ type Header struct {
// not be validated with the given verify options.
func (h Header) Certificates(opts x509.VerifyOptions) ([][]*x509.Certificate, error) {
if len(h.certificates) == 0 {
return nil, errors.New("go-jose/go-jose: no x5c header present in message")
return nil, ErrMissingX5cHeader
}
leaf := h.certificates[0]
@ -501,7 +507,7 @@ func curveName(crv elliptic.Curve) (string, error) {
case elliptic.P521():
return "P-521", nil
default:
return "", fmt.Errorf("go-jose/go-jose: unsupported/unknown elliptic curve")
return "", ErrUnsupportedEllipticCurve
}
}

View file

@ -25,7 +25,7 @@ import (
"errors"
"fmt"
"github.com/go-jose/go-jose/v3/json"
"github.com/go-jose/go-jose/v4/json"
)
// NonceSource represents a source of random nonces to go into JWS objects
@ -49,6 +49,11 @@ type Signer interface {
// - JSONWebKey
// - []byte (an HMAC key)
// - Any type that satisfies the OpaqueSigner interface
//
// If the key is an HMAC key, it must have at least as many bytes as the relevant hash output:
// - HS256: 32 bytes
// - HS384: 48 bytes
// - HS512: 64 bytes
type SigningKey struct {
Algorithm SignatureAlgorithm
Key interface{}
@ -353,8 +358,15 @@ func (ctx *genericSigner) Options() SignerOptions {
// - *rsa.PublicKey
// - *JSONWebKey
// - JSONWebKey
// - *JSONWebKeySet
// - JSONWebKeySet
// - []byte (an HMAC key)
// - Any type that implements the OpaqueVerifier interface.
//
// If the key is an HMAC key, it must have at least as many bytes as the relevant hash output:
// - HS256: 32 bytes
// - HS384: 48 bytes
// - HS512: 64 bytes
func (obj JSONWebSignature) Verify(verificationKey interface{}) ([]byte, error) {
err := obj.DetachedVerify(obj.payload, verificationKey)
if err != nil {

View file

@ -32,7 +32,7 @@ import (
"golang.org/x/crypto/pbkdf2"
josecipher "github.com/go-jose/go-jose/v3/cipher"
josecipher "github.com/go-jose/go-jose/v4/cipher"
)
// RandReader is a cryptographically secure random number generator (stubbed out in tests).
@ -454,7 +454,7 @@ func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipien
func (ctx symmetricMac) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
mac, err := ctx.hmac(payload, alg)
if err != nil {
return Signature{}, errors.New("go-jose/go-jose: failed to compute hmac")
return Signature{}, err
}
return Signature{
@ -486,12 +486,24 @@ func (ctx symmetricMac) verifyPayload(payload []byte, mac []byte, alg SignatureA
func (ctx symmetricMac) hmac(payload []byte, alg SignatureAlgorithm) ([]byte, error) {
var hash func() hash.Hash
// https://datatracker.ietf.org/doc/html/rfc7518#section-3.2
// A key of the same size as the hash output (for instance, 256 bits for
// "HS256") or larger MUST be used
switch alg {
case HS256:
if len(ctx.key)*8 < 256 {
return nil, ErrInvalidKeySize
}
hash = sha256.New
case HS384:
if len(ctx.key)*8 < 384 {
return nil, ErrInvalidKeySize
}
hash = sha512.New384
case HS512:
if len(ctx.key)*8 < 512 {
return nil, ErrInvalidKeySize
}
hash = sha512.New
default:
return nil, ErrUnsupportedAlgorithm

12
vendor/modules.txt vendored
View file

@ -181,12 +181,12 @@ github.com/docker/go-metrics
# github.com/felixge/httpsnoop v1.0.4
## explicit; go 1.13
github.com/felixge/httpsnoop
# github.com/go-jose/go-jose/v3 v3.0.3
## explicit; go 1.12
github.com/go-jose/go-jose/v3
github.com/go-jose/go-jose/v3/cipher
github.com/go-jose/go-jose/v3/json
github.com/go-jose/go-jose/v3/jwt
# github.com/go-jose/go-jose/v4 v4.0.2
## explicit; go 1.21
github.com/go-jose/go-jose/v4
github.com/go-jose/go-jose/v4/cipher
github.com/go-jose/go-jose/v4/json
github.com/go-jose/go-jose/v4/jwt
# github.com/go-logr/logr v1.3.0
## explicit; go 1.18
github.com/go-logr/logr