Skip to content

Commit

Permalink
jwk: Adds jwk rotation and improves jwk codebase
Browse files Browse the repository at this point in the history
  • Loading branch information
aeneasr committed Jun 20, 2018
1 parent ba707f8 commit 521d3f6
Show file tree
Hide file tree
Showing 22 changed files with 210 additions and 47 deletions.
42 changes: 41 additions & 1 deletion cmd/cli/handler_jwk.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/ory/hydra/config"
hydra "github.com/ory/hydra/sdk/go/hydra/swagger"
"github.com/spf13/cobra"
"os"
)

type JWKHandler struct {
Expand Down Expand Up @@ -57,6 +58,44 @@ func newJWKHandler(c *config.Config) *JWKHandler {
return &JWKHandler{Config: c}
}

func (h *JWKHandler) RotateKeys(cmd *cobra.Command, args []string) {
m := h.newJwkManager(cmd)
if len(args) < 1 || len(args) > 2 {
fmt.Println(cmd.UsageString())
return
}

setID := args[0]
kid := ""
if len(args) == 2 {
kid = args[1]
}

set, response, err := m.GetJsonWebKeySet(setID)
checkResponse(response, err, http.StatusOK)

var found bool
for _, key := range set.Keys {
if len(kid) == 0 || (len(kid) > 0 && kid == key.Kid) {
found = true
response, err := m.DeleteJsonWebKey(setID, key.Kid)
checkResponse(response, err, http.StatusNoContent)

_, response, err = m.CreateJsonWebKeySet(setID, hydra.JsonWebKeySetGeneratorRequest{Alg: key.Alg/*, Use: key.Use*/, Kid: kid})
checkResponse(response, err, http.StatusNoContent)
}
}

if !found {
if kid == "" {
fmt.Fprintln(os.Stderr, "The JSON Web Key Set does not contain any keys, thus no keys could be rotated.")
} else {
fmt.Fprintf(os.Stderr, "The JSON Web Key Set does not contain key with kid \"%s\" keys, thus the key could be rotated.\n", kid)
}
os.Exit(1)
}
}

func (h *JWKHandler) CreateKeys(cmd *cobra.Command, args []string) {
m := h.newJwkManager(cmd)
if len(args) < 1 || len(args) > 2 {
Expand All @@ -70,7 +109,8 @@ func (h *JWKHandler) CreateKeys(cmd *cobra.Command, args []string) {
}

alg, _ := cmd.Flags().GetString("alg")
keys, response, err := m.CreateJsonWebKeySet(args[0], hydra.JsonWebKeySetGeneratorRequest{Alg: alg, Kid: kid})
// use, _ := cmd.Flags().GetString("use")
keys, response, err := m.CreateJsonWebKeySet(args[0], hydra.JsonWebKeySetGeneratorRequest{Alg: alg, Kid: kid/*, Use: use*/})
checkResponse(response, err, http.StatusCreated)
fmt.Printf("%s\n", formatResponse(keys))
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/keys_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var keysCreateCmd = &cobra.Command{

func init() {
keysCmd.AddCommand(keysCreateCmd)
keysCreateCmd.Flags().StringP("alg", "a", "", "REQUIRED name that identifies the algorithm intended for use with the key. Supports: RS256, ES512, HS256")
keysCreateCmd.Flags().StringP("alg", "a", "RS256", "The algorithm to be used to generated they key. Supports: RS256, ES512, HS256")
keysCreateCmd.Flags().StringP("use", "u", "sig", "The intended use of this key")

}
43 changes: 43 additions & 0 deletions cmd/keys_rotate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright © 2018 NAME HERE <EMAIL ADDRESS>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
"fmt"

"github.com/spf13/cobra"
)

// rotateCmd represents the rotate command
var rotateCmd = &cobra.Command{
Use: "rotate <set> [<kid>] ",
Short: "Rotates keys",
Long: `This command rotates either all keys of a set, or, if provided, the specified kid only.`,
Run: cmdHandler.Keys.RotateKeys,
}

func init() {
keysCmd.AddCommand(rotateCmd)

// Here you will define your flags and configuration settings.

// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// rotateCmd.PersistentFlags().String("foo", "", "A help for foo")

// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// rotateCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
1 change: 1 addition & 0 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func TestExecute(t *testing.T) {
{args: []string{"keys", "create", "foo", "--endpoint", endpoint, "-a", "HS256"}},
{args: []string{"keys", "get", "--endpoint", endpoint, "foo"}},
{args: []string{"keys", "delete", "--endpoint", endpoint, "foo"}},
{args: []string{"keys", "rotate", "--endpoint", endpoint, "foo"}},
{args: []string{"token", "revoke", "--endpoint", endpoint, "--client-secret", "foobar", "--client-id", "foobarbaz", "foo"}},
{args: []string{"token", "client", "--endpoint", endpoint, "--client-secret", "foobar", "--client-id", "foobarbaz"}},
{args: []string{"help", "migrate", "sql"}},
Expand Down
2 changes: 1 addition & 1 deletion cmd/server/helper_keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func createOrGetJWK(c *config.Config, set string, prefix string) (key *jose.JSON

func createJWKS(ctx *config.Context, set string) (*jose.JSONWebKeySet, error) {
generator := jwk.RS256Generator{}
keys, err := generator.Generate("")
keys, err := generator.Generate("", "sig")
if err != nil {
return nil, errors.Wrapf(err, "Could not generate %s key", set)
}
Expand Down
2 changes: 1 addition & 1 deletion integration/sql_schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func TestSQLSchema(t *testing.T) {
}

var testGenerator = &jwk.RS256Generator{}
ks, _ := testGenerator.Generate("foo")
ks, _ := testGenerator.Generate("foo", "sig")
p1 := ks.Key("private:foo")
r := fosite.NewRequest()
r.ID = "foo"
Expand Down
2 changes: 1 addition & 1 deletion jwk/cast_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestMustRSAPrivate(t *testing.T) {
// t.SkipNow()
//}

keys, err := new(RS256Generator).Generate("foo")
keys, err := new(RS256Generator).Generate("foo", "sig")
assert.Nil(t, err)

_, err = ToRSAPrivate(&keys.Key("private:foo")[0])
Expand Down
24 changes: 24 additions & 0 deletions jwk/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,30 @@

package jwk

import "encoding/json"

// swagger:model jsonWebKeySetGeneratorRequest
type createRequest struct {
// The algorithm to be used for creating the key. Supports "RS256", "ES512", "HS512", and "HS256"
// required: true
Algorithm string `json:"alg"`

// The "use" (public key use) parameter identifies the intended use of
// the public key. The "use" parameter is employed to indicate whether
// a public key is used for encrypting data or verifying the signature
// on data. Valid values are "enc" and "sig".
// required: true
Use string `json:"use"`

// The kid of the key to be created
// required: true
KeyID string `json:"kid"`
}

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

// swagger:parameters getJsonWebKey deleteJsonWebKey
type swaggerJsonWebKeyQuery struct {
// The kid of the desired key
Expand Down
2 changes: 1 addition & 1 deletion jwk/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ package jwk
import "github.com/square/go-jose"

type KeyGenerator interface {
Generate(id string) (*jose.JSONWebKeySet, error)
Generate(id, use string) (*jose.JSONWebKeySet, error)
}
4 changes: 3 additions & 1 deletion jwk/generator_ecdsa256.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import (

type ECDSA256Generator struct{}

func (g *ECDSA256Generator) Generate(id string) (*jose.JSONWebKeySet, error) {
func (g *ECDSA256Generator) Generate(id, use string) (*jose.JSONWebKeySet, error) {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, errors.Errorf("Could not generate key because %s", err)
Expand All @@ -42,11 +42,13 @@ func (g *ECDSA256Generator) Generate(id string) (*jose.JSONWebKeySet, error) {
Keys: []jose.JSONWebKey{
{
Key: key,
Use: use,
KeyID: ider("private", id),
Certificates: []*x509.Certificate{},
},
{
Key: &key.PublicKey,
Use: use,
KeyID: ider("public", id),
Certificates: []*x509.Certificate{},
},
Expand Down
4 changes: 3 additions & 1 deletion jwk/generator_ecdsa521.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import (

type ECDSA512Generator struct{}

func (g *ECDSA512Generator) Generate(id string) (*jose.JSONWebKeySet, error) {
func (g *ECDSA512Generator) Generate(id, use string) (*jose.JSONWebKeySet, error) {
key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
if err != nil {
return nil, errors.Errorf("Could not generate key because %s", err)
Expand All @@ -42,11 +42,13 @@ func (g *ECDSA512Generator) Generate(id string) (*jose.JSONWebKeySet, error) {
Keys: []jose.JSONWebKey{
{
Key: key,
Use: use,
KeyID: ider("private", id),
Certificates: []*x509.Certificate{},
},
{
Key: &key.PublicKey,
Use: use,
KeyID: ider("public", id),
Certificates: []*x509.Certificate{},
},
Expand Down
3 changes: 2 additions & 1 deletion jwk/generator_hs256.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import (

type HS256Generator struct{}

func (g *HS256Generator) Generate(id string) (*jose.JSONWebKeySet, error) {
func (g *HS256Generator) Generate(id, use string) (*jose.JSONWebKeySet, error) {
// Taken from NewHMACKey
key := &[16]byte{}
_, err := io.ReadFull(rand.Reader, key[:])
Expand All @@ -50,6 +50,7 @@ func (g *HS256Generator) Generate(id string) (*jose.JSONWebKeySet, error) {
Keys: []jose.JSONWebKey{
{
Algorithm: "HS256",
Use: use,
Key: sliceKey,
KeyID: id,
Certificates: []*x509.Certificate{},
Expand Down
3 changes: 2 additions & 1 deletion jwk/generator_hs512.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import (

type HS512Generator struct{}

func (g *HS512Generator) Generate(id string) (*jose.JSONWebKeySet, error) {
func (g *HS512Generator) Generate(id, use string) (*jose.JSONWebKeySet, error) {
// Taken from NewHMACKey
key := &[32]byte{}
_, err := io.ReadFull(rand.Reader, key[:])
Expand All @@ -51,6 +51,7 @@ func (g *HS512Generator) Generate(id string) (*jose.JSONWebKeySet, error) {
{
Algorithm: "HS512",
Key: sliceKey,
Use: use,
KeyID: id,
Certificates: []*x509.Certificate{},
},
Expand Down
4 changes: 3 additions & 1 deletion jwk/generator_rs256.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type RS256Generator struct {
KeyLength int
}

func (g *RS256Generator) Generate(id string) (*jose.JSONWebKeySet, error) {
func (g *RS256Generator) Generate(id, use string) (*jose.JSONWebKeySet, error) {
if g.KeyLength < 4096 {
g.KeyLength = 4096
}
Expand All @@ -52,11 +52,13 @@ func (g *RS256Generator) Generate(id string) (*jose.JSONWebKeySet, error) {
{
Algorithm: "RS256",
Key: key,
Use: use,
KeyID: ider("private", id),
Certificates: []*x509.Certificate{},
},
{
Algorithm: "RS256",
Use: use,
Key: &key.PublicKey,
KeyID: ider("public", id),
Certificates: []*x509.Certificate{},
Expand Down
28 changes: 22 additions & 6 deletions jwk/generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,49 +36,65 @@ func TestGenerator(t *testing.T) {

for k, c := range []struct {
g KeyGenerator
use string
check func(*jose.JSONWebKeySet)
}{
{
g: &RS256Generator{},
g: &RS256Generator{},
use: "sig",
check: func(ks *jose.JSONWebKeySet) {
assert.Len(t, ks, 2)
assert.NotEmpty(t, ks.Keys[0].Key)
assert.NotEmpty(t, ks.Keys[1].Key)
assert.Equal(t, "sig", ks.Keys[0].Use)
assert.Equal(t, "sig", ks.Keys[1].Use)
},
},
{
g: &ECDSA512Generator{},
g: &ECDSA512Generator{},
use: "enc",
check: func(ks *jose.JSONWebKeySet) {
assert.Len(t, ks, 2)
assert.NotEmpty(t, ks.Keys[0].Key)
assert.NotEmpty(t, ks.Keys[1].Key)
assert.Equal(t, "enc", ks.Keys[0].Use)
assert.Equal(t, "enc", ks.Keys[1].Use)
},
},
{
g: &ECDSA256Generator{},
g: &ECDSA256Generator{},
use: "sig",
check: func(ks *jose.JSONWebKeySet) {
assert.Len(t, ks, 2)
assert.NotEmpty(t, ks.Keys[0].Key)
assert.NotEmpty(t, ks.Keys[1].Key)
assert.Equal(t, "sig", ks.Keys[0].Use)
assert.Equal(t, "sig", ks.Keys[1].Use)
},
},
{
g: &HS256Generator{},
g: &HS256Generator{},
use: "sig",
check: func(ks *jose.JSONWebKeySet) {
assert.Len(t, ks, 1)
assert.NotEmpty(t, ks.Keys[0].Key)
assert.Equal(t, "sig", ks.Keys[0].Use)
assert.Equal(t, "sig", ks.Keys[1].Use)
},
},
{
g: &HS512Generator{},
g: &HS512Generator{},
use: "enc",
check: func(ks *jose.JSONWebKeySet) {
assert.Len(t, ks, 1)
assert.NotEmpty(t, ks.Keys[0].Key)
assert.Equal(t, "enc", ks.Keys[0].Use)
assert.Equal(t, "enc", ks.Keys[1].Use)
},
},
} {
t.Run(fmt.Sprintf("case=%d", k), func(t *testing.T) {
keys, err := c.g.Generate("foo")
keys, err := c.g.Generate("foo", c.use)
require.NoError(t, err)
if err != nil {
c.check(keys)
Expand Down
Loading

0 comments on commit 521d3f6

Please sign in to comment.