diff --git a/jose/parse.go b/jose/parse.go index fdb93fd4..908ec8a9 100644 --- a/jose/parse.go +++ b/jose/parse.go @@ -2,6 +2,7 @@ package jose import ( "bytes" + "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" @@ -351,6 +352,24 @@ func guessJWKAlgorithm(ctx *context, jwk *JSONWebKey) { } } +// guessSignatureAlgorithm returns the signature algorithm for a given private key. +func guessSignatureAlgorithm(key crypto.PrivateKey) SignatureAlgorithm { + switch k := key.(type) { + case []byte: + return DefaultOctSigAlgorithm + case *ecdsa.PrivateKey: + return SignatureAlgorithm(getECAlgorithm(k.Curve)) + case *rsa.PrivateKey: + return DefaultRSASigAlgorithm + case ed25519.PrivateKey: + return EdDSA + case x25519.PrivateKey, X25519Signer: + return XEdDSA + default: + return "" + } +} + // guessKnownJWKAlgorithm sets the algorithm for keys that only have one // possible algorithm. func guessKnownJWKAlgorithm(ctx *context, jwk *JSONWebKey) { diff --git a/jose/parse_test.go b/jose/parse_test.go index d94e561b..9de4882e 100644 --- a/jose/parse_test.go +++ b/jose/parse_test.go @@ -685,3 +685,44 @@ func Test_guessKeyType(t *testing.T) { }) } } + +func Test_guessSignatureAlgorithm(t *testing.T) { + must := func(args ...interface{}) crypto.PrivateKey { + last := len(args) - 1 + if err := args[last]; err != nil { + t.Fatal(err) + } + return args[last-1] + } + + _, x25519Key, err := x25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + + type args struct { + key crypto.PrivateKey + } + tests := []struct { + name string + args args + want SignatureAlgorithm + }{ + {"byte", args{[]byte("the-key")}, HS256}, + {"ES256", args{must(ecdsa.GenerateKey(elliptic.P256(), rand.Reader))}, ES256}, + {"ES384", args{must(ecdsa.GenerateKey(elliptic.P384(), rand.Reader))}, ES384}, + {"ES512", args{must(ecdsa.GenerateKey(elliptic.P521(), rand.Reader))}, ES512}, + {"RS256", args{must(rsa.GenerateKey(rand.Reader, 2048))}, RS256}, + {"EdDSA", args{must(ed25519.GenerateKey(rand.Reader))}, EdDSA}, + {"XEdDSA", args{x25519Key}, XEdDSA}, + {"XEdDSA with X25519Signer", args{X25519Signer(x25519Key)}, XEdDSA}, + {"empty", args{must(ecdsa.GenerateKey(elliptic.P224(), rand.Reader))}, ""}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := guessSignatureAlgorithm(tt.args.key); !reflect.DeepEqual(got, tt.want) { + t.Errorf("guessSignatureAlgorithm() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/jose/types.go b/jose/types.go index b1212d25..07f6b96d 100644 --- a/jose/types.go +++ b/jose/types.go @@ -118,6 +118,9 @@ var ErrInvalidSubject = jwt.ErrInvalidSubject // ErrInvalidID indicates invalid jti claim. var ErrInvalidID = jwt.ErrInvalidID +// ErrIssuedInTheFuture indicates that the iat field is in the future. +var ErrIssuedInTheFuture = jwt.ErrIssuedInTheFuture + // Key management algorithms //nolint:revive // use standard names in upper-case const ( @@ -236,6 +239,9 @@ func NewSigner(sig SigningKey, opts *SignerOptions) (Signer, error) { if k, ok := sig.Key.(x25519.PrivateKey); ok { sig.Key = X25519Signer(k) } + if sig.Algorithm == "" { + sig.Algorithm = guessSignatureAlgorithm(sig.Key) + } return jose.NewSigner(sig, opts) } diff --git a/jose/types_test.go b/jose/types_test.go index 58bc8165..c1dc4fd8 100644 --- a/jose/types_test.go +++ b/jose/types_test.go @@ -1,13 +1,18 @@ -// Code generated (comment to force golint to ignore this file). DO NOT EDIT. - package jose import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" "reflect" "testing" "time" "github.com/pkg/errors" + "go.step.sm/crypto/x25519" ) func TestNumericDate(t *testing.T) { @@ -100,3 +105,77 @@ func TestTrimPrefix(t *testing.T) { }) } } + +func TestSignVerify(t *testing.T) { + must := func(args ...interface{}) crypto.Signer { + last := len(args) - 1 + if err := args[last]; err != nil { + t.Fatal(err) + } + return args[last-1].(crypto.Signer) + } + + p224 := must(ecdsa.GenerateKey(elliptic.P224(), rand.Reader)) + p256 := must(ecdsa.GenerateKey(elliptic.P256(), rand.Reader)) + p384 := must(ecdsa.GenerateKey(elliptic.P384(), rand.Reader)) + p521 := must(ecdsa.GenerateKey(elliptic.P521(), rand.Reader)) + rsa2048 := must(rsa.GenerateKey(rand.Reader, 2048)) + edKey := must(ed25519.GenerateKey(rand.Reader)) + xKey := must(x25519.GenerateKey(rand.Reader)) + + type args struct { + sig SigningKey + opts *SignerOptions + } + tests := []struct { + name string + args args + wantErr bool + }{ + {"byte", args{SigningKey{Key: []byte("the-key")}, nil}, false}, + {"P256", args{SigningKey{Key: p256}, nil}, false}, + {"P384", args{SigningKey{Key: p384}, nil}, false}, + {"P521", args{SigningKey{Key: p521}, nil}, false}, + {"rsa2048", args{SigningKey{Key: rsa2048}, nil}, false}, + {"ed", args{SigningKey{Key: edKey}, nil}, false}, + {"x25519", args{SigningKey{Key: xKey}, nil}, false}, + {"fail P224", args{SigningKey{Key: p224}, nil}, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewSigner(tt.args.sig, tt.args.opts) + if (err != nil) != tt.wantErr { + t.Errorf("NewSigner() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr { + payload := []byte(`{"sub": "sub"}`) + jws, err := got.Sign(payload) + if err != nil { + t.Errorf("Signer.Sign() error = %v", err) + return + } + jwt, err := ParseSigned(jws.FullSerialize()) + if err != nil { + t.Errorf("ParseSigned() error = %v", err) + return + } + + var claims Claims + if signer, ok := tt.args.sig.Key.(crypto.Signer); ok { + err = Verify(jwt, signer.Public(), &claims) + } else { + err = Verify(jwt, tt.args.sig.Key, &claims) + } + if err != nil { + t.Errorf("JSONWebSignature.Verify() error = %v", err) + return + } + want := Claims{Subject: "sub"} + if !reflect.DeepEqual(claims, want) { + t.Errorf("JSONWebSignature.Verify() claims = %v, want %v", claims, want) + } + } + }) + } +}