From 2b4d1ba5031084e934c9d4d5ef4f7f8adb6eecec Mon Sep 17 00:00:00 2001
From: Bob Callaway <bobcallaway@users.noreply.github.com>
Date: Thu, 29 Apr 2021 17:05:13 -0400
Subject: [PATCH] Add sha256 prefix to index keys for artifact hashes (#290)

* Add sha256 prefix to index keys for artifact hashes

This change adds the `sha256:` prefix to index values that are created to simplify searching the transparency log for artifacts. In case we shift to using a different hashing algorithm in the future, this will provide a way to specify it.

Fixes #289

Signed-off-by: Bob Callaway <bob.callaway@gmail.com>
---
 cmd/rekor-cli/app/pflags.go            | 33 +++++++++++++++++++++++++-
 cmd/rekor-cli/app/search.go            |  2 +-
 openapi.yaml                           |  2 +-
 pkg/api/entries.go                     | 12 ++++++----
 pkg/generated/models/search_index.go   |  4 ++--
 pkg/generated/restapi/embedded_spec.go |  4 ++--
 pkg/types/jar/v0.0.1/entry.go          |  3 ++-
 pkg/types/rekord/v0.0.1/entry.go       |  3 ++-
 pkg/types/rpm/v0.0.1/entry.go          |  3 ++-
 tests/e2e-test.sh                      |  6 +++++
 tests/e2e_test.go                      | 13 ++++++++++
 11 files changed, 70 insertions(+), 15 deletions(-)

diff --git a/cmd/rekor-cli/app/pflags.go b/cmd/rekor-cli/app/pflags.go
index 2b4fcec..13864dd 100644
--- a/cmd/rekor-cli/app/pflags.go
+++ b/cmd/rekor-cli/app/pflags.go
@@ -27,6 +27,7 @@ import (
 	"os"
 	"path/filepath"
 	"strconv"
+	"strings"
 
 	"github.com/go-openapi/strfmt"
 	"github.com/go-openapi/swag"
@@ -47,7 +48,7 @@ func addSearchPFlags(cmd *cobra.Command) error {
 
 	cmd.Flags().Var(&fileOrURLFlag{}, "artifact", "path or URL to artifact file")
 
-	cmd.Flags().Var(&uuidFlag{}, "sha", "the SHA256 sum of the artifact")
+	cmd.Flags().Var(&shaFlag{}, "sha", "the SHA256 sum of the artifact")
 
 	cmd.Flags().Var(&emailFlag{}, "email", "email associated with the public key's subject")
 	return nil
@@ -468,6 +469,36 @@ func (f *pkiFormatFlag) Set(s string) error {
 	return fmt.Errorf("value specified is invalid: [%s] supported values are: [pgp, minisign, x509, ssh]", s)
 }
 
+type shaFlag struct {
+	hash string
+}
+
+func (s *shaFlag) String() string {
+	return s.hash
+}
+
+func (s *shaFlag) Set(v string) error {
+	if v == "" {
+		return errors.New("flag must be specified")
+	}
+	strToCheck := v
+	if strings.HasPrefix(v, "sha256:") {
+		strToCheck = strings.Replace(v, "sha256:", "", 1)
+	}
+	if _, err := hex.DecodeString(strToCheck); (err != nil) || (len(strToCheck) != 64) {
+		if err == nil {
+			err = errors.New("invalid length for value")
+		}
+		return fmt.Errorf("value specified is invalid: %w", err)
+	}
+	s.hash = v
+	return nil
+}
+
+func (s *shaFlag) Type() string {
+	return "sha"
+}
+
 type uuidFlag struct {
 	hash string
 }
diff --git a/cmd/rekor-cli/app/search.go b/cmd/rekor-cli/app/search.go
index 41837d5..d96d454 100644
--- a/cmd/rekor-cli/app/search.go
+++ b/cmd/rekor-cli/app/search.go
@@ -116,7 +116,7 @@ var searchCmd = &cobra.Command{
 			}
 
 			hashVal := strings.ToLower(hex.EncodeToString(hasher.Sum(nil)))
-			params.Query.Hash = hashVal
+			params.Query.Hash = "sha256:" + hashVal
 		}
 
 		publicKeyStr := viper.GetString("public-key")
diff --git a/openapi.yaml b/openapi.yaml
index 71be9ad..b531661 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -338,7 +338,7 @@ definitions:
           - "format"
       hash:
         type: string
-        pattern: '^[0-9a-fA-F]{64}$'
+        pattern: '^(sha256:)?[0-9a-fA-F]{64}$'
 
   SearchLogQuery:
     type: object
diff --git a/pkg/api/entries.go b/pkg/api/entries.go
index 203b6fd..257b20b 100644
--- a/pkg/api/entries.go
+++ b/pkg/api/entries.go
@@ -296,12 +296,14 @@ func SearchLogQueryHandler(params entries.SearchLogQueryParams) middleware.Respo
 		}
 
 		for _, leafResp := range searchByHashResults {
-			logEntry, err := logEntryFromLeaf(tc, leafResp.Leaf, leafResp.SignedLogRoot, leafResp.Proof)
-			if err != nil {
-				return handleRekorAPIError(params, code, err, err.Error())
-			}
+			if leafResp != nil {
+				logEntry, err := logEntryFromLeaf(tc, leafResp.Leaf, leafResp.SignedLogRoot, leafResp.Proof)
+				if err != nil {
+					return handleRekorAPIError(params, code, err, err.Error())
+				}
 
-			resultPayload = append(resultPayload, logEntry)
+				resultPayload = append(resultPayload, logEntry)
+			}
 		}
 	}
 
