Skip to content

Commit

Permalink
Add implementation for encrypt and decrypt commands.
Browse files Browse the repository at this point in the history
  • Loading branch information
maraino committed Oct 19, 2022
1 parent 2fb6872 commit df5b775
Show file tree
Hide file tree
Showing 4 changed files with 347 additions and 47 deletions.
152 changes: 152 additions & 0 deletions cmd/decrypt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// Copyright 2022 Smallstep Labs, Inc.
//
// 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 (
"context"
"crypto"
"crypto/rand"
"crypto/rsa"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"os"

"github.com/smallstep/step-kms-plugin/internal/flagutil"
"github.com/spf13/cobra"
"go.step.sm/crypto/kms"
"go.step.sm/crypto/kms/apiv1"
)

// decryptCmd represents the decrypt command
var decryptCmd = &cobra.Command{
Use: "decrypt <uri>",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
RunE: func(cmd *cobra.Command, args []string) error {
if l := len(args); l != 1 {
return showErrUsage(cmd)
}

flags := cmd.Flags()
kuri := flagutil.MustString(flags, "kms")
if kuri == "" {
kuri = args[0]
}

oaep := flagutil.MustBool(flags, "oaep")
format := flagutil.MustString(flags, "format")
in := flagutil.MustString(flags, "in")

// OAEP requires a hash algorithm.
// It uses SHA256 by default.
var hash crypto.Hash
var err error
if oaep {
alg := flagutil.MustString(flags, "alg")
if hash, err = getHashAlgorithm(alg); err != nil {
return err
}
}

var src, data []byte
if in != "" {
if src, err = os.ReadFile(in); err != nil {
return fmt.Errorf("failed to read file %q: %w", in, err)
}
} else {
if src, err = io.ReadAll(os.Stdin); err != nil {
return fmt.Errorf("failed to read from STDIN: %w", err)
}
}

switch format {
case "hex":
size := hex.DecodedLen(len(src))
data = make([]byte, size)
n, err := hex.Decode(data, src)
if err != nil {
return fmt.Errorf("failed to decode input: %w", err)
}
data = data[:n]
case "raw":
data = src
default:
size := base64.StdEncoding.DecodedLen(len(src))
data = make([]byte, size)
n, err := base64.StdEncoding.Decode(data, src)
if err != nil {
return fmt.Errorf("failed to decode input: %w", err)
}
data = data[:n]
}

km, err := kms.New(context.Background(), apiv1.Options{
URI: kuri,
})
if err != nil {
return fmt.Errorf("failed to load key manager: %w", err)
}
defer km.Close()

dec, ok := km.(apiv1.Decrypter)
if !ok {
return fmt.Errorf("%s does not implement Decrypter", kuri)
}

d, err := dec.CreateDecrypter(&apiv1.CreateDecrypterRequest{
DecryptionKey: kuri,
})
if err != nil {
return err
}

var opts crypto.DecrypterOpts
if oaep {
opts = &rsa.OAEPOptions{
Hash: hash,
Label: []byte(DefaultOEAPLabel),
}
}

b, err := d.Decrypt(rand.Reader, data, opts)
if err != nil {
return fmt.Errorf("error decrypting input: %w", err)
}
os.Stdout.Write(b)
return nil
},
}

func init() {
rootCmd.AddCommand(decryptCmd)
decryptCmd.SilenceUsage = true

flags := decryptCmd.Flags()
flags.SortFlags = false

alg := flagutil.NormalizedValue("alg", []string{"SHA256", "SHA384", "SHA512"}, "SHA256")
format := flagutil.LowerValue("format", []string{"base64", "hex", "raw"}, "base64")

flags.Bool("oaep", false, "Use RSA-OAEP instead of RSA PKCS #1 v1.5")
flags.Var(alg, "alg", "The hashing `algorithm` to use on RSA-OAEP.\nOptions are SHA256, SHA384 or SHA512")
flags.Var(format, "format", "The `format` to print the signature.\nOptions are base64, hex, or raw")
flags.String("in", "", "The `file` to decrypt instead of using STDIN.")
}
155 changes: 155 additions & 0 deletions cmd/encrypt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Copyright 2022 Smallstep Labs, Inc.
//
// 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 (
"context"
"crypto"
"crypto/rand"
"crypto/rsa"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"os"

"github.com/smallstep/step-kms-plugin/internal/flagutil"
"github.com/spf13/cobra"
"go.step.sm/crypto/kms"
"go.step.sm/crypto/kms/apiv1"
)

// DefaultOEAPLabel is the label used when OAEP is used.
const DefaultOEAPLabel = "step-kms-plugin/v1/oaep"

