Skip to content

Commit

Permalink
Support processing of JOSE extensions.
Browse files Browse the repository at this point in the history
  • Loading branch information
pascaldekloe committed May 6, 2020
1 parent bd1cd80 commit 4b2db6d
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 10 deletions.
31 changes: 23 additions & 8 deletions check.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ var ErrSigMiss = errors.New("jwt: signature mismatch")

var errPart = errors.New("jwt: missing base64 part")

// “Producers MUST NOT use the empty list "[]" as the "crit" value.”
// — “JSON Web Signature (JWS)” RFC 7515, subsection 4.1.11
var errCritEmpty = errors.New("jwt: empty array in crit header")

// EvalCrit is invoked by the Check functions for each token with one or more
// JOSE extensions. The crit slice has the JSON field names (for header) which
// “MUST be understood and processed” according to RFC 7515, subsection 4.1.11.
// “If any of the listed extension Header Parameters are not understood and
// supported by the recipient, then the JWS is invalid.”
// The respective Check function returns any error from EvalCrit as is.
var EvalCrit = func(token []byte, crit []string, header json.RawMessage) error {
return fmt.Errorf("jwt: unsupported critical extension in JOSE header: %q", crit)
}

// ParseWithoutCheck skips the signature validation.
func ParseWithoutCheck(token []byte) (*Claims, error) {
var c Claims
Expand Down Expand Up @@ -146,9 +160,9 @@ func (c *Claims) scan(token []byte) (firstDot, lastDot int, sig []byte, alg stri
}

var header struct {
Kid string `json:"kid"`
Alg string `json:"alg"`
Crit []interface{} `json:"crit"`
Kid string `json:"kid"`
Alg string `json:"alg"`
Crit []string `json:"crit"`
}
if err := json.Unmarshal(buf[:n], &header); err != nil {
return 0, 0, nil, "", fmt.Errorf("jwt: malformed JOSE header: %w", err)
Expand All @@ -158,12 +172,13 @@ func (c *Claims) scan(token []byte) (firstDot, lastDot int, sig []byte, alg stri

alg = header.Alg
c.KeyID = header.Kid
// “If any of the listed extension Header Parameters are not understood
// and supported by the recipient, then the JWS is invalid. […]
// Producers MUST NOT use the empty list "[]" as the "crit" value.”
// — “JSON Web Signature (JWS)” RFC 7515, subsection 4.1.11
if header.Crit != nil {
return 0, 0, nil, "", fmt.Errorf("jwt: unsupported critical extension in JOSE header: %q", header.Crit)
if len(header.Crit) == 0 {
return 0, 0, nil, "", errCritEmpty
}
if err := EvalCrit(token, header.Crit, c.RawHeader); err != nil {
return 0, 0, nil, "", err
}
}

// signature
Expand Down
43 changes: 41 additions & 2 deletions check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"crypto/ed25519"
"crypto/rand"
"crypto/rsa"
"encoding/json"
"errors"
"strings"
"testing"
)
Expand Down Expand Up @@ -208,13 +210,50 @@ func TestCheckHashNotLinked(t *testing.T) {

func TestJOSEExtension(t *testing.T) {
// “Negative Test Case for "crit" Header Parameter” from RFC 7515, appendix E.
const token = "eyJhbGciOiJub25lIiwNCiAiY3JpdCI6WyJodHRwOi8vZXhhbXBsZS5jb20vVU5ERUZJTkVEIl0sDQogImh0dHA6Ly9leGFtcGxlLmNvbS9VTkRFRklORUQiOnRydWUNCn0.RkFJTA."
const testToken = "eyJhbGciOiJub25lIiwNCiAiY3JpdCI6WyJodHRwOi8vZXhhbXBsZS5jb20vVU5ERUZJTkVEIl0sDQogImh0dHA6Ly9leGFtcGxlLmNvbS9VTkRFRklORUQiOnRydWUNCn0.RkFJTA."
errTest := errors.New("test error")

_, err := ParseWithoutCheck([]byte(token))
_, err := ParseWithoutCheck([]byte(testToken))
const want = "jwt: unsupported critical extension in JOSE header: [\"http://example.com/UNDEFINED\"]"
if err == nil || err.Error() != want {
t.Errorf("got error %q, want %q", err, want)
}

bu := EvalCrit
defer func() {
EvalCrit = bu // restore
}()
// extend
EvalCrit = func(token []byte, crit []string, header json.RawMessage) error {
if string(token) != testToken {
t.Errorf("got token %q, want %q", token, testToken)
}

const wantHeader = "{\"alg\":\"none\",\r\n \"crit\":[\"http://example.com/UNDEFINED\"],\r\n \"http://example.com/UNDEFINED\":true\r\n}"
if string(header) != wantHeader {
t.Errorf("got header %q, want %q", header, wantHeader)
}

const wantCrit = "http://example.com/UNDEFINED"
if len(crit) != 1 || crit[0] != wantCrit {
t.Errorf("got crit %q, want %q", crit, wantCrit)
}

return errTest
}
_, err = ParseWithoutCheck([]byte(testToken))
if err != errTest {
t.Errorf("got error %q, want %q", err, errTest)
}

token, err := new(Claims).HMACSign(HS256, []byte("secret"),
json.RawMessage(`{ "crit": [] }`))
if err != nil {
t.Fatal("compose token with empty crit array:", err)
}
if _, err := HMACCheck(token, []byte("wrong")); err != errCritEmpty {
t.Errorf("got error %q, want %q", err, errCritEmpty)
}
}

func TestErrPart(t *testing.T) {
Expand Down

0 comments on commit 4b2db6d

Please sign in to comment.