Skip to content

Commit

Permalink
Use KMS keys to sign and verify.
Browse files Browse the repository at this point in the history
  • Loading branch information
jiggoha committed Nov 7, 2023
1 parent ca6f468 commit 586e70d
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 102 deletions.
168 changes: 114 additions & 54 deletions experimental/gcp-log/function.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,53 +23,89 @@ import (
"fmt"
"net/http"
"os"

"github.com/transparency-dev/merkle/rfc6962"
"golang.org/x/mod/sumdb/note"
"google.golang.org/api/iterator"
"path/filepath"

"github.com/gcp_serverless_module/internal/storage"
"github.com/transparency-dev/serverless-log/pkg/log"

"cloud.google.com/go/kms/apiv1"
"github.com/transparency-dev/armored-witness/pkg/kmssigner"
fmtlog "github.com/transparency-dev/formats/log"
"github.com/transparency-dev/merkle/rfc6962"
"github.com/transparency-dev/serverless-log/pkg/log"
"golang.org/x/mod/sumdb/note"
"google.golang.org/api/iterator"
)

func validateCommonArgs(w http.ResponseWriter, origin string) (ok bool, pubKey string) {
if len(origin) == 0 {
type requestData struct {
// Common args.
Origin string `json:"origin"`
Bucket string `json:"bucket"`
NoteKeyName string `json:"noteKeyName"`
KMSKeyRing string `json:"kmsKeyRing"`
KMSKeyName string `json:"kmsKeyName"`
KMSKeyLocation string `json:"kmsKeyLocation"`
KMSKeyVersion uint `json:"kmsKeyVersion"`

// For Sequence requests.
EntriesDir string `json:"entriesDir"`

// For Integrate requests.
Initialise bool `json:"initialise"`
}

func validateCommonArgs(w http.ResponseWriter, d requestData) (ok bool) {
if len(d.Origin) == 0 {
http.Error(w, "Please set `origin` in HTTP body to log identifier.", http.StatusBadRequest)
return false, ""
return false
}

pubKey = os.Getenv("SERVERLESS_LOG_PUBLIC_KEY")
if len(pubKey) == 0 {
http.Error(w,
"Please set SERVERLESS_LOG_PUBLIC_KEY environment variable",
if len(d.KMSKeyRing) == 0 {
http.Error(w, "Please set `kmsKeyRing` in HTTP body to the signing key's key ring.",
http.StatusBadRequest)
return false
}
if len(d.KMSKeyName) == 0 {
http.Error(w, "Please set `kmsKeyName` in HTTP body to the signing key's name.",
http.StatusBadRequest)
return false, ""
return false
}
if len(d.KMSKeyLocation) == 0 {
http.Error(w, "Please set `kmsKeyLocation` in HTTP body to the signing key's location.",
http.StatusBadRequest)
return false
}
if d.KMSKeyVersion == 0 {
http.Error(w, "Please set `kmsKeyVersion` in HTTP body to the signing key's version as an integer.",
http.StatusBadRequest)
return false
}
if len(d.NoteKeyName) == 0 {
http.Error(w, "Please set `noteKeyName` in HTTP body to the key name for the note.",
http.StatusBadRequest)
return false
}

return true, pubKey
return true
}

// Sequence is the entrypoint of the `sequence` GCF function.
func Sequence(w http.ResponseWriter, r *http.Request) {
// TODO(jayhou): validate that EntriesDir is only touching the log path.

var d struct {
Bucket string `json:"bucket"`
EntriesDir string `json:"entriesDir"`
Origin string `json:"origin"`
}
// process request args

d := requestData{}
if err := json.NewDecoder(r.Body).Decode(&d); err != nil {
code := http.StatusBadRequest
fmt.Printf("json.NewDecoder: %v", err)
http.Error(w, http.StatusText(code), code)
http.Error(w, fmt.Sprintf("Failed to decode JSON: %q", err), http.StatusBadRequest)
return
}

ok, pubKey := validateCommonArgs(w, d.Origin)
if !ok {
if ok := validateCommonArgs(w, d); !ok {
return
}
if len(d.EntriesDir) == 0 {
http.Error(w, "Please set `entriesDir` in HTTP body to the key name for the note.",
http.StatusBadRequest)
return
}

Expand All @@ -84,19 +120,36 @@ func Sequence(w http.ResponseWriter, r *http.Request) {

// Read the current log checkpoint to retrieve next sequence number.

cpRaw, err := client.ReadCheckpoint(ctx)
cpBytes, err := client.ReadCheckpoint(ctx)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to read log checkpoint: %q", err), http.StatusInternalServerError)
return
}

// Check signatures
v, err := note.NewVerifier(pubKey)
kmsKeyName := fmt.Sprintf(kmssigner.KeyVersionNameFormat,
os.Getenv("GCP_PROJECT"), d.KMSKeyLocation, d.KMSKeyRing, d.KMSKeyName, d.KMSKeyVersion)

kmClient, err := kms.NewKeyManagementClient(ctx)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to instantiate Verifier: %q", err), http.StatusInternalServerError)
return
http.Error(w, fmt.Sprintf("Failed to create KeyManagementClient: %q", err), http.StatusInternalServerError)
}
cp, _, _, err := fmtlog.ParseCheckpoint(cpRaw, d.Origin, v)
defer kmClient.Close()

vkey, err := kmssigner.VerifierKeyString(ctx, kmClient, kmsKeyName, d.NoteKeyName)
if err != nil {
http.Error(w,
fmt.Sprintf("Failed to create verifier key string: %q", err),
http.StatusInternalServerError)
}
noteVerifier, err := note.NewVerifier(vkey)
if err != nil {
http.Error(w,
fmt.Sprintf("Failed to instantiate verifier: %q", err),
http.StatusInternalServerError)
}

cp, _, _, err := fmtlog.ParseCheckpoint(cpBytes, d.Origin, noteVerifier)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to parse Checkpoint: %q", err), http.StatusInternalServerError)
return
Expand All @@ -119,11 +172,12 @@ func Sequence(w http.ResponseWriter, r *http.Request) {
return
}
// Skip this directory - only add files under it.
if attrs.Name == d.EntriesDir {
if filepath.Clean(attrs.Name) == filepath.Clean(d.EntriesDir) {
continue
}

bytes, err := client.GetObjectData(ctx, attrs.Name)
fmt.Printf("Sequencing object %q with content %q\n", attrs.Name, string(bytes))
if err != nil {
http.Error(w,
fmt.Sprintf("Failed to get data of object %q: %q", attrs.Name, err),
Expand Down Expand Up @@ -156,37 +210,49 @@ func Sequence(w http.ResponseWriter, r *http.Request) {

// Integrate is the entrypoint of the `integrate` GCF function.
func Integrate(w http.ResponseWriter, r *http.Request) {
var d struct {
Origin string `json:"origin"`
Initialise bool `json:"initialise"`
Bucket string `json:"bucket"`
}
// process request args

d := requestData{}
if err := json.NewDecoder(r.Body).Decode(&d); err != nil {
fmt.Printf("json.NewDecoder: %v", err)
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
http.Error(w, fmt.Sprintf("Failed to decode JSON: %q", err), http.StatusBadRequest)
return
}

ok, pubKey := validateCommonArgs(w, d.Origin)
if !ok {
if ok := validateCommonArgs(w, d); !ok {
return
}

privKey := os.Getenv("SERVERLESS_LOG_PRIVATE_KEY")
if len(privKey) == 0 {
http.Error(w,
"Please set SERVERLESS_LOG_PUBLIC_KEY environment variable",
http.StatusBadRequest)
kmsKeyName := fmt.Sprintf(kmssigner.KeyVersionNameFormat,
os.Getenv("GCP_PROJECT"), d.KMSKeyLocation, d.KMSKeyRing, d.KMSKeyName, d.KMSKeyVersion)

ctx := context.Background()
kmClient, err := kms.NewKeyManagementClient(ctx)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to create KeyManagementClient: %q", err), http.StatusInternalServerError)
}
defer kmClient.Close()

s, err := note.NewSigner(privKey)
noteSigner, err := kmssigner.New(ctx, kmClient, kmsKeyName, d.NoteKeyName)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to instantiate signer: %q", err), http.StatusInternalServerError)
return
}

ctx := context.Background()
vkey, err := kmssigner.VerifierKeyString(ctx, kmClient, kmsKeyName, noteSigner.Name())
if err != nil {
http.Error(w,
fmt.Sprintf("Failed to create verifier key string: %q", err),
http.StatusInternalServerError)
}

noteVerifier, err := note.NewVerifier(vkey)
if err != nil {
http.Error(w,
fmt.Sprintf("Failed to instantiate verifier: %q", err),
http.StatusInternalServerError)
}

client, err := storage.NewClient(ctx, os.Getenv("GCP_PROJECT"), d.Bucket)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to create GCS client: %v", err), http.StatusBadRequest)
Expand All @@ -204,7 +270,7 @@ func Integrate(w http.ResponseWriter, r *http.Request) {
cp := fmtlog.Checkpoint{
Hash: h.EmptyRoot(),
}
if err := signAndWrite(ctx, &cp, cpNote, s, client, d.Origin); err != nil {
if err := signAndWrite(ctx, &cp, cpNote, noteSigner, client, d.Origin); err != nil {
http.Error(w, fmt.Sprintf("Failed to sign: %q", err), http.StatusInternalServerError)
}
fmt.Fprintf(w, fmt.Sprintf("Initialised log at %s.", d.Bucket))
Expand All @@ -220,13 +286,7 @@ func Integrate(w http.ResponseWriter, r *http.Request) {
}

// Check signatures
v, err := note.NewVerifier(pubKey)
if err != nil {
http.Error(w,
fmt.Sprintf("Failed to instantiate Verifier: %q", err),
http.StatusInternalServerError)
}
cp, _, _, err := fmtlog.ParseCheckpoint(cpRaw, d.Origin, v)
cp, _, _, err := fmtlog.ParseCheckpoint(cpRaw, d.Origin, noteVerifier)
if err != nil {
http.Error(w,
fmt.Sprintf("Failed to open Checkpoint: %q", err),
Expand All @@ -245,7 +305,7 @@ func Integrate(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Nothing to integrate", http.StatusInternalServerError)
}

err = signAndWrite(ctx, newCp, cpNote, s, client, d.Origin)
err = signAndWrite(ctx, newCp, cpNote, noteSigner, client, d.Origin)
if err != nil {
http.Error(w,
fmt.Sprintf("Failed to sign: %q", err),
Expand Down
33 changes: 17 additions & 16 deletions experimental/gcp-log/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,41 @@ module github.com/gcp_serverless_module
go 1.20

require (
cloud.google.com/go/kms v1.15.5
cloud.google.com/go/storage v1.33.0
github.com/transparency-dev/armored-witness v0.0.0-20231106114509-3d1fed57e76e
github.com/transparency-dev/formats v0.0.0-20230928092353-f8ed364213f7
github.com/transparency-dev/merkle v0.0.2
github.com/transparency-dev/serverless-log v0.0.0-20231001212932-d1a42e72eef9
golang.org/x/mod v0.12.0
google.golang.org/api v0.143.0
k8s.io/klog/v2 v2.100.1
golang.org/x/mod v0.14.0
google.golang.org/api v0.149.0
k8s.io/klog/v2 v2.110.1
)

require (
cloud.google.com/go v0.110.7 // indirect
cloud.google.com/go/compute v1.23.0 // indirect
cloud.google.com/go v0.110.8 // indirect
cloud.google.com/go/compute v1.23.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.1.1 // indirect
github.com/go-logr/logr v1.2.0 // indirect
cloud.google.com/go/iam v1.1.3 // indirect
github.com/go-logr/logr v1.3.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/oauth2 v0.12.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/oauth2 v0.13.0 // indirect
golang.org/x/sync v0.4.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230913181813-007df8e322eb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230920204549-e6e6cdab5c13 // indirect
google.golang.org/grpc v1.57.1 // indirect
google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
)
Loading

0 comments on commit 586e70d

Please sign in to comment.