Skip to content
This repository was archived by the owner on Mar 18, 2023. It is now read-only.

JSON Web Keys #10

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 3 additions & 23 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
- checkout
- run: apk add git build-base
- run: go get -u github.com/jstemmer/go-junit-report
- run: mkdir test-results test-results/Base test-results/HMAC-SHA2 test-results/RSA-PKCS1_5 test-results/RSA-PSS test-results/ECDSA test-results/EdDSA test-results/PublicKey
- run: mkdir test-results test-results/Base test-results/HMAC-SHA2 test-results/RSA-PKCS1_5 test-results/RSA-PSS test-results/ECDSA test-results/EdDSA test-results/JWK
- run:
name: Base package unit tests
command: go test -v 2>&1 | go-junit-report > test-results/Base/report.xml
Expand All @@ -29,7 +29,7 @@ jobs:
command: go test -v ./alg-eddsa 2>&1 | go-junit-report > test-results/EdDSA/report.xml
- run:
name: Public key unit tests
command: go test -v ./publickey 2>&1 | go-junit-report > test-results/PublicKey/report.xml
command: go test -v ./jwk 2>&1 | go-junit-report > test-results/JWK/report.xml
- store_test_results:
path: test-results
coverage:
Expand All @@ -43,34 +43,14 @@ jobs:
- run: chmod +x uploader.run
- run:
name: Calculate coverage
command: go test -coverprofile=coverage.txt . ./alg-hs ./alg-rs ./alg-ps ./alg-es ./alg-eddsa ./publickey
command: go test -coverprofile=coverage.txt . ./alg-hs ./alg-rs ./alg-ps ./alg-es ./alg-eddsa ./jwk
- run:
name: Upload coverage
command: ./uploader.run
golangci-lint:
docker:
- image: golang:alpine
working_directory: ~/jwt
steps:
- run: apk add git build-base curl jq
- checkout
- run: go version | grep -o "[0-9]*\.[0-9]*\.*[0-9]*" > vgo
- run: curl -s https://api.github.com/repos/golangci/golangci-lint/releases/latest | jq -r .tag_name > vlinter
- restore_cache:
key: linter{{checksum "vlinter"}}_go{{checksum "vgo"}}
- run: go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
- save_cache:
key: linter{{checksum "vlinter"}}_go{{checksum "vgo"}}
paths:
- /go
- run:
name: golangci-lint
command: golangci-lint run

