Skip to content

Commit

Permalink
review of AWS KMS signer and adding scrappy implementation of GCP Signer
Browse files Browse the repository at this point in the history
- needs cleanup and testing

Signed-off-by: chaosinthecrd <[email protected]>
ChaosInTheCRD committed Jan 17, 2024

Verified

This commit was signed with the committer’s verified signature.
caixw caixw
1 parent b8eee30 commit eda08d5
Showing 7 changed files with 961 additions and 65 deletions.
19 changes: 10 additions & 9 deletions signer/kms/aws/client.go
Original file line number Diff line number Diff line change
@@ -51,14 +51,6 @@ const (
ReferenceScheme = "awskms://"
)

type awsClient struct {
client *akms.Client
endpoint string
keyID string
alias string
keyCache *ttlcache.Cache[string, cmk]
}

var (
errKMSReference = errors.New("kms specification should be in the format awskms://[ENDPOINT]/[ID/ALIAS/ARN] (endpoint optional)")

@@ -109,6 +101,14 @@ func ParseReference(resourceID string) (endpoint, keyID, alias string, err error
return
}

type awsClient struct {
client *akms.Client
endpoint string
keyID string
alias string
keyCache *ttlcache.Cache[string, cmk]
}

func newAWSClient(ctx context.Context, ksp *kms.KMSSignerProvider) (*awsClient, error) {
if err := ValidReference(ksp.Reference); err != nil {
return nil, err
@@ -264,7 +264,7 @@ func (a *awsClient) createKey(ctx context.Context, algorithm string) (crypto.Pub
}

usage := types.KeyUsageTypeSignVerify
description := "Created by Sigstore"
description := "Created by Witness"
key, err := a.client.CreateKey(ctx, &akms.CreateKeyInput{
CustomerMasterKeySpec: types.CustomerMasterKeySpec(algorithm),
KeyUsage: usage,
@@ -290,6 +290,7 @@ func (a *awsClient) createKey(ctx context.Context, algorithm string) (crypto.Pub
return cmk.PublicKey, err
}

// At the moment this function lies unused, but it is here for future if necessary
func (a *awsClient) verify(ctx context.Context, sig, message io.Reader) error {
cmk, err := a.getCMK(ctx)
if err != nil {
2 changes: 1 addition & 1 deletion signer/kms/aws/go.mod
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ module github.com/in-toto/go-witness/signer/kms/aws

replace github.com/in-toto/go-witness => ../../../

go 1.21
go 1.19

require (
github.com/aws/aws-sdk-go-v2 v1.24.0
69 changes: 14 additions & 55 deletions signer/kms/aws/signer.go
Original file line number Diff line number Diff line change
@@ -70,25 +70,16 @@ func LoadSignerVerifier(ctx context.Context, ksp *kms.KMSSignerProvider) (*Signe
return a, nil
}

// NOTE: This might ben all wrong but setting it like so for now
// KeyID returnst the key identifier for the key used by this signer.
// NOTE: This might be all wrong but setting it like so for now
//
// KeyID returns the key identifier for the key used by this signer.
func (a *SignerVerifier) KeyID() (string, error) {
return a.client.keyID, nil
}

// SignMessage signs the provided message using AWS KMS. If the message is provided,
// Sign signs the provided message using AWS KMS. If the message is provided,
// this method will compute the digest according to the hash function specified
// when the Signer was created.
//
// SignMessage recognizes the following Options listed in order of preference:
//
// - WithContext()
//
// - WithDigest()
//
// - WithCryptoSignerOpts()
//
// All other options are ignored if specified.
func (a *SignerVerifier) Sign(message io.Reader) ([]byte, error) {
var err error
ctx := context.Background()
@@ -110,11 +101,7 @@ func (a *SignerVerifier) Sign(message io.Reader) ([]byte, error) {
return a.client.sign(ctx, digest, hf)
}

// PublicKey returns the public key that can be used to verify signatures created by
// this signer. If the caller wishes to specify the context to use to obtain
// the public key, pass option.WithContext(desiredCtx).
//
// All other options are ignored if specified.
// Verifier returns a cryptoutil.Verifier that can be used to verify signatures created by this signer.
func (a *SignerVerifier) Verifier() (cryptoutil.Verifier, error) {
return a, nil
}
@@ -130,33 +117,11 @@ func (a *SignerVerifier) Bytes() ([]byte, error) {
return cryptoutil.PublicPemBytes(p)
}

// VerifySignature verifies the signature for the given message. Unless provided
// in an option, the digest of the message will be computed using the hash function specified
// when the SignerVerifier was created.
//
// This function returns nil if the verification succeeded, and an error message otherwise.
//
// This function recognizes the following Options listed in order of preference:
//
// - WithContext()
//
// - WithDigest()
//
// - WithRemoteVerification()
//
// - WithCryptoSignerOpts()
//
// All other options are ignored if specified.
// VerifySignature verifies the signature for the given message, returning
// nil if the verification succeeded, and an error message otherwise.
func (a *SignerVerifier) Verify(message io.Reader, sig []byte) (err error) {
ctx := context.Background()
var digest []byte
// var remoteVerification bool

//for _, opt := range opts {
// opt.ApplyContext(&ctx)
// opt.ApplyDigest(&digest)
// opt.ApplyRemoteVerification(&remoteVerification)
//}

var signerOpts crypto.SignerOpts
signerOpts, err = a.client.getHashFunc(ctx)
@@ -173,13 +138,13 @@ func (a *SignerVerifier) Verify(message io.Reader, sig []byte) (err error) {
err = a.client.verifyRemotely(ctx, sig, digest)
if err != nil {
log.Info(err.Error())
} else {
log.Info("Verification Succeeded")
}

return err
}

// NOTE:Wondering if this should exist, at least for now
//
// CreateKey attempts to create a new key in Vault with the specified algorithm.
func (a *SignerVerifier) CreateKey(ctx context.Context, algorithm string) (crypto.PublicKey, error) {
return a.client.createKey(ctx, algorithm)
@@ -192,7 +157,7 @@ type cryptoSignerWrapper struct {
errFunc func(error)
}

func (c cryptoSignerWrapper) Public() crypto.PublicKey {
func (c *cryptoSignerWrapper) Public() crypto.PublicKey {
ctx := context.Background()

cmk, err := c.sv.client.getCMK(ctx)
@@ -203,16 +168,10 @@ func (c cryptoSignerWrapper) Public() crypto.PublicKey {
return cmk.PublicKey
}

func (c cryptoSignerWrapper) Sign(message io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
//hashFunc := c.hashFunc
//if opts != nil {
// hashFunc = opts.HashFunc()
//}
//awsOptions := []signature.SignOption{
// options.WithContext(c.ctx),
// options.WithDigest(digest),
// options.WithCryptoSignerOpts(hashFunc),
//}
func (c *cryptoSignerWrapper) Sign(message io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
if opts != nil {
c.hashFunc = opts.HashFunc()
}

return c.sv.Sign(message)
}
457 changes: 457 additions & 0 deletions signer/kms/gcp/client.go

Large diffs are not rendered by default.

54 changes: 54 additions & 0 deletions signer/kms/gcp/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
module github.com/in-toto/go-witness/signer/kms/gcp

replace github.com/in-toto/go-witness => ../../../

go 1.19

require (
cloud.google.com/go/kms v1.15.5
github.com/in-toto/go-witness v0.0.0-00010101000000-000000000000
github.com/jellydator/ttlcache/v3 v3.1.1
github.com/sigstore/sigstore v1.6.4
google.golang.org/protobuf v1.32.0
)

require (
cloud.google.com/go/compute v1.23.3 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.1.5 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.3.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-containerregistry v0.17.0 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/letsencrypt/boulder v0.0.0-20230907030200-6d76a0f91e1e // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/theupdateframework/go-tuf v0.5.2 // indirect
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
go.opentelemetry.io/otel v1.21.0 // indirect
go.opentelemetry.io/otel/metric v1.21.0 // indirect
go.opentelemetry.io/otel/trace v1.21.0 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/oauth2 v0.15.0 // indirect
golang.org/x/sync v0.5.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/term v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/api v0.154.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 // indirect
google.golang.org/grpc v1.59.0 // indirect
gopkg.in/go-jose/go-jose.v2 v2.6.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
217 changes: 217 additions & 0 deletions signer/kms/gcp/go.sum

Large diffs are not rendered by default.

208 changes: 208 additions & 0 deletions signer/kms/gcp/signer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// Copyright 2023 The Witness Contributors
//
// 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 gcp

import (
"context"
"crypto"
"fmt"
"hash/crc32"
"io"

"github.com/in-toto/go-witness/cryptoutil"
"github.com/in-toto/go-witness/log"
kms "github.com/in-toto/go-witness/signer/kms"
)

var gcpSupportedHashFuncs = []crypto.Hash{
crypto.SHA256,
crypto.SHA512,
crypto.SHA384,
}

// SignerVerifier is a cryptoutil.SignerVerifier that uses the AWS Key Management Service
type SignerVerifier struct {
client *gcpClient
hashFunc crypto.Hash
}

// LoadSignerVerifier generates signatures using the specified key object in AWS KMS and hash algorithm.
func LoadSignerVerifier(ctx context.Context, ksp *kms.KMSSignerProvider) (*SignerVerifier, error) {
g := &SignerVerifier{}

var err error
g.client, err = newGCPClient(ctx, ksp)
if err != nil {
return nil, err
}

for _, hashFunc := range gcpSupportedHashFuncs {
if hashFunc == ksp.HashFunc {
g.hashFunc = ksp.HashFunc
}
}

if g.hashFunc == 0 {
return nil, fmt.Errorf("unsupported hash function: %v", ksp.HashFunc)
}

return g, nil
}

// NOTE: This might be all wrong but setting it like so for now
//
// KeyID returns the key identifier for the key used by this signer.
func (g *SignerVerifier) KeyID() (string, error) {
return g.client.keyID, nil
}

// Sign signs the provided message using GCP KMS. If the message is provided,
// this method will compute the digest according to the hash function specified
// when the Signer was created.
func (g *SignerVerifier) Sign(message io.Reader) ([]byte, error) {
var err error
ctx := context.Background()
var digest []byte

var signerOpts crypto.SignerOpts
signerOpts, err = g.client.getHashFunc()
if err != nil {
return nil, fmt.Errorf("getting fetching default hash function: %w", err)
}

hf := signerOpts.HashFunc()

digest, _, err = cryptoutil.ComputeDigestForSigning(message, hf, gcpSupportedHashFuncs)
if err != nil {
return nil, err
}

crc32cHasher := crc32.New(crc32.MakeTable(crc32.Castagnoli))
_, err = crc32cHasher.Write(digest)
if err != nil {
return nil, err
}

return g.client.sign(ctx, digest, hf, crc32cHasher.Sum32())
}

// Verifier returns a cryptoutil.Verifier that can be used to verify signatures created by this signer.
func (g *SignerVerifier) Verifier() (cryptoutil.Verifier, error) {
return g, nil
}

// PublicKey returns the public key that can be used to verify signatures created by
// this signer.
func (g *SignerVerifier) PublicKey(ctx context.Context) (crypto.PublicKey, error) {
return g.client.public(ctx)
}

// Bytes returns the bytes of the public key that can be used to verify signatures created by the signer.
func (g *SignerVerifier) Bytes() ([]byte, error) {
ckv, err := g.client.getCKV()
if err != nil {
return nil, fmt.Errorf("failed to get KMS key version: %w", err)
}

return cryptoutil.PublicPemBytes(ckv.PublicKey)
}

// VerifySignature verifies the signature for the given message, returning
// nil if the verification succeeded, and an error message otherwise.
func (g *SignerVerifier) Verify(message io.Reader, sig []byte) (err error) {
var digest []byte

var signerOpts crypto.SignerOpts
signerOpts, err = g.client.getHashFunc()
if err != nil {
return fmt.Errorf("getting hash func: %w", err)
}
hf := signerOpts.HashFunc()

digest, _, err = cryptoutil.ComputeDigestForVerifying(message, hf, gcpSupportedHashFuncs)
if err != nil {
return err
}

err = g.client.verify(digest, sig)
if err != nil {
log.Info(err.Error())
}

return err
}

// NOTE:Wondering if this should exist, at least for now
//
// CreateKey attempts to create a new key in Vault with the specified algorithm.
func (a *SignerVerifier) CreateKey(ctx context.Context, algorithm string) (crypto.PublicKey, error) {
return a.client.createKey(ctx, algorithm)
}

type cryptoSignerWrapper struct {
ctx context.Context
hashFunc crypto.Hash
sv *SignerVerifier
errFunc func(error)
}

func (c *cryptoSignerWrapper) Public() crypto.PublicKey {
ctx := context.Background()

pk, err := c.sv.PublicKey(ctx)
if err != nil && c.errFunc != nil {
c.errFunc(err)
}
return pk
}

func (c *cryptoSignerWrapper) Sign(message io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
if opts != nil {
c.hashFunc = opts.HashFunc()
}

return c.sv.Sign(message)
}

// CryptoSigner returns a crypto.Signer object that uses the underlying SignerVerifier, along with a crypto.SignerOpts object
// that allows the KMS to be used in APIs that only accept the standard golang objects
func (g *SignerVerifier) CryptoSigner(ctx context.Context, errFunc func(error)) (crypto.Signer, crypto.SignerOpts, error) {
defaultHf, err := g.client.getHashFunc()
if err != nil {
return nil, nil, fmt.Errorf("getting fetching default hash function: %w", err)
}

csw := &cryptoSignerWrapper{
ctx: ctx,
sv: g,
hashFunc: defaultHf,
errFunc: errFunc,
}

return csw, defaultHf, nil
}

// SupportedAlgorithms returns the list of algorithms supported by the AWS KMS service
func (*SignerVerifier) SupportedAlgorithms() (result []string) {
for k := range algorithmMap {
result = append(result, k)
}
return
}

// DefaultAlgorithm returns the default algorithm for the GCP KMS service
func (g *SignerVerifier) DefaultAlgorithm() string {
return AlgorithmECDSAP256SHA256
}

0 comments on commit eda08d5

Please sign in to comment.