From d196a0ce0449a20d6e2d0db261fae29b3814cab2 Mon Sep 17 00:00:00 2001
From: priyawadhwa <priyawadhwa@google.com>
Date: Fri, 30 Apr 2021 18:07:03 -0700
Subject: [PATCH] Verify signedEntryTimestamp on (#295)

Signed-off-by: Priya Wadhwa <priyawadhwa@google.com>
---
 cmd/rekor-cli/app/upload.go | 47 ++++++++++++++++++++++++++++++++++
 pkg/util/pubkey.go          | 51 +++++++++++++++++++++++++++++++++++++
 tests/e2e_test.go           | 32 ++++-------------------
 3 files changed, 103 insertions(+), 27 deletions(-)
 create mode 100644 pkg/util/pubkey.go

diff --git a/cmd/rekor-cli/app/upload.go b/cmd/rekor-cli/app/upload.go
index c9cfc6a..7e11377 100644
--- a/cmd/rekor-cli/app/upload.go
+++ b/cmd/rekor-cli/app/upload.go
@@ -16,18 +16,24 @@
 package app
 
 import (
+	"context"
+	"crypto/ecdsa"
+	"crypto/sha256"
 	"errors"
 	"fmt"
 	"os"
 
+	"github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer"
 	"github.com/go-openapi/swag"
 	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
 
 	"github.com/sigstore/rekor/cmd/rekor-cli/app/format"
+	"github.com/sigstore/rekor/pkg/generated/client"
 	"github.com/sigstore/rekor/pkg/generated/client/entries"
 	"github.com/sigstore/rekor/pkg/generated/models"
 	"github.com/sigstore/rekor/pkg/log"
+	"github.com/sigstore/rekor/pkg/util"
 )
 
 type uploadCmdOutput struct {
@@ -60,6 +66,7 @@ var uploadCmd = &cobra.Command{
 	},
 	Long: `This command takes the public key, signature and URL of the release artifact and uploads it to the rekor server.`,
 	Run: format.WrapCmd(func(args []string) (interface{}, error) {
+		ctx := context.Background()
 		rekorClient, err := GetRekorClient(viper.GetString("rekor_server"))
 		if err != nil {
 			return nil, err
@@ -103,8 +110,15 @@ var uploadCmd = &cobra.Command{
 		}
 
 		var newIndex int64
+		var logEntry models.LogEntryAnon
 		for _, entry := range resp.Payload {
 			newIndex = swag.Int64Value(entry.LogIndex)
+			logEntry = entry
+		}
+
+		// verify log entry
+		if verified, err := verifyLogEntry(ctx, rekorClient, logEntry); err != nil || !verified {
+			fmt.Fprintf(os.Stderr, "unable to verify entry was added to log: %v", err)
 		}
 
 		return &uploadCmdOutput{
@@ -114,6 +128,39 @@ var uploadCmd = &cobra.Command{
 	}),
 }
 
+func verifyLogEntry(ctx context.Context, rekorClient *client.Rekor, logEntry models.LogEntryAnon) (bool, error) {
+	if logEntry.Verification == nil {
+		return false, nil
+	}
+	// verify the entry
+	le := &models.LogEntryAnon{
+		IntegratedTime: logEntry.IntegratedTime,
+		LogIndex:       logEntry.LogIndex,
+		Body:           logEntry.Body,
+	}
+	payload, err := le.MarshalBinary()
+	if err != nil {
+		return false, err
+	}
+	canonicalized, err := jsoncanonicalizer.Transform(payload)
+	if err != nil {
+		return false, err
+	}
+
+	// get rekor's public key
+	rekorPubKey, err := util.PublicKey(ctx, rekorClient)
+	if err != nil {
+		return false, err
+	}
+
+	// verify the SET against the public key
+	hash := sha256.Sum256(canonicalized)
+	if !ecdsa.VerifyASN1(rekorPubKey, hash[:], []byte(logEntry.Verification.SignedEntryTimestamp)) {
+		return false, fmt.Errorf("unable to verify")
+	}
+	return true, nil
+}
+
 func init() {
 	if err := addArtifactPFlags(uploadCmd); err != nil {
 		log.Logger.Fatal("Error parsing cmd line args:", err)
diff --git a/pkg/util/pubkey.go b/pkg/util/pubkey.go
new file mode 100644
index 0000000..8327d7a
--- /dev/null
+++ b/pkg/util/pubkey.go
@@ -0,0 +1,51 @@
+//
+// Copyright 2021 The Sigstore Authors.
+//
+// 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 util
+
+import (
+	"context"
+	"crypto/ecdsa"
+	"crypto/x509"
+	"encoding/pem"
+	"errors"
+
+	"github.com/sigstore/rekor/pkg/generated/client"
+	"github.com/sigstore/rekor/pkg/generated/client/pubkey"
+)
+
+func PublicKey(ctx context.Context, c *client.Rekor) (*ecdsa.PublicKey, error) {
+	resp, err := c.Pubkey.GetPublicKey(&pubkey.GetPublicKeyParams{Context: ctx})
+	if err != nil {
+		return nil, err
+	}
+	pubKey := resp.GetPayload()
+
+	// marshal the pubkey
+	p, _ := pem.Decode([]byte(pubKey))
+	if p == nil {
+		return nil, errors.New("public key shouldn't be nil")
+	}
+
+	decoded, err := x509.ParsePKIXPublicKey(p.Bytes)
+	if err != nil {
+		return nil, err
+	}
+	ed, ok := decoded.(*ecdsa.PublicKey)
+	if !ok {
+		return nil, err
+	}
+	return ed, nil
+}
diff --git a/tests/e2e_test.go b/tests/e2e_test.go
index a554bea..12254da 100644
--- a/tests/e2e_test.go
+++ b/tests/e2e_test.go
@@ -40,12 +40,11 @@ import (
 	"github.com/go-openapi/strfmt"
 	"github.com/go-openapi/swag"
 	"github.com/sigstore/rekor/cmd/rekor-cli/app"
-	"github.com/sigstore/rekor/pkg/generated/client"
 	"github.com/sigstore/rekor/pkg/generated/client/entries"
-	"github.com/sigstore/rekor/pkg/generated/client/pubkey"
 	"github.com/sigstore/rekor/pkg/generated/models"
 	"github.com/sigstore/rekor/pkg/signer"
 	rekord "github.com/sigstore/rekor/pkg/types/rekord/v0.0.1"
+	"github.com/sigstore/rekor/pkg/util"
 )
 
 func getUUIDFromUploadOutput(t *testing.T, out string) string {
@@ -460,7 +459,10 @@ func TestSignedEntryTimestamp(t *testing.T) {
 		t.Fatal(err)
 	}
 	// get rekor's public key
-	rekorPubKey := rekorPublicKey(t, ctx, rekorClient)
+	rekorPubKey, err := util.PublicKey(ctx, rekorClient)
+	if err != nil {
+		t.Fatal(err)
+	}
 
 	// verify the signature against the public key
 	h := crypto.SHA256.New()
@@ -473,27 +475,3 @@ func TestSignedEntryTimestamp(t *testing.T) {
 		t.Fatal("unable to verify")
 	}
 }
-
-func rekorPublicKey(t *testing.T, ctx context.Context, c *client.Rekor) *ecdsa.PublicKey {
-	resp, err := c.Pubkey.GetPublicKey(&pubkey.GetPublicKeyParams{Context: ctx})
-	if err != nil {
-		t.Fatal(err)
-	}
-	pubKey := resp.GetPayload()
-
-	// marshal the pubkey
-	p, _ := pem.Decode([]byte(pubKey))
-	if p == nil {
-		t.Fatal("shouldn't be nil")
-	}
-
-	decoded, err := x509.ParsePKIXPublicKey(p.Bytes)
-	if err != nil {
-		t.Fatal(err)
-	}
-	ed, ok := decoded.(*ecdsa.PublicKey)
-	if !ok {
-		t.Fatal("not ecdsa public key")
-	}
-	return ed
-}
-- 
GitLab