Skip to content

Commit

Permalink
Next major release v4 (#118)
Browse files Browse the repository at this point in the history
  • Loading branch information
cristaloleg authored Oct 18, 2021
1 parent bf8716c commit 42e32e5
Show file tree
Hide file tree
Showing 29 changed files with 958 additions and 800 deletions.
40 changes: 20 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

JSON Web Token for Go [RFC 7519](https://tools.ietf.org/html/rfc7519), also see [jwt.io](https://jwt.io) for more.

The latest version is `v3`.
The latest version is `v4`.

## Rationale

Expand All @@ -30,10 +30,10 @@ There are many JWT libraries, but many of them are hard to use (unclear or fixed

## Install

Go version 1.13+
Go version 1.17+

```
GO111MODULE=on go get github.com/cristalhq/jwt/v3
go get github.com/cristalhq/jwt/v4
```

## Example
Expand All @@ -59,8 +59,8 @@ builder := jwt.NewBuilder(signer)
token, err := builder.Build(claims)
checkErr(err)

// here is token as byte slice
var _ []byte = token.Bytes() // or just token.String() for string
// here is token as a string
var _ string = token.String()
```

Parse and verify token:
Expand All @@ -70,25 +70,25 @@ key := []byte(`secret`)
verifier, err := jwt.NewVerifierHS(jwt.HS256, key)
checkErr(err)

// parse a Token (by example received from a request)
tokenStr := `<header.payload.signature>`
token, err := jwt.ParseString(tokenStr)
// parse and verify a token
tokenBytes := token.Bytes()
newToken, err := jwt.Parse(tokenBytes, verifier)
checkErr(err)

// and verify it's signature
err = verifier.Verify(token.Payload(), token.Signature())
// or just verify it's signature
err = verifier.Verify(newToken)
checkErr(err)

// also you can parse and verify together
newToken, err := jwt.ParseAndVerifyString(tokenStr, verifier)
checkErr(err)

// get standard claims
var newClaims jwt.StandardClaims
errClaims := json.Unmarshal(newToken.RawClaims(), &newClaims)
// get Registered claims
var newClaims jwt.RegisteredClaims
errClaims := json.Unmarshal(newToken.Claims(), &newClaims)
checkErr(errClaims)

// verify claims as you
// or parse only claims
errParseClaims := jwt.ParseClaims(tokenBytes, verifier, &newClaims)
checkErr(errParseClaims)

// verify claims as you wish
var _ bool = newClaims.IsForAudience("admin")
var _ bool = newClaims.IsValidAt(time.Now())
```
Expand All @@ -105,8 +105,8 @@ See [these docs][pkg-url].

[build-img]: https://github.com/cristalhq/jwt/workflows/build/badge.svg
[build-url]: https://github.com/cristalhq/jwt/actions
[pkg-img]: https://pkg.go.dev/badge/cristalhq/jwt/v3
[pkg-url]: https://pkg.go.dev/github.com/cristalhq/jwt/v3
[pkg-img]: https://pkg.go.dev/badge/cristalhq/jwt/v4
[pkg-url]: https://pkg.go.dev/github.com/cristalhq/jwt/v4
[reportcard-img]: https://goreportcard.com/badge/cristalhq/jwt
[reportcard-url]: https://goreportcard.com/report/cristalhq/jwt
[coverage-img]: https://codecov.io/gh/cristalhq/jwt/branch/master/graph/badge.svg
Expand Down
25 changes: 2 additions & 23 deletions algo.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,14 @@ type Signer interface {
// Verifier is used to verify tokens.
type Verifier interface {
Algorithm() Algorithm
Verify(payload, signature []byte) error
Verify(token *Token) error
}

// Algorithm for signing and verifying.
type Algorithm string

func (a Algorithm) String() string { return string(a) }

// keySize of the algorithm's key (if exist). Is similar to Signer.SignSize.
func (a Algorithm) keySize() int { return algsKeySize[a] }

var algsKeySize = map[Algorithm]int{
// for EdDSA private and public key have different sizes, so 0
// for HS there is no limits for key size, so 0

RS256: 256,
RS384: 384,
RS512: 512,

ES256: 64,
ES384: 96,
ES512: 132,

PS256: 256,
PS384: 384,
PS512: 512,
}

// Algorithm names for signing and verifying.
const (
EdDSA Algorithm = "EdDSA"
Expand All @@ -68,8 +48,7 @@ const (
func hashPayload(hash crypto.Hash, payload []byte) ([]byte, error) {
hasher := hash.New()

_, err := hasher.Write(payload)
if err != nil {
if _, err := hasher.Write(payload); err != nil {
return nil, err
}
signed := hasher.Sum(nil)
Expand Down
35 changes: 17 additions & 18 deletions algo_eddsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,59 +5,58 @@ import (
)

// NewSignerEdDSA returns a new ed25519-based signer.
func NewSignerEdDSA(key ed25519.PrivateKey) (Signer, error) {
func NewSignerEdDSA(key ed25519.PrivateKey) (*EdDSAAlg, error) {
if len(key) == 0 {
return nil, ErrNilKey
}
if len(key) != ed25519.PrivateKeySize {
return nil, ErrInvalidKey
}
return &edDSAAlg{
alg: EdDSA,
return &EdDSAAlg{
publicKey: nil,
privateKey: key,
}, nil
}

// NewVerifierEdDSA returns a new ed25519-based verifier.
func NewVerifierEdDSA(key ed25519.PublicKey) (Verifier, error) {
func NewVerifierEdDSA(key ed25519.PublicKey) (*EdDSAAlg, error) {
if len(key) == 0 {
return nil, ErrNilKey
}
if len(key) != ed25519.PublicKeySize {
return nil, ErrInvalidKey
}
return &edDSAAlg{
alg: EdDSA,
publicKey: key,
return &EdDSAAlg{
publicKey: key,
privateKey: nil,
}, nil
}

type edDSAAlg struct {
alg Algorithm
type EdDSAAlg struct {
publicKey ed25519.PublicKey
privateKey ed25519.PrivateKey
}

func (ed *edDSAAlg) Algorithm() Algorithm {
return ed.alg
func (ed *EdDSAAlg) Algorithm() Algorithm {
return EdDSA
}

func (ed *edDSAAlg) SignSize() int {
func (ed *EdDSAAlg) SignSize() int {
return ed25519.SignatureSize
}

func (ed *edDSAAlg) Sign(payload []byte) ([]byte, error) {
func (ed *EdDSAAlg) Sign(payload []byte) ([]byte, error) {
return ed25519.Sign(ed.privateKey, payload), nil
}

func (ed *edDSAAlg) VerifyToken(token *Token) error {
if constTimeAlgEqual(token.Header().Algorithm, ed.alg) {
return ed.Verify(token.Payload(), token.Signature())
func (ed *EdDSAAlg) Verify(token *Token) error {
if !constTimeAlgEqual(token.Header().Algorithm, EdDSA) {
return ErrAlgorithmMismatch
}
return ErrAlgorithmMismatch
return ed.verify(token.PayloadPart(), token.Signature())
}

func (ed *edDSAAlg) Verify(payload, signature []byte) error {
func (ed *EdDSAAlg) verify(payload, signature []byte) error {
if !ed25519.Verify(ed.publicKey, payload, signature) {
return ErrInvalidSignature
}
Expand Down
96 changes: 38 additions & 58 deletions algo_eddsa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,37 @@ package jwt

import (
"crypto/ed25519"
"crypto/rand"
"errors"
"testing"
)

var (
ed25519PrivateKey ed25519.PrivateKey
ed25519PublicKey ed25519.PublicKey

ed25519PrivateKeyAnother ed25519.PrivateKey
ed25519PublicKeyAnother ed25519.PublicKey
)

func init() {
f := func() (ed25519.PrivateKey, ed25519.PublicKey) {
pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
panic(err)
}
return privKey, pubKey
}

ed25519PrivateKey, ed25519PublicKey = f()
ed25519PrivateKeyAnother, ed25519PublicKeyAnother = f()
}

func TestEdDSA(t *testing.T) {
f := func(privateKey ed25519.PrivateKey, publicKey ed25519.PublicKey, isCorrectSign bool) {
f := func(privateKey ed25519.PrivateKey, publicKey ed25519.PublicKey, wantErr error) {
t.Helper()

const payload = `simple-string-payload`

sign := ed25519Sign(t, privateKey, payload)
signer, errSigner := NewSignerEdDSA(privateKey)
if errSigner != nil {
t.Fatalf("NewSignerEdDSA %v", errSigner)
}
verifier, errVerifier := NewVerifierEdDSA(publicKey)
if errVerifier != nil {
t.Fatalf("NewVerifierEdDSA %v", errVerifier)
}

err := ed25519Verify(t, publicKey, payload, sign)
if err != nil && isCorrectSign {
t.Fatal(err)
token, err := NewBuilder(signer).Build(simplePayload)
if err != nil {
t.Fatalf("Build %v", errVerifier)
}
if err == nil && !isCorrectSign {
t.Fatal("must be not nil")

errVerify := verifier.Verify(token)
if !errors.Is(errVerify, wantErr) {
t.Errorf("want %v, got %v", wantErr, errVerify)
}
}

f(ed25519PrivateKey, ed25519PublicKey, true)
f(ed25519PrivateKey, ed25519PublicKeyAnother, false)
f(ed25519PrivateKeyAnother, ed25519PublicKey, false)
f(ed25519PrivateKey, ed25519PublicKey, nil)
f(ed25519PrivateKey, ed25519PublicKeyAnother, ErrInvalidSignature)
f(ed25519PrivateKeyAnother, ed25519PublicKey, ErrInvalidSignature)
}

func TestEdDSA_BadKeys(t *testing.T) {
Expand All @@ -58,35 +43,30 @@ func TestEdDSA_BadKeys(t *testing.T) {
}

f(getSignerError(NewSignerEdDSA(nil)), ErrNilKey)

priv := ed25519.PrivateKey(make([]byte, 72))
f(getSignerError(NewSignerEdDSA(priv)), ErrInvalidKey)

f(getVerifierError(NewVerifierEdDSA(nil)), ErrNilKey)

pub := ed25519.PublicKey(make([]byte, 72))
f(getVerifierError(NewVerifierEdDSA(pub)), ErrInvalidKey)
}

func ed25519Sign(t *testing.T, privateKey ed25519.PrivateKey, payload string) []byte {
t.Helper()

signer, errSigner := NewSignerEdDSA(privateKey)
if errSigner != nil {
t.Fatalf("NewSignerEdDSA %v", errSigner)
}

sign, errSign := signer.Sign([]byte(payload))
if errSign != nil {
t.Fatalf("SignEdDSA %v", errSign)
}
return sign
}

func ed25519Verify(t *testing.T, publicKey ed25519.PublicKey, payload string, sign []byte) error {
t.Helper()

verifier, errVerifier := NewVerifierEdDSA(publicKey)
if errVerifier != nil {
t.Fatalf("NewVerifierEdDSA %v", errVerifier)
}
return verifier.Verify([]byte(payload), sign)
}
var (
// See: RFC 8037, appendix A.1
ed25519PrivateKey = ed25519.PrivateKey([]byte{
0x9d, 0x61, 0xb1, 0x9d, 0xef, 0xfd, 0x5a, 0x60,
0xba, 0x84, 0x4a, 0xf4, 0x92, 0xec, 0x2c, 0xc4,
0x44, 0x49, 0xc5, 0x69, 0x7b, 0x32, 0x69, 0x19,
0x70, 0x3b, 0xac, 0x03, 0x1c, 0xae, 0x7f, 0x60,
0xd7, 0x5a, 0x98, 0x01, 0x82, 0xb1, 0x0a, 0xb7,
0xd5, 0x4b, 0xfe, 0xd3, 0xc9, 0x64, 0x07, 0x3a,
0x0e, 0xe1, 0x72, 0xf3, 0xda, 0xa6, 0x23, 0x25,
0xaf, 0x02, 0x1a, 0x68, 0xf7, 0x07, 0x51, 0x1a,
})
ed25519PublicKey = ed25519PrivateKey.Public().(ed25519.PublicKey)

ed25519PrivateKeyAnother ed25519.PrivateKey = base64ToBytes("eJGvQDFFiaYHaZU2sfRhPrGKlgZcHBT8CPY3Fx2zhQEjlzQ5-3qTgKZ5wCmIRqL4sbNhWvpPx5Y_PqmSEg3oYg")
ed25519PublicKeyAnother ed25519.PublicKey = base64ToBytes("I5c0Oft6k4CmecApiEai-LGzYVr6T8eWPz6pkhIN6GI")
)
Loading

0 comments on commit 42e32e5

Please sign in to comment.