Skip to content

Commit

Permalink
Add support for using GCP KMS in example-gcp (#153)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlCutter authored Aug 20, 2024
1 parent f1d200d commit e7a8d34
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 11 deletions.
167 changes: 167 additions & 0 deletions cmd/example-gcp/kms.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Copyright 2024 The Tessera authors. All Rights Reserved.
//
// 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 main

import (
"context"
"crypto/ed25519"
"crypto/sha256"
"crypto/x509"
"encoding/binary"
"encoding/pem"
"errors"

kms "cloud.google.com/go/kms/apiv1"
"golang.org/x/mod/sumdb/note"

"cloud.google.com/go/kms/apiv1/kmspb"
)

const (
// KeyVersionNameFormat is the GCP resource identifier for a key version.
// google.cloud.kms.v1.CryptoKeyVersion.name
// https://cloud.google.com/php/docs/reference/cloud-kms/latest/V1.CryptoKeyVersion
KeyVersionNameFormat = "projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s/cryptoKeyVersions/%d"
// From
// https://cs.opensource.google/go/x/mod/+/refs/tags/v0.12.0:sumdb/note/note.go;l=232;drc=baa5c2d058db25484c20d76985ba394e73176132
algEd25519 = 1
)

func publicKeyFromPEM(pemKey []byte) ([]byte, error) {
block, _ := pem.Decode(pemKey)
if block == nil {
return nil, errors.New("failed to decode pemKey")
}

k, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}

publicKey, ok := k.(ed25519.PublicKey)
if !ok {
return nil, errors.New("failed to assert ed25519.PublicKey type")
}

return publicKey, nil
}

// keyHash calculates the ed25519 key hash from the key name and public key.
func keyHash(keyName string, publicKey []byte) (uint32, error) {
h := sha256.New()
h.Write([]byte(keyName))
h.Write([]byte("\n"))
prefixedPublicKey := append([]byte{algEd25519}, publicKey...)
h.Write(prefixedPublicKey)
sum := h.Sum(nil)

return binary.BigEndian.Uint32(sum), nil
}

// Signer is an implementation of a
// [note signer](https://pkg.go.dev/golang.org/x/mod/sumdb/note#Signer) which
// interfaces with GCP KMS.
type Signer struct {
// ctx must be stored because Signer is used as an implementation of the
// note.Signer interface, which does not allow for a context in the Sign
// method. However, the KMS AsymmetricSign API requires a context.
ctx context.Context
client *kms.KeyManagementClient
keyHash uint32
keyName string
kmsKeyName string
}

// NewKMSSigner creates a signer which uses an Ed25519 key in GCP KMS.
// See https://cloud.google.com/kms/docs/algorithms#elliptic_curve_signing_algorithms
//
// kmsKeyName is the GCP KMS name of the key to be used.
// noteKeyName is the value used as the signer name in the note signature.
func NewKMSSigner(ctx context.Context, c *kms.KeyManagementClient, kmsKeyName, noteKeyName string) (*Signer, error) {
s := &Signer{}

s.client = c
s.ctx = ctx
s.keyName = noteKeyName
s.kmsKeyName = kmsKeyName

// Set keyHash.
req := &kmspb.GetPublicKeyRequest{
Name: kmsKeyName,
}
resp, err := c.GetPublicKey(ctx, req)
if err != nil {
return nil, err
}

publicKey, err := publicKeyFromPEM([]byte(resp.Pem))
if err != nil {
return nil, err
}

kh, err := keyHash(s.keyName, publicKey)
if err != nil {
return nil, err
}
s.keyHash = kh

return s, nil
}

// Name identifies the key that this Signer uses.
func (s *Signer) Name() string {
return s.keyName
}

// KeyHash returns the computed key hash of the signer's public key and name.
// It is used as a hint in identifying the correct key to verify with.
func (s *Signer) KeyHash() uint32 {
return s.keyHash
}

// Sign returns a signature for the given message.
func (s *Signer) Sign(msg []byte) ([]byte, error) {
req := &kmspb.AsymmetricSignRequest{
Name: s.kmsKeyName,
Data: msg,
}
resp, err := s.client.AsymmetricSign(s.ctx, req)
if err != nil {
return nil, err
}

return resp.GetSignature(), nil
}