diff --git a/pkg/generated/models/search_index.go b/pkg/generated/models/search_index.go
index 9163f78..37645b7 100644
--- a/pkg/generated/models/search_index.go
+++ b/pkg/generated/models/search_index.go
@@ -41,7 +41,7 @@ type SearchIndex struct {
 	Email strfmt.Email `json:"email,omitempty"`
 
 	// hash
-	// Pattern: ^[0-9a-fA-F]{64}$
+	// Pattern: ^(sha256:)?[0-9a-fA-F]{64}$
 	Hash string `json:"hash,omitempty"`
 
 	// public key
@@ -87,7 +87,7 @@ func (m *SearchIndex) validateHash(formats strfmt.Registry) error {
 		return nil
 	}
 
-	if err := validate.Pattern("hash", "body", m.Hash, `^[0-9a-fA-F]{64}$`); err != nil {
+	if err := validate.Pattern("hash", "body", m.Hash, `^(sha256:)?[0-9a-fA-F]{64}$`); err != nil {
 		return err
 	}
 
diff --git a/pkg/generated/restapi/embedded_spec.go b/pkg/generated/restapi/embedded_spec.go
index 44da32e..1cd7cb5 100644
--- a/pkg/generated/restapi/embedded_spec.go
+++ b/pkg/generated/restapi/embedded_spec.go
@@ -497,7 +497,7 @@ func init() {
         },
         "hash": {
           "type": "string",
-          "pattern": "^[0-9a-fA-F]{64}$"
+          "pattern": "^(sha256:)?[0-9a-fA-F]{64}$"
         },
         "publicKey": {
           "type": "object",
@@ -1602,7 +1602,7 @@ func init() {
         },
         "hash": {
           "type": "string",
-          "pattern": "^[0-9a-fA-F]{64}$"
+          "pattern": "^(sha256:)?[0-9a-fA-F]{64}$"
         },
         "publicKey": {
           "type": "object",
diff --git a/pkg/types/jar/v0.0.1/entry.go b/pkg/types/jar/v0.0.1/entry.go
index 2ea6fad..37df37f 100644
--- a/pkg/types/jar/v0.0.1/entry.go
+++ b/pkg/types/jar/v0.0.1/entry.go
@@ -93,7 +93,8 @@ func (v V001Entry) IndexKeys() []string {
 	}
 
 	if v.JARModel.Archive.Hash != nil {
-		result = append(result, strings.ToLower(swag.StringValue(v.JARModel.Archive.Hash.Value)))
+		hashKey := strings.ToLower(fmt.Sprintf("%s:%s", *v.JARModel.Archive.Hash.Algorithm, *v.JARModel.Archive.Hash.Value))
+		result = append(result, hashKey)
 	}
 
 	return result
diff --git a/pkg/types/rekord/v0.0.1/entry.go b/pkg/types/rekord/v0.0.1/entry.go
index 3e5f55d..a7f6344 100644
--- a/pkg/types/rekord/v0.0.1/entry.go
+++ b/pkg/types/rekord/v0.0.1/entry.go
@@ -88,7 +88,8 @@ func (v V001Entry) IndexKeys() []string {
 	result = append(result, v.keyObj.EmailAddresses()...)
 
 	if v.RekordObj.Data.Hash != nil {
-		result = append(result, strings.ToLower(swag.StringValue(v.RekordObj.Data.Hash.Value)))
+		hashKey := strings.ToLower(fmt.Sprintf("%s:%s", *v.RekordObj.Data.Hash.Algorithm, *v.RekordObj.Data.Hash.Value))
+		result = append(result, hashKey)
 	}
 
 	return result
diff --git a/pkg/types/rpm/v0.0.1/entry.go b/pkg/types/rpm/v0.0.1/entry.go
index b8ebbcb..10c9bdb 100644
--- a/pkg/types/rpm/v0.0.1/entry.go
+++ b/pkg/types/rpm/v0.0.1/entry.go
@@ -92,7 +92,8 @@ func (v V001Entry) IndexKeys() []string {
 	result = append(result, v.keyObj.EmailAddresses()...)
 
 	if v.RPMModel.Package.Hash != nil {
-		result = append(result, strings.ToLower(swag.StringValue(v.RPMModel.Package.Hash.Value)))
+		hashKey := strings.ToLower(fmt.Sprintf("%s:%s", *v.RPMModel.Package.Hash.Algorithm, *v.RPMModel.Package.Hash.Value))
+		result = append(result, hashKey)
 	}
 
 	return result
diff --git a/tests/e2e-test.sh b/tests/e2e-test.sh
index 2e8938a..b9f890c 100755
--- a/tests/e2e-test.sh
+++ b/tests/e2e-test.sh
@@ -45,3 +45,9 @@ TMPDIR="$(mktemp -d -t rekor_test.XXXXXX)"
 touch $TMPDIR.rekor.yaml
 trap "rm -rf $TMPDIR" EXIT
 TMPDIR=$TMPDIR go test -tags=e2e ./tests/
+if docker-compose logs --no-color | grep -q "panic: runtime error:" ; then
+   # if we're here, we found a panic
+   echo "Failing due to panics detected in logs"
+   docker-compose logs --no-color
+   exit 1
+fi
diff --git a/tests/e2e_test.go b/tests/e2e_test.go
index 1dfb80d..a554bea 100644
--- a/tests/e2e_test.go
+++ b/tests/e2e_test.go
@@ -21,7 +21,9 @@ import (
 	"context"
 	"crypto"
 	"crypto/ecdsa"
+	"crypto/sha256"
 	"crypto/x509"
+	"encoding/hex"
 	"encoding/json"
 	"encoding/pem"
 	"fmt"
@@ -187,6 +189,17 @@ func TestGet(t *testing.T) {
 
 	out = runCli(t, "search", "--public-key", pubPath)
 	outputContains(t, out, uuid)
+
+	hash := sha256.New()
+	artifactBytes, err := ioutil.ReadFile(artifactPath)
+	if err != nil {
+		t.Error(err)
+	}
+	hash.Write(artifactBytes)
+	sha := hash.Sum(nil)
+
+	out = runCli(t, "search", "--sha", fmt.Sprintf("sha256:%s", hex.EncodeToString(sha)))
+	outputContains(t, out, uuid)
 }
 
 func TestMinisign(t *testing.T) {
-- 
GitLab