From 0685dac005492e2456f472d885502ad557d45086 Mon Sep 17 00:00:00 2001 From: aeneasr <3372410+aeneasr@users.noreply.github.com> Date: Wed, 27 Dec 2023 14:55:26 +0100 Subject: [PATCH] feat: add flag to export public keys --- cmd/cmd_create_jwks.go | 12 +++++++++++- cmd/cmd_create_jwks_test.go | 8 ++++++++ cmd/cmd_get_jwks.go | 23 ++++++++++++++++++++--- cmd/cmd_get_jwks_test.go | 14 +++++++++++++- jwk/helper.go | 33 +++++++++++++++++++++++++++++++++ jwk/helper_test.go | 20 ++++++++++++++++++++ 6 files changed, 105 insertions(+), 5 deletions(-) diff --git a/cmd/cmd_create_jwks.go b/cmd/cmd_create_jwks.go index f5b29682a1c..a96705af335 100644 --- a/cmd/cmd_create_jwks.go +++ b/cmd/cmd_create_jwks.go @@ -6,6 +6,8 @@ package cmd import ( "context" + "github.com/ory/hydra/v2/jwk" + "github.com/spf13/cobra" hydra "github.com/ory/hydra-client-go/v2" @@ -46,12 +48,20 @@ func NewCreateJWKSCmd() *cobra.Command { return cmdx.PrintOpenAPIError(cmd, err) } + if flagx.MustGetBool(cmd, "public") { + jwks.Keys, err = jwk.OnlyPublicSDKKeys(jwks.Keys) + if err != nil { + return err + } + } + cmdx.PrintTable(cmd, &outputJSONWebKeyCollection{Keys: jwks.Keys, Set: args[0]}) return nil }, } - cmd.Root().Name() + cmd.Flags().String(alg, "RS256", "The algorithm to be used to generated they key. Supports: RS256, RS512, ES256, ES512, EdDSA") cmd.Flags().String(use, "sig", "The intended use of this key. Supports: sig, enc") + cmd.Flags().Bool("public", false, "Only return public keys") return cmd } diff --git a/cmd/cmd_create_jwks_test.go b/cmd/cmd_create_jwks_test.go index 5acc1c7867e..bfacdb51721 100644 --- a/cmd/cmd_create_jwks_test.go +++ b/cmd/cmd_create_jwks_test.go @@ -33,4 +33,12 @@ func TestCreateJWKS(t *testing.T) { require.NoError(t, err) assert.Equal(t, expected.Keys[0].KeyID, actual.Get("keys.0.kid").String()) }) + + t.Run("case=gets jwks public", func(t *testing.T) { + set := uuid.Must(uuid.NewV4()).String() + actual := gjson.Parse(cmdx.ExecNoErr(t, c, set, "--use", "enc", "--alg", "RS256", "--public")) + + assert.NotEmptyf(t, actual.Get("keys.0.kid").String(), "Expected kid to be set but got: %s", actual.Raw) + assert.Empty(t, actual.Get("keys.0.p").String(), "public key should not contain private key components: %s", actual.Raw) + }) } diff --git a/cmd/cmd_get_jwks.go b/cmd/cmd_get_jwks.go index e6a7b0c6c21..949d00f7178 100644 --- a/cmd/cmd_get_jwks.go +++ b/cmd/cmd_get_jwks.go @@ -6,20 +6,28 @@ package cmd import ( "github.com/spf13/cobra" + "github.com/ory/hydra/v2/jwk" + "github.com/ory/x/flagx" + "github.com/ory/hydra/v2/cmd/cliclient" "github.com/ory/x/cmdx" ) func NewGetJWKSCmd() *cobra.Command { - return &cobra.Command{ + cmd := &cobra.Command{ Use: "jwk set-1 [set-2] ...", Aliases: []string{"jwks"}, Args: cobra.MinimumNArgs(1), Short: "Get one or more JSON Web Key Set by its ID(s)", Long: `This command gets all the details about an JSON Web Key. You can use this command in combination with jq.`, - Example: `To get the JSON Web Key Set's secret, run: + Example: `To get the JSON Web Key Set's use, run: + + {{ .CommandPath }} | jq -r '.[].use' - {{ .CommandPath }} | jq -r '.[].use'`, +To get the JSON Web Key Set as only public keys: + + {{ .CommandPath }} --public ' +`, RunE: func(cmd *cobra.Command, args []string) error { m, _, err := cliclient.NewClient(cmd) if err != nil { @@ -36,6 +44,13 @@ func NewGetJWKSCmd() *cobra.Command { sets.Keys = append(sets.Keys, key.Keys...) } + if flagx.MustGetBool(cmd, "public") { + sets.Keys, err = jwk.OnlyPublicSDKKeys(sets.Keys) + if err != nil { + return err + } + } + if len(sets.Keys) == 1 { cmdx.PrintRow(cmd, outputJsonWebKey{Set: args[0], JsonWebKey: sets.Keys[0]}) } else if len(sets.Keys) > 1 { @@ -45,4 +60,6 @@ func NewGetJWKSCmd() *cobra.Command { return nil }, } + cmd.Flags().Bool("public", false, "Only return public keys") + return cmd } diff --git a/cmd/cmd_get_jwks_test.go b/cmd/cmd_get_jwks_test.go index 03d4727dcf6..7f60d15119d 100644 --- a/cmd/cmd_get_jwks_test.go +++ b/cmd/cmd_get_jwks_test.go @@ -17,7 +17,7 @@ import ( "github.com/ory/x/cmdx" ) -func TestGetJwks(t *testing.T) { +func TestGetJWKS(t *testing.T) { ctx := context.Background() c := cmd.NewGetJWKSCmd() reg := setup(t, c) @@ -34,4 +34,16 @@ func TestGetJwks(t *testing.T) { assert.Equal(t, expected.Keys[0].KeyID, actual.Get("kid").String()) }) + + t.Run("case=gets jwks public", func(t *testing.T) { + actual := gjson.Parse(cmdx.ExecNoErr(t, c, set, "--public")) + + expected, err := reg.KeyManager().GetKeySet(ctx, set) + require.NoError(t, err) + + assert.Equal(t, expected.Keys[0].KeyID, actual.Get("kid").String()) + + assert.NotEmptyf(t, actual.Get("kid").String(), "Expected kid to be set but got: %s", actual.Raw) + assert.Empty(t, actual.Get("p").String(), "public key should not contain private key components: %s", actual.Raw) + }) } diff --git a/jwk/helper.go b/jwk/helper.go index 8f0ee3c8d9f..50f3a28b2d2 100644 --- a/jwk/helper.go +++ b/jwk/helper.go @@ -4,14 +4,18 @@ package jwk import ( + "bytes" "context" "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" "crypto/x509" + "encoding/json" "encoding/pem" "sync" + hydra "github.com/ory/hydra-client-go/v2" + "github.com/ory/x/josex" "github.com/ory/x/errorsx" @@ -149,3 +153,32 @@ func PEMBlockForKey(key interface{}) (*pem.Block, error) { return nil, errors.New("Invalid key type") } } + +func OnlyPublicSDKKeys(in []hydra.JsonWebKey) (out []hydra.JsonWebKey, _ error) { + var interim []jose.JSONWebKey + var b bytes.Buffer + + if err := json.NewEncoder(&b).Encode(&in); err != nil { + return nil, errors.Wrap(err, "failed to encode JSON Web Key Set") + } + + if err := json.NewDecoder(&b).Decode(&interim); err != nil { + return nil, errors.Wrap(err, "failed to encode JSON Web Key Set") + } + + for i, key := range interim { + interim[i] = key.Public() + } + + b.Reset() + if err := json.NewEncoder(&b).Encode(&interim); err != nil { + return nil, errors.Wrap(err, "failed to encode JSON Web Key Set") + } + + var keys []hydra.JsonWebKey + if err := json.NewDecoder(&b).Decode(&keys); err != nil { + return nil, errors.Wrap(err, "failed to encode JSON Web Key Set") + } + + return keys, nil +} diff --git a/jwk/helper_test.go b/jwk/helper_test.go index e9ddd61a322..2e991fd9cad 100644 --- a/jwk/helper_test.go +++ b/jwk/helper_test.go @@ -11,11 +11,14 @@ import ( "crypto/ed25519" "crypto/rsa" "crypto/x509" + "encoding/json" "encoding/pem" "io" "strings" "testing" + hydra "github.com/ory/hydra-client-go/v2" + "github.com/go-jose/go-jose/v3" "github.com/go-jose/go-jose/v3/cryptosigner" "github.com/golang/mock/gomock" @@ -267,3 +270,20 @@ func TestGetOrGenerateKeys(t *testing.T) { assert.EqualError(t, err, "key not found") }) } + +func TestOnlyPublicSDKKeys(t *testing.T) { + set, err := jwk.GenerateJWK(context.Background(), jose.RS256, "test-id-1", "sig") + require.NoError(t, err) + + out, err := json.Marshal(set) + require.NoError(t, err) + + var sdkSet hydra.JsonWebKeySet + require.NoError(t, json.Unmarshal(out, &sdkSet)) + + assert.NotEmpty(t, sdkSet.Keys[0].P) + result, err := jwk.OnlyPublicSDKKeys(&sdkSet) + require.NoError(t, err) + + assert.Empty(t, result.Keys[0].P) +}