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

Rfc7797 prohibit in jwt #424

Merged
merged 2 commits into from Jul 30, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
42 changes: 8 additions & 34 deletions jwk/refresh.go
Expand Up @@ -26,12 +26,11 @@ import (
// All JWKS objects that are retrieved via the auto-fetch mechanism should be
// treated read-only, as they are shared among the consumers and this object.
type AutoRefresh struct {
errDst chan AutoRefreshError // user-specified error sink
errSink chan AutoRefreshError // AutoRefresh's error sink
errSink chan AutoRefreshError
cache map[string]Set
configureCh chan struct{}
fetching map[string]chan struct{}
muErrDst sync.Mutex
muErrSink sync.Mutex
muCache sync.RWMutex
muFetching sync.Mutex
muRegistry sync.RWMutex
Expand Down Expand Up @@ -111,15 +110,13 @@ type resetTimerReq struct {
// }
func NewAutoRefresh(ctx context.Context) *AutoRefresh {
af := &AutoRefresh{
errSink: make(chan AutoRefreshError, 1),
cache: make(map[string]Set),
configureCh: make(chan struct{}),
fetching: make(map[string]chan struct{}),
registry: make(map[string]*target),
resetTimerCh: make(chan *resetTimerReq),
}
go af.refreshLoop(ctx)
go af.drainErrSink(ctx)
return af
}

Expand Down Expand Up @@ -485,13 +482,11 @@ func (af *AutoRefresh) doRefreshRequest(ctx context.Context, url string, enableB
// At this point if err != nil, we know that there was something wrong
// in either the fetching or the parsing. Send this error to be processed,
// but take the extra mileage to not block regular processing by
// sending the error to a "proxy" sink, and not directly at the user-specified sink
// (see drainErrSink)
if err != nil && af.errSink != nil {
// discarding the error if we fail to send it through the channel
if err != nil {
select {
case af.errSink <- AutoRefreshError{Error: err, URL: url}:
default:
panic("af.errSink is not draining")
}
}

Expand All @@ -514,38 +509,17 @@ func (af *AutoRefresh) doRefreshRequest(ctx context.Context, url string, enableB
return err
}

// drainErrSink is used proxy the errors that were sent to the main
// error sink (af.errSink) to the user specified error sink
func (af *AutoRefresh) drainErrSink(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
case err := <-af.errSink:
af.muErrDst.Lock()
dst := af.errDst
af.muErrDst.Unlock()
if dst != nil {
// This will block if the user isn't properly draining the channel.
// It is the user's responsibility to drain it once they
// requested the errors to be streamed
dst <- err
}
}
}
}

// ErrorSink sets a channel to receive JWK fetch errors, if any.
// Only the errors that occurred *after* the channel was set will be sent.
//
// The user is responsible for properly draining the channel. If the channel
// is not drained, the fetch operation will block on repeated errors.
// is not drained properly, errors will be discarded.
//
// To disable, set a nil channel.
func (af *AutoRefresh) ErrorSink(ch chan AutoRefreshError) {
af.muErrDst.Lock()
af.errDst = ch
af.muErrDst.Unlock()
af.muErrSink.Lock()
af.errSink = ch
af.muErrSink.Unlock()
}

func calculateRefreshDuration(res *http.Response, refreshInterval *time.Duration, minRefreshInterval time.Duration) time.Duration {
Expand Down
19 changes: 19 additions & 0 deletions jwt/jwt_test.go
Expand Up @@ -1060,3 +1060,22 @@ func TestNested(t *testing.T) {
}
_ = parsed
}

func TestRFC7797(t *testing.T) {
key, err := jwxtest.GenerateRsaKey()
if !assert.NoError(t, err, `jwxtest.GenerateRsaKey should succeed`) {
return
}

hdrs := jws.NewHeaders()
hdrs.Set("b64", false)
hdrs.Set("crit", "b64")

token := jwt.New()
token.Set(jwt.AudienceKey, `foo`)

_, err = jwt.Sign(token, jwa.RS256, key, jwt.WithJwsHeaders(hdrs))
if !assert.Error(t, err, `jwt.Sign should fail`) {
return
}
}
10 changes: 10 additions & 0 deletions jwt/serialize.go
Expand Up @@ -147,6 +147,16 @@ func (s *jwsSerializer) Serialize(ctx SerializeCtx, v interface{}) (interface{},
if err := setTypeOrCty(ctx, hdrs); err != nil {
return nil, err // this is already wrapped
}

// JWTs MUST NOT use b64 = false
// https://datatracker.ietf.org/doc/html/rfc7797#section-7
if v, ok := hdrs.Get("b64"); ok {
if bval, bok := v.(bool); bok {
if !bval { // b64 = false
return nil, errors.New(`b64 cannot be false for JWTs`)
}
}
}
return jws.Sign(payload, s.alg, s.key, jws.WithHeaders(hdrs))
}

Expand Down