// VerifierKeyString returns a string which can be used to create a note
// verifier based on a GCP KMS
// [Ed25519](https://pkg.go.dev/golang.org/x/mod/sumdb/note#hdr-Generating_Keys)
// key.
func VerifierKeyString(ctx context.Context, c *kms.KeyManagementClient, kmsKeyName, noteKeyName string) (string, error) {
req := &kmspb.GetPublicKeyRequest{
Name: kmsKeyName,
}
resp, err := c.GetPublicKey(ctx, req)
if err != nil {
return "", err
}

publicKey, err := publicKeyFromPEM([]byte(resp.Pem))
if err != nil {
return "", err
}

return note.NewEd25519VerifierKey(noteKeyName, publicKey)
}
45 changes: 34 additions & 11 deletions cmd/example-gcp/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,32 +28,45 @@ import (
"strings"
"time"

kms "cloud.google.com/go/kms/apiv1"
tessera "github.com/transparency-dev/trillian-tessera"
"github.com/transparency-dev/trillian-tessera/storage/gcp"
"golang.org/x/mod/sumdb/note"
"k8s.io/klog/v2"
)

var (
bucket = flag.String("bucket", "", "Bucket to use for storing log")
listen = flag.String("listen", ":2024", "Address:port to listen on")
project = flag.String("project", os.Getenv("GOOGLE_CLOUD_PROJECT"), "GCP Project, take from env if unset")
spanner = flag.String("spanner", "", "Spanner resource URI ('projects/.../...')")
signer = flag.String("signer", "", "Path to file containing log private key")
bucket = flag.String("bucket", "", "Bucket to use for storing log")
listen = flag.String("listen", ":2024", "Address:port to listen on")
project = flag.String("project", os.Getenv("GOOGLE_CLOUD_PROJECT"), "GCP Project, take from env if unset")
spanner = flag.String("spanner", "", "Spanner resource URI ('projects/.../...')")
kmsKeyName = flag.String("kms_key", "", "GCP KMS key name for signing checkpoints")
origin = flag.String("origin", "", "Log origin string")
)

func main() {
klog.InitFlags(nil)
flag.Parse()
ctx := context.Background()

if *origin == "" {
klog.Exit("Must supply --origin")
}

gcpCfg := gcp.Config{
ProjectID: *project,
Bucket: *bucket,
Spanner: *spanner,
}
signer, verifier, kmsClose := signerFromFlags(ctx)
defer func() {
if err := kmsClose(); err != nil {
klog.Errorf("kmsClose(): %v", err)
}
}()

storage, err := gcp.New(ctx, gcpCfg,
tessera.WithCheckpointSignerVerifier(signerFromFlags(), nil),
tessera.WithCheckpointSignerVerifier(signer, verifier),
tessera.WithBatching(1024, time.Second),
tessera.WithPushback(10*4096),
)
Expand Down Expand Up @@ -104,14 +117,24 @@ func main() {
}
}

func signerFromFlags() note.Signer {
raw, err := os.ReadFile(*signer)
// signerFromFlags creates and returns a new KMSSigner from the flags, along with a close func.
func signerFromFlags(ctx context.Context) (note.Signer, note.Verifier, func() error) {
kmClient, err := kms.NewKeyManagementClient(ctx)
if err != nil {
klog.Exitf("Failed to read secret key file %q: %v", *signer, err)
klog.Fatalf("Failed to create KeyManagementClient: %v", err)
}
signer, err := note.NewSigner(string(raw))
signer, err := NewKMSSigner(ctx, kmClient, *kmsKeyName, *origin)
if err != nil {
klog.Exitf("Failed to create new signer: %v", err)
}
return signer
vRaw, err := VerifierKeyString(ctx, kmClient, *kmsKeyName, *origin)
if err != nil {
klog.Exitf("Failed to create verifier string: %v", err)
}
verifier, err := note.NewVerifier(vRaw)
if err != nil {
klog.Exitf("Failed to create verifier from %q: %v", vRaw, err)
}

return signer, verifier, kmClient.Close
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/transparency-dev/trillian-tessera
go 1.22.5

require (
cloud.google.com/go/kms v1.18.4
cloud.google.com/go/spanner v1.67.0
cloud.google.com/go/storage v1.43.0
github.com/RobinUS2/golang-moving-average v1.0.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,8 @@ cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4
cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w=
cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24=
cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI=
cloud.google.com/go/kms v1.18.4 h1:dYN3OCsQ6wJLLtOnI8DGUwQ5shMusXsWCCC+s09ATsk=
cloud.google.com/go/kms v1.18.4/go.mod h1:SG1bgQ3UWW6/KdPo9uuJnzELXY5YTTMJtDYvajiQ22g=
cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic=
cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI=
cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE=
Expand Down

0 comments on commit e7a8d34

Please sign in to comment.