Skip to content

Commit

Permalink
Merge pull request #27 from smallstep/jws-format
Browse files Browse the repository at this point in the history
Add support for producing JWS signatures
  • Loading branch information
maraino authored Jan 13, 2023
2 parents 385ebf5 + df34cbf commit 11ffe0c
Showing 1 changed file with 62 additions and 10 deletions.
72 changes: 62 additions & 10 deletions cmd/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,18 @@ import (
"crypto/rsa"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"io"
"math/big"
"os"

"github.com/spf13/cobra"
"go.step.sm/crypto/kms"
"go.step.sm/crypto/kms/apiv1"
"go.step.sm/crypto/sshutil"
"golang.org/x/crypto/cryptobyte"
"golang.org/x/crypto/cryptobyte/asn1"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"

Expand All @@ -49,23 +53,28 @@ or a binary data filename via the --in flag.
If you use the --in flag with an EC or RSA key, this command will generate the
digest of the data file for you.`,
Example: ` # Signs the given file using a key in the PKCS #11 module.
step-kms-plugin --in data.bin \
Example: ` # Sign the given file using a key in the PKCS #11 module.
step-kms-plugin sign --in data.bin \
--kms 'pkcs11:module-path=/path/to/libsofthsm2.so;token=softhsm?pin-value=pass' \
'pkcs11:id=1000'
# Signs a digest using a key in Google's Cloud KMS.
step-kms-plugin 1b8de4254213f8c3f784b3da4611eaeec1e720e74b4357029f8271b4ef9e1c2c \
# Sign a digest using a key in Google's Cloud KMS.
step-kms-plugin sign 1b8de4254213f8c3f784b3da4611eaeec1e720e74b4357029f8271b4ef9e1c2c \
--kms cloudkms: \
projects/my-project/locations/us-west1/keyRings/my-keyring/cryptoKeys/my-rsa-key/cryptoKeyVersions/1
# Signs and verify using RSA PKCS #1 with SHA512:
step-kms-plugin --in data.bin --verify --alg SHA512 \
# Sign and verify using RSA PKCS #1 with SHA512:
step-kms-plugin sign --in data.bin --verify --alg SHA512 \
--kms 'pkcs11:module-path=/path/to/libsofthsm2.so;token=softhsm?pin-value=pass' \
'pkcs11:object=my-rsa-key'
# Sign a file using an Ed25519 key in the ssh-agent :
step-kms-plugin sign --in data.bin sshagentkms:user@localhost`,
# Sign a file using an Ed25519 key in the ssh-agent:
step-kms-plugin sign --in data.bin sshagentkms:user@localhost
# Sign the header and payload of a JWT to produce the signature:
step-kms-plugin sign --in data.jwt --format jws \
--kms 'pkcs11:module-path=/path/to/libsofthsm2.so;token=softhsm?pin-value=pass' \
'pkcs11:id=1000`,
RunE: func(cmd *cobra.Command, args []string) error {
if l := len(args); l != 1 && l != 2 {
return showErrUsage(cmd)
Expand Down Expand Up @@ -150,6 +159,11 @@ digest of the data file for you.`,
switch format {
case "hex":
fmt.Println(hex.EncodeToString(sig))
case "jws":
if sig, err = jwsSignature(sig, pub); err != nil {
return err
}
fmt.Println(base64.RawURLEncoding.EncodeToString(sig))
case "raw":
os.Stdout.Write(sig)
default:
Expand All @@ -171,6 +185,44 @@ func signsRawInput(pub crypto.PublicKey) bool {
}
}

func jwsSignature(sig []byte, pub crypto.PublicKey) ([]byte, error) {
ec, ok := pub.(*ecdsa.PublicKey)
if !ok {
return sig, nil
}

var r, s big.Int
var inner cryptobyte.String
input := cryptobyte.String(sig)
if !input.ReadASN1(&inner, asn1.SEQUENCE) ||
!input.Empty() ||
!inner.ReadASN1Integer(&r) ||
!inner.ReadASN1Integer(&s) ||
!inner.Empty() {
return nil, errors.New("failed decoding ASN.1 signature")
}

curveBits := ec.Curve.Params().BitSize
keyBytes := curveBits / 8
if curveBits%8 > 0 {
keyBytes++
}

// We serialize the outputs (r and s) into big-endian byte arrays and pad
// them with zeros on the left to make sure the sizes work out. Both arrays
// must be keyBytes long, and the output must be 2*keyBytes long.
rBytes := r.Bytes()
rBytesPadded := make([]byte, keyBytes)
copy(rBytesPadded[keyBytes-len(rBytes):], rBytes)

sBytes := s.Bytes()
sBytesPadded := make([]byte, keyBytes)
copy(sBytesPadded[keyBytes-len(sBytes):], sBytes)

//nolint:makezero // we actually want the 0 bytes padding
return append(rBytesPadded, sBytesPadded...), nil
}

func getSignerOptions(pub crypto.PublicKey, alg string, pss bool) (crypto.SignerOpts, error) {
switch k := pub.(type) {
case *ecdsa.PublicKey:
Expand Down Expand Up @@ -273,11 +325,11 @@ func init() {
flags.SortFlags = false

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

flags.Var(alg, "alg", "The hashing `algorithm` to use on RSA PKCS #1 and RSA-PSS signatures.\nOptions are SHA256, SHA384 or SHA512")
flags.Bool("pss", false, "Use RSA-PSS signature scheme instead of RSA PKCS #1")
flags.Var(format, "format", "The `format` to print the signature.\nOptions are base64, hex, or raw")
flags.Var(format, "format", "The `format` to print the signature.\nOptions are base64, hex, jws, or raw")
flags.String("in", "", "The `file` to sign. Required for Ed25519 keys.")
flags.Bool("verify", false, "Verify the signature with the public key")
}

0 comments on commit 11ffe0c

Please sign in to comment.