Skip to content

Commit

Permalink
JSONWebKeySet: ignore unsupported key types (#130)
Browse files Browse the repository at this point in the history
Support unmarshaling JSONWebKeySet containing unsupported key types.

Return ErrUnsupportedKeyType when unmarshaling JSONWebKey with unsupported key type.

Co-authored-by: samiponkanenssh <[email protected]>
  • Loading branch information
jsha and samiponkanenssh authored Jul 8, 2024
1 parent ab072bd commit feacf31
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 8 deletions.
53 changes: 48 additions & 5 deletions jwk.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,11 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) {
return json.Marshal(raw)
}

var errUnsupportedJWK = errors.New("go-jose/go-jose: unsupported json web key")

// UnmarshalJSON reads a key from its JSON representation.
//
// Returns ErrUnsupportedKeyType for unrecognized or unsupported "kty" header values.
func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
var raw rawJSONWebKey
err = json.Unmarshal(data, &raw)
Expand Down Expand Up @@ -228,7 +232,7 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
}
key, err = raw.symmetricKey()
case "OKP":
if raw.Crv == "Ed25519" && raw.X != nil {
if raw.Crv == "Ed25519" {
if raw.D != nil {
key, err = raw.edPrivateKey()
if err == nil {
Expand All @@ -238,17 +242,27 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
key, err = raw.edPublicKey()
keyPub = key
}
} else {
err = fmt.Errorf("go-jose/go-jose: unknown curve %s'", raw.Crv)
}
default:
err = fmt.Errorf("go-jose/go-jose: unknown json web key type '%s'", raw.Kty)
case "":
// kty MUST be present
err = fmt.Errorf("go-jose/go-jose: missing json web key type")
}

if err != nil {
return
}

if key == nil {
// RFC 7517:
// 5. JWK Set Format
// ...
// Implementations SHOULD ignore JWKs within a JWK Set that use "kty"
// (key type) values that are not understood by them, that are missing
// required members, or for which values are out of the supported
// ranges.
return ErrUnsupportedKeyType
}

if certPub != nil && keyPub != nil {
if !reflect.DeepEqual(certPub, keyPub) {
return errors.New("go-jose/go-jose: invalid JWK, public keys in key and x5c fields do not match")
Expand Down Expand Up @@ -348,6 +362,35 @@ func (s *JSONWebKeySet) Key(kid string) []JSONWebKey {
return keys
}

func (s *JSONWebKeySet) UnmarshalJSON(data []byte) (err error) {
s.Keys = nil

type rawJSONWebKeySet struct {
Keys []json.RawMessage `json:"keys"`
}

var rs rawJSONWebKeySet
err = json.Unmarshal(data, &rs)
if err != nil {
return err
}

for _, rk := range rs.Keys {
var k JSONWebKey
err = json.Unmarshal(rk, &k)
if err != nil {
// Skip key and continue unmarshalling the rest of the JWK Set
if !errors.Is(err, ErrUnsupportedKeyType) {
return err
}
} else {
s.Keys = append(s.Keys, k)
}
}

return nil
}

const rsaThumbprintTemplate = `{"e":"%s","kty":"RSA","n":"%s"}`
const ecThumbprintTemplate = `{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`
const edThumbprintTemplate = `{"crv":"%s","kty":"OKP","x":"%s"}`
Expand Down
61 changes: 61 additions & 0 deletions jwk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import (
"crypto/sha256"
"crypto/x509"
"encoding/hex"
"errors"
"fmt"
"math/big"
"reflect"
"strings"
Expand Down Expand Up @@ -663,6 +665,12 @@ func TestWebKeyVectorsInvalid(t *testing.T) {
`{"kty":"EC","crv":"P-256","d":"XXX"}`,
`{"kty":"EC","crv":"ABC","d":"dGVzdA","x":"dGVzdA"}`,
`{"kty":"EC","crv":"P-256","d":"dGVzdA","x":"dGVzdA"}`,
// Invalid oct key
`{"kty":"oct"}`,
`{"kty":"oct","k":"%not-base64url-encoded*"}`,
// Invalid OKP key
`{"kty":"OKP","crv":"Ed25519"}`,
`{"kty":"OKP","crv":"Ed25519","x":"%not-base64url-encoded*"}`,
}

for _, key := range keys {
Expand All @@ -674,6 +682,49 @@ func TestWebKeyVectorsInvalid(t *testing.T) {
}
}

// TestJWKUnsupported checks for an error when parsing a JWK with an unsupported key type.
func TestJWKUnsupported(t *testing.T) {
var jwk JSONWebKey
err := jwk.UnmarshalJSON([]byte(`{"kty": "XXX"}`))
if !errors.Is(err, ErrUnsupportedKeyType) {
t.Error("expected ErrUnsupportedKeyType, got:", err)
}
}

// TestJWKUnsupported checks that unmarshaling into a JSONWebKeySet succeeds even if some JWKs have an unsupported key type.
func TestJWKSetIgnoresUnsupported(t *testing.T) {
var jwkSet JSONWebKeySet
err := jwkSet.UnmarshalJSON([]byte(fmt.Sprintf(`
{
"keys": [
%s,
{"kty": "XXX"}
]
}
`, cookbookJWKs[0])))
if err != nil {
t.Error("parsing JWK Set with one unsupported and one supported key:", err)
}
if len(jwkSet.Keys) != 1 {
t.Error("expected one key to be parsed, got:", len(jwkSet.Keys))
}

err = jwkSet.UnmarshalJSON([]byte(`
{
"keys": [
{"kty": "ABC"},
{"kty": "XXX"}
]
}
`))
if err != nil {
t.Error("parsing JWK Set with two unsupported keys:", err)
}
if len(jwkSet.Keys) != 0 {
t.Error("expected zero keys to be parsed, got:", len(jwkSet.Keys))
}
}

// Test vectors from RFC 7520
var cookbookJWKs = []string{
// EC Public
Expand Down Expand Up @@ -900,6 +951,16 @@ func TestMarshalUnmarshalJWKSet(t *testing.T) {
if !bytes.Equal(jsonbar, jsonbar2) {
t.Error("roundtrip should not lose information")
}

set3 := JSONWebKeySet{Keys: []JSONWebKey{jwk1}}
err = json.Unmarshal(jsonbar, &set3)
if err != nil {
t.Fatal("problem unmarshalling set", err)
}

if len(set3.Keys) != 2 {
t.Error("unmarshaling should clear any existing keys")
}
}

func TestJWKSetKey(t *testing.T) {
Expand Down
7 changes: 4 additions & 3 deletions shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ var (
ErrUnsupportedAlgorithm = errors.New("go-jose/go-jose: unknown/unsupported algorithm")

// ErrUnsupportedKeyType indicates that the given key type/format is not
// supported. This occurs when trying to instantiate an encrypter and passing
// it a key of an unrecognized type or with unsupported parameters, such as
// an RSA private key with more than two primes.
// supported. This occurs when parsing a JWK with an unsupported key type (kty),
// or instantiating a signer or encrypter with an unsupported key type in Go
// (e.g. *dsa.PrivateKey), or a key type in Go that doesn't match the requested
// algorithm.
ErrUnsupportedKeyType = errors.New("go-jose/go-jose: unsupported key type/format")

// ErrInvalidKeySize indicates that the given key is not the correct size
Expand Down

0 comments on commit feacf31

Please sign in to comment.