brokeyourbike/clearbank-api-client-go

View on GitHub
signature/google/signer.go

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
package google

import (
    "context"
    "crypto/sha256"
    "encoding/base64"
    "errors"
    "fmt"
    "hash/crc32"

    "cloud.google.com/go/kms/apiv1/kmspb"
    "github.com/googleapis/gax-go/v2"
    "google.golang.org/protobuf/types/known/wrapperspb"
)

var (
    ErrRequestCorrupted  = errors.New("AsymmetricSign: request corrupted in-transit")
    ErrResponseCorrupted = errors.New("AsymmetricSign: response corrupted in-transit")
)

// KeyManagementClient is the interface for the Google Cloud KMS client.
type KeyManagementClient interface {
    AsymmetricSign(context.Context, *kmspb.AsymmetricSignRequest, ...gax.CallOption) (*kmspb.AsymmetricSignResponse, error)
}

type signer struct {
    client  KeyManagementClient
    keyName string
}

// NewSigner creates a new Google Cloud KMS signer.
func NewSigner(client KeyManagementClient, keyName string) *signer {
    return &signer{client: client, keyName: keyName}
}

// Sign signs the given message using the Google Cloud KMS service.
// It returns the signature and an error if the signing fails.
func (g *signer) Sign(ctx context.Context, message []byte) ([]byte, error) {
    // calculate the digest of the message
    digest := sha256.Sum256(message)

    // compute digest's CRC32C
    digestCRC32C := ComputeCRC32(digest[:])

    // build the signing request
    req := &kmspb.AsymmetricSignRequest{
        Name: g.keyName,
        Digest: &kmspb.Digest{
            Digest: &kmspb.Digest_Sha256{
                Sha256: digest[:],
            },
        },
        DigestCrc32C: wrapperspb.Int64(digestCRC32C),
    }

    result, err := g.client.AsymmetricSign(ctx, req)
    if err != nil {
        return nil, fmt.Errorf("failed to sign digest: %w", err)
    }

    // perform integrity verification on result
    if !result.VerifiedDigestCrc32C {
        return nil, ErrRequestCorrupted
    }

    // perform integrity verification on signature
    if ComputeCRC32(result.Signature) != result.SignatureCrc32C.Value {
        return nil, ErrResponseCorrupted
    }

    encoded := base64.StdEncoding.EncodeToString(result.Signature)

    return []byte(encoded), nil
}

// ComputeCRC32 computes the CRC32C of the given byte slice.
func ComputeCRC32(data []byte) int64 {
    t := crc32.MakeTable(crc32.Castagnoli)

    return int64(crc32.Checksum(data, t))
}