workflows:
version: 2
run-tests:
jobs:
- go-test
- coverage
- golangci-lint
12 changes: 6 additions & 6 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ type Provider interface {
Sign([]byte) ([]byte, error)
Verify([]byte, []byte, Header) error
Header(*Header)
AddPublicKey(publickey.PublicKey) error
AddPublicKey(jwk.JWK) error
RemovePublicKey(string) error
CurrentKey() publickey.PublicKey
CurrentKey() jwk.JWK
}
```

Expand All @@ -56,24 +56,24 @@ A `Provider` has to implement the following functionality:

In case additional fields in the header are necessary, please open an issue on GitHub to discuss possible solutions.

`AddPublicKey(key publickey.PublicKey) error` adds a public key that the provider can use to validate signatures generated by other applications or instances. It returns an error if the key cannot be parsed or already exists.
`AddPublicKey(key jwk.JWK) error` adds a public key that the provider can use to validate signatures generated by other applications or instances. It returns an error if the key cannot be parsed or already exists.

`RemovePublicKey(keyid string)` removes a public key by key ID from the verification set. This can be used to remove compromised keys. It is a noop for the public key belonging to the private key used for signing.

`CurrentKey() publickey.PublicKey` returns the public key belonging to the private key used for signing. The key should be properly encoded so it can easily be encoded to PEM or transferred in binary with the least possible overhead.
`CurrentKey() jwk.JWK` returns the public key belonging to the private key used for signing as a JSON Web Key.

### `SignatureSettings`

`SignatureSettings` are only necessary when supporting `LoadProvider` and have to implement the following functionality:

```go
func NewSettings(key []byte, keyid string) (SignatureSettings, error)
func NewSettings(key jwk.JWK) (SignatureSettings, error)
```

`NewSettigns` parses the key and returns SignatureSettings with the key and key ID set and an error indicating whether parsing was successful

```go
func NewSettingsWithKeyURL(key []byte, keyid string, keyurl string) (SignatureSettings, error)
func NewSettingsWithKeyURL(key jwk.JWK, keyurl string) (SignatureSettings, error)
```

`NewSettingsWithKeyURL` parses the key and returns SignatureSettings with the key, key ID and key URL set and an error indicating whether parsing was successful
Expand Down
60 changes: 45 additions & 15 deletions alg-eddsa/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,48 @@ package eddsa
import (
"errors"

"github.com/fossoreslp/go-jwt/publickey"
"github.com/fossoreslp/go-jwt/jwk"
"github.com/fossoreslp/go-uuid-v4"
"github.com/otrv4/ed448"
"golang.org/x/crypto/ed25519"
)

// Settings stores the signature settings for an EdDSA curve
type Settings struct {
typ int
ed25519 ed25519.PrivateKey
ed448 [144]byte
kid string
jku string
}

// NewSettings creates new signature settings for the parameters
func NewSettings(key jwk.JWK) (Settings, error) {
return NewSettingsWithKeyURL(key, "")
}

// NewSettingsWithKeyURL creates new signature settings for the parameters
func NewSettingsWithKeyURL(key jwk.JWK, keyurl string) (Settings, error) {
priv, err := key.GetEdDSAPrivateKey()
if err != nil {
return Settings{}, err
}
if len(priv) == ed25519.SeedSize {
return Settings{Ed25519, ed25519.NewKeyFromSeed(priv), [144]byte{0x00}, key.GetKeyID(), keyurl}, nil
}
var arr [144]byte
copy(arr[:], priv)
return Settings{Ed448, nil, arr, key.GetKeyID(), keyurl}, nil
}

// AddPublicKey adds a public key for verification
func (p *Provider) AddPublicKey(key publickey.PublicKey) error {
func (p *Provider) AddPublicKey(key jwk.JWK) error {
id := key.GetKeyID()
enc := key.GetPublicKey()
if len(enc) == ed25519.PublicKeySize {
if _, ok := p.c2[id]; ok {
return errors.New("key ID already exists")
}
p.c2[id] = ed25519.PublicKey(enc)
return nil
enc, err := key.GetEdDSAPublicKey()
if err != nil {
return err
}
if len(enc) == 56 {
if key.Crv == jwk.CurveEd448 {
if _, ok := p.c4[id]; ok {
return errors.New("key ID already exists")
}
Expand All @@ -29,7 +53,11 @@ func (p *Provider) AddPublicKey(key publickey.PublicKey) error {
p.c4[id] = pub
return nil
}
return errors.New("key has invalid length")
if _, ok := p.c2[id]; ok {
return errors.New("key ID already exists")
}
p.c2[id] = ed25519.PublicKey(enc)
return nil
}

// RemovePublicKey removes a public key by it's key ID from the verification set
Expand All @@ -42,15 +70,17 @@ func (p *Provider) RemovePublicKey(keyid string) {
}

// CurrentKey returns the public key belonging to the private key used for signing
func (p Provider) CurrentKey() publickey.PublicKey {
func (p Provider) CurrentKey() jwk.JWK {
if p.curve == Ed25519 {
return publickey.New(p.c2[p.settings.kid], p.settings.kid)
key, _ := jwk.NewEdDSAPublicKey(p.c2[p.settings.kid], p.settings.kid)
return key
}
if p.curve == Ed448 {
k := p.c4[p.settings.kid]
return publickey.New(k[:], p.settings.kid)
key, _ := jwk.NewEdDSAPublicKey(k[:], p.settings.kid)
return key
}
return publickey.PublicKey{}
return jwk.JWK{}
}

func generateEd25519Keys() (ed25519.PrivateKey, ed25519.PublicKey, string, error) {
Expand Down
86 changes: 75 additions & 11 deletions alg-eddsa/keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,71 @@ import (
"reflect"
"testing"

"github.com/fossoreslp/go-jwt/publickey"
"github.com/fossoreslp/go-jwt/jwk"
"golang.org/x/crypto/ed25519"
)

var (
ed25519PrivateKey = [64]byte{0x40, 0xb7, 0xd9, 0xb5, 0x60, 0x97, 0x87, 0xfd, 0xee, 0x7d, 0x4e, 0xf9, 0x34, 0xa5, 0xfd, 0x44, 0xc1, 0x8b, 0x80, 0xfa, 0xd9, 0xfd, 0x2f, 0x5a, 0x73, 0xa6, 0x70, 0xc2, 0xab, 0x2c, 0xcb, 0x2e, 0x4a, 0x84, 0x0b, 0x8b, 0xcf, 0x8d, 0xac, 0xe4, 0xfe, 0x25, 0x86, 0x1d, 0xe2, 0x96, 0xfe, 0x0a, 0xd3, 0x7c, 0xdd, 0x9d, 0xb2, 0xf6, 0xd6, 0x28, 0x85, 0xb3, 0x86, 0x6d, 0x78, 0xe9, 0xb7, 0x9f}
ed25519Seed = [32]byte{0x40, 0xb7, 0xd9, 0xb5, 0x60, 0x97, 0x87, 0xfd, 0xee, 0x7d, 0x4e, 0xf9, 0x34, 0xa5, 0xfd, 0x44, 0xc1, 0x8b, 0x80, 0xfa, 0xd9, 0xfd, 0x2f, 0x5a, 0x73, 0xa6, 0x70, 0xc2, 0xab, 0x2c, 0xcb, 0x2e}
ed25519PrivJWK, _ = jwk.NewEdDSAPrivateKey(ed25519Seed[:], ed25519PublicKey[:], "key_id")
ed448PrivateKey = [144]byte{0xFF}
ed448PrivJWK, _ = jwk.NewEdDSAPrivateKey(ed448PrivateKey[:], ed448PublicKey[:], "key_id")
ed448EmptyPrivateKey = [144]byte{0x0}
)

func TestNewSettings(t *testing.T) {
tests := []struct {
name string
key jwk.JWK
want Settings
wantErr bool
}{
{"Normal", ed25519PrivJWK, Settings{Ed25519, ed25519.PrivateKey(ed25519PrivateKey[:]), ed448EmptyPrivateKey, "key_id", ""}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewSettings(tt.key)
if (err != nil) != tt.wantErr {
t.Errorf("NewSettings() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewSettings() = %v, want %v", got, tt.want)
}
})
}
}

func TestNewSettingsWithKeyURL(t *testing.T) {
type args struct {
key jwk.JWK
keyurl string
}
tests := []struct {
name string
args args
want Settings
wantErr bool
}{
{"Ed25519", args{ed25519PrivJWK, "key_url"}, Settings{Ed25519, ed25519.PrivateKey(ed25519PrivateKey[:]), ed448EmptyPrivateKey, "key_id", "key_url"}, false},
{"Ed448", args{ed448PrivJWK, "key_url"}, Settings{Ed448, ed25519.PrivateKey(nil), ed448PrivateKey, "key_id", "key_url"}, false},
{"Wrong length", args{invalidJWK, "key_url"}, Settings{}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewSettingsWithKeyURL(tt.args.key, tt.args.keyurl)
if (err != nil) != tt.wantErr {
t.Errorf("NewSettingsWithKeyID() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewSettingsWithKeyID() = %v, want %v", got, tt.want)
}
})
}
}

func Test_generateEd25519Keys(t *testing.T) {
random := rand.Reader
rand.Reader = bytes.NewReader(nil)
Expand Down Expand Up @@ -45,20 +106,23 @@ func Test_generateEd448Keys(t *testing.T) {
var (
ed25519PublicKey = [32]byte{0x4a, 0x84, 0x0b, 0x8b, 0xcf, 0x8d, 0xac, 0xe4, 0xfe, 0x25, 0x86, 0x1d, 0xe2, 0x96, 0xfe, 0x0a, 0xd3, 0x7c, 0xdd, 0x9d, 0xb2, 0xf6, 0xd6, 0x28, 0x85, 0xb3, 0x86, 0x6d, 0x78, 0xe9, 0xb7, 0x9f}
ed448PublicKey = [56]byte{0x00}
ed25519PubJWK, _ = jwk.NewEdDSAPublicKey(ed25519PublicKey[:], "key_id")
ed448PubJWK, _ = jwk.NewEdDSAPublicKey(ed448PublicKey[:], "key_id")
invalidJWK = jwk.NewBasic(nil, "key_id")
)

func TestProvider_AddPublicKey(t *testing.T) {
tests := []struct {
name string
p *Provider
key publickey.PublicKey
key jwk.JWK
wantErr bool
}{
{"Ed25519", &Provider{c2: make(map[string]ed25519.PublicKey), c4: make(map[string][56]byte)}, publickey.New(ed25519PublicKey[:], "key_id"), false},
{"Ed25519 already exists", &Provider{c2: map[string]ed25519.PublicKey{"key_id": ed25519.PublicKey(ed25519PublicKey[:])}, c4: make(map[string][56]byte)}, publickey.New(ed25519PublicKey[:], "key_id"), true},
{"Ed448", &Provider{c2: make(map[string]ed25519.PublicKey), c4: make(map[string][56]byte)}, publickey.New(ed448PublicKey[:], "key_id"), false},
{"Ed448 already exists", &Provider{c2: make(map[string]ed25519.PublicKey), c4: map[string][56]byte{"key_id": ed448PublicKey}}, publickey.New(ed448PublicKey[:], "key_id"), true},
{"Invalid public key length", &Provider{c2: make(map[string]ed25519.PublicKey), c4: make(map[string][56]byte)}, publickey.New(nil, "key_id"), true},
{"Ed25519", &Provider{c2: make(map[string]ed25519.PublicKey), c4: make(map[string][56]byte)}, ed25519PubJWK, false},
{"Ed25519 already exists", &Provider{c2: map[string]ed25519.PublicKey{"key_id": ed25519.PublicKey(ed25519PublicKey[:])}, c4: make(map[string][56]byte)}, ed25519PubJWK, true},
{"Ed448", &Provider{c2: make(map[string]ed25519.PublicKey), c4: make(map[string][56]byte)}, ed448PubJWK, false},
{"Ed448 already exists", &Provider{c2: make(map[string]ed25519.PublicKey), c4: map[string][56]byte{"key_id": ed448PublicKey}}, ed448PubJWK, true},
{"Invalid JWK", &Provider{c2: make(map[string]ed25519.PublicKey), c4: make(map[string][56]byte)}, invalidJWK, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -89,11 +153,11 @@ func TestProvider_CurrentKey(t *testing.T) {
tests := []struct {
name string
p Provider
want publickey.PublicKey
want jwk.JWK
}{
{"Ed25519", Provider{curve: Ed25519, settings: Settings{kid: "key_id"}, c2: map[string]ed25519.PublicKey{"key_id": ed25519.PublicKey(ed25519PublicKey[:])}}, publickey.New(ed25519PublicKey[:], "key_id")},
{"Ed448", Provider{curve: Ed448, settings: Settings{kid: "key_id"}, c4: map[string][56]byte{"key_id": ed448PublicKey}}, publickey.New(ed448PublicKey[:], "key_id")},
{"Invalid curve", Provider{curve: 12}, publickey.PublicKey{}},
{"Ed25519", Provider{curve: Ed25519, settings: Settings{kid: "key_id"}, c2: map[string]ed25519.PublicKey{"key_id": ed25519.PublicKey(ed25519PublicKey[:])}}, ed25519PubJWK},
{"Ed448", Provider{curve: Ed448, settings: Settings{kid: "key_id"}, c4: map[string][56]byte{"key_id": ed448PublicKey}}, ed448PubJWK},
{"Invalid curve", Provider{curve: 12}, jwk.JWK{}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
34 changes: 0 additions & 34 deletions alg-eddsa/settings.go

This file was deleted.

Loading