You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
smallstep-certificates/kms/azurekms/signer.go

161 lines
4.0 KiB
Go

package azurekms
import (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"encoding/base64"
"io"
"math/big"
"github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault"
"github.com/pkg/errors"
"golang.org/x/crypto/cryptobyte"
"golang.org/x/crypto/cryptobyte/asn1"
)
// Signer implements a crypto.Signer using the AWS KMS.
type Signer struct {
client KeyVaultClient
vaultBaseURL string
name string
version string
publicKey crypto.PublicKey
}
// NewSigner creates a new signer using a key in the AWS KMS.
func NewSigner(client KeyVaultClient, signingKey string) (crypto.Signer, error) {
vault, name, version, _, err := parseKeyName(signingKey)
if err != nil {
return nil, err
}
// Make sure that the key exists.
signer := &Signer{
client: client,
vaultBaseURL: vaultBaseURL(vault),
name: name,
version: version,
}
if err := signer.preloadKey(); err != nil {
return nil, err
}
return signer, nil
}
func (s *Signer) preloadKey() error {
ctx, cancel := defaultContext()
defer cancel()
resp, err := s.client.GetKey(ctx, s.vaultBaseURL, s.name, s.version)
if err != nil {
return errors.Wrap(err, "keyVault GetKey failed")
}
s.publicKey, err = convertKey(resp.Key)
return err
}
// Public returns the public key of this signer or an error.
func (s *Signer) Public() crypto.PublicKey {
return s.publicKey
}
// Sign signs digest with the private key stored in the AWS KMS.
func (s *Signer) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
alg, err := getSigningAlgorithm(s.Public(), opts)
if err != nil {
return nil, err
}
ctx, cancel := defaultContext()
defer cancel()
b64 := base64.RawURLEncoding.EncodeToString(digest)
resp, err := s.client.Sign(ctx, s.vaultBaseURL, s.name, s.version, keyvault.KeySignParameters{
Algorithm: alg,
Value: &b64,
})
if err != nil {
return nil, errors.Wrap(err, "keyVault Sign failed")
}
sig, err := base64.RawURLEncoding.DecodeString(*resp.Result)
if err != nil {
return nil, errors.Wrap(err, "error decoding keyVault Sign result")
}
var octetSize int
switch alg {
case keyvault.ES256:
octetSize = 32 // 256-bit, concat(R,S) = 64 bytes
case keyvault.ES384:
octetSize = 48 // 384-bit, concat(R,S) = 96 bytes
case keyvault.ES512:
octetSize = 66 // 528-bit, concat(R,S) = 132 bytes
default:
return sig, nil
}
// Convert to asn1
if len(sig) != octetSize*2 {
return nil, errors.Errorf("keyVault Sign failed: unexpected signature length")
}
var b cryptobyte.Builder
b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
b.AddASN1BigInt(new(big.Int).SetBytes(sig[:octetSize])) // R
b.AddASN1BigInt(new(big.Int).SetBytes(sig[octetSize:])) // S
})
return b.Bytes()
}
func getSigningAlgorithm(key crypto.PublicKey, opts crypto.SignerOpts) (keyvault.JSONWebKeySignatureAlgorithm, error) {
switch key.(type) {
case *rsa.PublicKey:
hashFunc := opts.HashFunc()
pss, isPSS := opts.(*rsa.PSSOptions)
// Random salt lengths are not supported
if isPSS &&
pss.SaltLength != rsa.PSSSaltLengthAuto &&
pss.SaltLength != rsa.PSSSaltLengthEqualsHash &&
pss.SaltLength != hashFunc.Size() {
return "", errors.Errorf("unsupported RSA-PSS salt length %d", pss.SaltLength)
}
switch h := hashFunc; h {
case crypto.SHA256:
if isPSS {
return keyvault.PS256, nil
}
return keyvault.RS256, nil
case crypto.SHA384:
if isPSS {
return keyvault.PS384, nil
}
return keyvault.RS384, nil
case crypto.SHA512:
if isPSS {
return keyvault.PS512, nil
}
return keyvault.RS512, nil
default:
return "", errors.Errorf("unsupported hash function %v", h)
}
case *ecdsa.PublicKey:
switch h := opts.HashFunc(); h {
case crypto.SHA256:
return keyvault.ES256, nil
case crypto.SHA384:
return keyvault.ES384, nil
case crypto.SHA512:
return keyvault.ES512, nil
default:
return "", errors.Errorf("unsupported hash function %v", h)
}
default:
return "", errors.Errorf("unsupported key type %T", key)
}
}