// encryptCmd represents the encrypt command
var encryptCmd = &cobra.Command{
Use: "encrypt <uri>",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
RunE: func(cmd *cobra.Command, args []string) error {
if l := len(args); l != 1 {
return showErrUsage(cmd)
}

flags := cmd.Flags()
kuri := flagutil.MustString(flags, "kms")
if kuri == "" {
kuri = args[0]
}

oaep := flagutil.MustBool(flags, "oaep")
format := flagutil.MustString(flags, "format")
in := flagutil.MustString(flags, "in")

// OAEP requires a hash algorithm.
// It uses SHA256 by default.
var hash crypto.Hash
var err error
if oaep {
alg := flagutil.MustString(flags, "alg")
if hash, err = getHashAlgorithm(alg); err != nil {
return err
}
}

// Read input
var data []byte
if in != "" {
if data, err = os.ReadFile(in); err != nil {
return fmt.Errorf("failed to read file %q: %w", in, err)
}
} else {
if data, err = io.ReadAll(os.Stdin); err != nil {
return fmt.Errorf("failed to read from STDIN: %w", err)
}
}

km, err := kms.New(context.Background(), apiv1.Options{
URI: kuri,
})
if err != nil {
return fmt.Errorf("failed to load key manager: %w", err)
}
defer km.Close()

key, err := km.GetPublicKey(&apiv1.GetPublicKeyRequest{
Name: kuri,
})
if err != nil {
return fmt.Errorf("failed to get the public key: %w", err)
}

pub, ok := key.(*rsa.PublicKey)
if !ok {
return fmt.Errorf("%s is not an RSA key", kuri)
}

var b []byte
if oaep {
if b, err = rsa.EncryptOAEP(hash.New(), rand.Reader, pub, data, []byte(DefaultOEAPLabel)); err != nil {
return err
}
} else {
if b, err = rsa.EncryptPKCS1v15(rand.Reader, pub, data); err != nil {
return err
}
}

switch format {
case "hex":
fmt.Println(hex.EncodeToString(b))
case "raw":
os.Stdout.Write(b)
default:
fmt.Println(base64.StdEncoding.EncodeToString(b))
}

return nil
},
}

func getHashAlgorithm(alg string) (crypto.Hash, error) {
switch alg {
case "", "SHA256":
return crypto.SHA256, nil
case "SHA384":
return crypto.SHA384, nil
case "SHA512":
return crypto.SHA512, nil
default:
return 0, fmt.Errorf("unsupported hashing algorithm %q", alg)
}
}

func init() {
rootCmd.AddCommand(encryptCmd)
encryptCmd.SilenceUsage = true

flags := encryptCmd.Flags()
flags.SortFlags = false

alg := flagutil.NormalizedValue("alg", []string{"SHA256", "SHA384", "SHA512"}, "SHA256")
format := flagutil.LowerValue("format", []string{"base64", "hex", "raw"}, "base64")

flags.Bool("oaep", false, "Use RSA-OAEP instead of RSA PKCS #1 v1.5")
flags.Var(alg, "alg", "The hashing `algorithm` to use on RSA-OAEP.\nOptions are SHA256, SHA384 or SHA512")
flags.Var(format, "format", "The `format` used in the input.\nOptions are base64, hex, or raw")
flags.String("in", "", "The `file` to encrypt instead of using STDIN.")
}
26 changes: 13 additions & 13 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ go 1.18
require (
github.com/spf13/cobra v1.6.0
github.com/spf13/pflag v1.0.5
go.step.sm/crypto v0.21.0
go.step.sm/crypto v0.21.1-0.20221019013358-270af151e7b8
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
)

require (
cloud.google.com/go v0.102.0 // indirect
cloud.google.com/go/compute v1.7.0 // indirect
cloud.google.com/go v0.102.1 // indirect
cloud.google.com/go/compute v1.10.0 // indirect
cloud.google.com/go/iam v0.3.0 // indirect
cloud.google.com/go/kms v1.4.0 // indirect
filippo.io/edwards25519 v1.0.0 // indirect
github.com/Azure/azure-sdk-for-go v65.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go v67.0.0+incompatible // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.28 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
Expand All @@ -30,16 +30,16 @@ require (
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/Masterminds/sprig/v3 v3.2.2 // indirect
github.com/ThalesIgnite/crypto11 v1.2.5 // indirect
github.com/aws/aws-sdk-go v1.44.111 // indirect
github.com/aws/aws-sdk-go v1.44.117 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/go-piv/piv-go v1.10.0 // indirect
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect
github.com/googleapis/gax-go/v2 v2.5.1 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
github.com/googleapis/gax-go/v2 v2.6.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
Expand All @@ -53,14 +53,14 @@ require (
github.com/spf13/cast v1.4.1 // indirect
github.com/thales-e-security/pool v0.0.2 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/net v0.0.0-20220927171203-f486391704dc // indirect
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 // indirect
golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458 // indirect
golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1 // indirect
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b // indirect
google.golang.org/api v0.98.0 // indirect
google.golang.org/api v0.99.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252 // indirect
google.golang.org/grpc v1.49.0 // indirect
google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e // indirect
google.golang.org/grpc v1.50.1 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
)
Expand Down
Loading

0 comments on commit df5b775

Please sign in to comment.