Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Crypto] fix KMS signing bug for long messages #318

Merged
merged 2 commits into from
Sep 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 29 additions & 9 deletions crypto/cloudkms/cloudkms_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ func gcloudApplicationSignin(kms cloudkms.Key) error {
// This tests requires access to KMS and cannot be run by CI.
// Please use this test manually by commenting t.Skip(),
// when making any change to the KMS signing code.
// This test assumes gcloud CLI is already installed on
// your machine.
func TestManualKMSSigning(t *testing.T) {
// to comment when testing manually
t.Skip()
Expand All @@ -113,16 +115,34 @@ func TestManualKMSSigning(t *testing.T) {
pk, _, err := cl.GetPublicKey(ctx, key)
require.NoError(t, err)

// Sign
msg := []byte("random_message")
// signer
signer, err := cl.SignerForKey(ctx, key)
require.NoError(t, err)
sig, err := signer.Sign(msg)
require.NoError(t, err)

// verify
hasher := crypto.NewSHA2_256()
valid, err := pk.Verify(sig, msg, hasher)
require.NoError(t, err)
assert.True(t, valid)
signAndVerify := func(t *testing.T, msgLen int) {
// Sign
msg := make([]byte, msgLen)
sig, err := signer.Sign(msg)
require.NoError(t, err)

// verify
hasher := crypto.NewSHA2_256()
valid, err := pk.Verify(sig, msg, hasher)
require.NoError(t, err)
assert.True(t, valid)
}

kmsPreHashLimit := 65536
// google KMS supports signing messages without prehashing
// up to 65536 bytes
t.Run("short message", func(t *testing.T) {
signAndVerify(t, kmsPreHashLimit)
})

// google KMS does not support signing messages longer than 65536
// without prehashing
t.Run("long message", func(t *testing.T) {
signAndVerify(t, kmsPreHashLimit+1)
})

}
50 changes: 45 additions & 5 deletions crypto/cloudkms/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ type Signer struct {
curve crypto.SignatureAlgorithm
// public key for easier access
publicKey crypto.PublicKey
// Hash algorithm associated to the KMS signing key
hashAlgo crypto.HashAlgorithm
}

// SignerForKey returns a new Google Cloud KMS signer for an asymmetric signing key version.
Expand All @@ -52,7 +54,7 @@ func (c *Client) SignerForKey(
ctx context.Context,
key Key,
) (*Signer, error) {
pk, _, err := c.GetPublicKey(ctx, key)
pk, hashAlgo, err := c.GetPublicKey(ctx, key)
if err != nil {
return nil, err
}
Expand All @@ -63,6 +65,7 @@ func (c *Client) SignerForKey(
key: key,
curve: pk.Algorithm(),
publicKey: pk,
hashAlgo: hashAlgo,
}, nil
}

Expand All @@ -71,10 +74,32 @@ func (c *Client) SignerForKey(
// Reference: https://cloud.google.com/kms/docs/create-validate-signatures
func (s *Signer) Sign(message []byte) ([]byte, error) {

request := &kmspb.AsymmetricSignRequest{
Name: s.key.ResourceID(),
Data: message,
DataCrc32C: checksum(message),
// Google KMS supports signing messages without pre-hashing
// up to 65536 bytes. Beyond that limit, messages must be
// prehashed outside KMS.
kmsPreHashLimit := 65536

var request *kmspb.AsymmetricSignRequest
if len(message) <= kmsPreHashLimit {
// hash within KMS
request = &kmspb.AsymmetricSignRequest{
Name: s.key.ResourceID(),
Data: message,
DataCrc32C: checksum(message),
}
} else {
// this is guaranteed to only return supported hash algos by KMS
hasher, err := crypto.NewHasher(s.hashAlgo)
if err != nil {
return nil, fmt.Errorf("cloudkms: failed to sign: %w", err)
}
// pre-hash outside KMS
hash := hasher.ComputeHash(message)
request = &kmspb.AsymmetricSignRequest{
Name: s.key.ResourceID(),
Digest: getDigest(s.hashAlgo, hash),
DigestCrc32C: checksum(hash),
}
}
result, err := s.client.AsymmetricSign(s.ctx, request)
if err != nil {
Expand Down Expand Up @@ -131,3 +156,18 @@ func curveOrder(curve crypto.SignatureAlgorithm) int {
func (s *Signer) PublicKey() crypto.PublicKey {
return s.publicKey
}

// returns the Digest structure for the hashing algoroithm and hash value, required by the
// signing prehash request
// This function only covers algorithms supported by KMS. It should be extended
// whenever a new hashing algorithm needs to be supported (for instance SHA3-256)
func getDigest(algo crypto.HashAlgorithm, hash []byte) *kmspb.Digest {
if algo == crypto.SHA2_256 {
return &kmspb.Digest{
Digest: &kmspb.Digest_Sha256{
Sha256: hash,
},
}
}
return nil
}