Skip to content

Commit

Permalink
Merge pull request #37 from smallstep/feat/guessSigAlg
Browse files Browse the repository at this point in the history
Make it easier to sign a JWT with a key.
  • Loading branch information
maraino authored Mar 11, 2022
2 parents 6be1399 + a20952a commit 31cf01b
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 2 deletions.
19 changes: 19 additions & 0 deletions jose/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package jose

import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
Expand Down Expand Up @@ -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) {
Expand Down
41 changes: 41 additions & 0 deletions jose/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})
}
}
6 changes: 6 additions & 0 deletions jose/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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)
}

Expand Down
83 changes: 81 additions & 2 deletions jose/types_test.go
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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)
}
}
})
}
}

0 comments on commit 31cf01b

Please sign in to comment.