From c28f0099b239217abf9b284bd99d69ab377df716 Mon Sep 17 00:00:00 2001
From: Bob Callaway <bobcallaway@users.noreply.github.com>
Date: Thu, 6 May 2021 08:03:41 -0400
Subject: [PATCH] Add Log ID to LogEntry field (#294)

* Add Log ID to LogEntry field

Since the signed entry timestamp (SET) will be able to prove insertion into the log, adding the log ID (aka public key SHA256 hash) makes it easier to know which log the entry came from.

Signed-off-by: Bob Callaway <bob.callaway@gmail.com>
---
 cmd/rekor-cli/app/get.go               |  7 +++--
 openapi.yaml                           |  8 +++++-
 pkg/api/api.go                         | 31 +++++++++++++-------
 pkg/api/entries.go                     |  6 ++--
 pkg/generated/models/log_entry.go      | 40 ++++++++++++++++++++++++--
 pkg/generated/restapi/embedded_spec.go | 24 ++++++++++++----
 6 files changed, 93 insertions(+), 23 deletions(-)

diff --git a/cmd/rekor-cli/app/get.go b/cmd/rekor-cli/app/get.go
index 2b3a7b4..d40801c 100644
--- a/cmd/rekor-cli/app/get.go
+++ b/cmd/rekor-cli/app/get.go
@@ -40,10 +40,12 @@ type getCmdOutput struct {
 	LogIndex       int
 	IntegratedTime int64
 	UUID           string
+	LogID          string
 }
 
 func (g *getCmdOutput) String() string {
-	s := fmt.Sprintf("Index: %d\n", g.LogIndex)
+	s := fmt.Sprintf("LogID: %v\n", g.LogID)
+	s += fmt.Sprintf("Index: %d\n", g.LogIndex)
 	dt := time.Unix(g.IntegratedTime, 0).UTC().Format(time.RFC3339)
 	s += fmt.Sprintf("IntegratedTime: %s\n", dt)
 	s += fmt.Sprintf("UUID: %s\n", g.UUID)
@@ -130,8 +132,9 @@ func parseEntry(uuid string, e models.LogEntryAnon) (interface{}, error) {
 	obj := getCmdOutput{
 		Body:           eimpl,
 		UUID:           uuid,
-		IntegratedTime: e.IntegratedTime,
+		IntegratedTime: *e.IntegratedTime,
 		LogIndex:       int(*e.LogIndex),
+		LogID:          *e.LogID,
 	}
 
 	return &obj, nil
diff --git a/openapi.yaml b/openapi.yaml
index b531661..561167d 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -291,6 +291,10 @@ definitions:
     additionalProperties:
       type: object
       properties:
+        logID:
+          type: string
+          pattern: '^[0-9a-fA-F]{64}$'
+          description: This is the SHA256 hash of the DER-encoded public key for the log at the time the entry was included in the log
         logIndex:
           type: integer
           minimum: 0
@@ -311,10 +315,12 @@ definitions:
                 # 1. Remove the Verification object from the JSON Document
                 # 2. Canonicalize the remaining JSON document by following RFC 8785 rules
                 # 3. Verify the canonicalized payload and signedEntryTimestamp against rekor's public key
-              description: Signature over the logIndex, body and integratedTime. 
+              description: Signature over the logID, logIndex, body and integratedTime.
       required:
+        - "logID"
         - "logIndex"
         - "body"
+        - "integratedTime"
 
   SearchIndex:
     type: object
diff --git a/pkg/api/api.go b/pkg/api/api.go
index cfd150d..d1b8fbd 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -17,7 +17,9 @@ package api
 
 import (
 	"context"
+	"crypto/sha256"
 	"crypto/x509"
+	"encoding/hex"
 	"encoding/pem"
 	"fmt"
 	"time"
@@ -47,12 +49,12 @@ func dial(ctx context.Context, rpcServer string) (*grpc.ClientConn, error) {
 }
 
 type API struct {
-	logClient trillian.TrillianLogClient
-	logID     int64
-	// PEM encoded public key
-	pubkey   string
-	signer   signature.Signer
-	verifier *client.LogVerifier
+	logClient  trillian.TrillianLogClient
+	logID      int64
+	pubkey     string // PEM encoded public key
+	pubkeyHash string // SHA256 hash of DER-encoded public key
+	signer     signature.Signer
+	verifier   *client.LogVerifier
 }
 
 func NewAPI() (*API, error) {
@@ -95,6 +97,12 @@ func NewAPI() (*API, error) {
 	if err != nil {
 		return nil, errors.Wrap(err, "marshalling public key")
 	}
+	hasher := sha256.New()
+	if _, err = hasher.Write(b); err != nil {
+		return nil, errors.Wrap(err, "computing hash of public key")
+	}
+	pubkeyHashBytes := hasher.Sum(nil)
+
 	pubkey := pem.EncodeToMemory(&pem.Block{
 		Type:  "PUBLIC KEY",
 		Bytes: b,
@@ -106,11 +114,12 @@ func NewAPI() (*API, error) {
 	}
 
 	return &API{
-		logClient: logClient,
-		logID:     tLogID,
-		pubkey:    string(pubkey),
-		signer:    signer,
-		verifier:  verifier,
+		logClient:  logClient,
+		logID:      tLogID,
+		pubkey:     string(pubkey),
+		pubkeyHash: hex.EncodeToString(pubkeyHashBytes),
+		signer:     signer,
+		verifier:   verifier,
 	}, nil
 }
 
diff --git a/pkg/api/entries.go b/pkg/api/entries.go
index e67fafc..69b649a 100644
--- a/pkg/api/entries.go
+++ b/pkg/api/entries.go
@@ -62,9 +62,10 @@ func logEntryFromLeaf(tc TrillianClient, leaf *trillian.LogLeaf, signedLogRoot *
 
 	logEntry := models.LogEntry{
 		hex.EncodeToString(leaf.MerkleLeafHash): models.LogEntryAnon{
+			LogID:          swag.String(api.pubkeyHash),
 			LogIndex:       &leaf.LeafIndex,
 			Body:           leaf.LeafValue,
-			IntegratedTime: leaf.IntegrateTimestamp.AsTime().Unix(),
+			IntegratedTime: swag.Int64(leaf.IntegrateTimestamp.AsTime().Unix()),
 			Verification: &models.LogEntryAnonVerification{
 				InclusionProof: &inclusionProof,
 			},
@@ -143,9 +144,10 @@ func CreateLogEntryHandler(params entries.CreateLogEntryParams) middleware.Respo
 	uuid := hex.EncodeToString(queuedLeaf.GetMerkleLeafHash())
 
 	logEntryAnon := models.LogEntryAnon{
+		LogID:          swag.String(api.pubkeyHash),
 		LogIndex:       swag.Int64(queuedLeaf.LeafIndex),
 		Body:           queuedLeaf.GetLeafValue(),
-		IntegratedTime: queuedLeaf.IntegrateTimestamp.AsTime().Unix(),
+		IntegratedTime: swag.Int64(queuedLeaf.IntegrateTimestamp.AsTime().Unix()),
 	}
 
 	if viper.GetBool("enable_retrieve_api") {
diff --git a/pkg/generated/models/log_entry.go b/pkg/generated/models/log_entry.go
index 9924fd8..a9ae7cc 100644
--- a/pkg/generated/models/log_entry.go
+++ b/pkg/generated/models/log_entry.go
@@ -88,7 +88,13 @@ type LogEntryAnon struct {
 	Body interface{} `json:"body"`
 
 	// integrated time
-	IntegratedTime int64 `json:"integratedTime,omitempty"`
+	// Required: true
+	IntegratedTime *int64 `json:"integratedTime"`
+
+	// This is the SHA256 hash of the DER-encoded public key for the log at the time the entry was included in the log
+	// Required: true
+	// Pattern: ^[0-9a-fA-F]{64}$
+	LogID *string `json:"logID"`
 
 	// log index
 	// Required: true
@@ -107,6 +113,14 @@ func (m *LogEntryAnon) Validate(formats strfmt.Registry) error {
 		res = append(res, err)
 	}
 
+	if err := m.validateIntegratedTime(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateLogID(formats); err != nil {
+		res = append(res, err)
+	}
+
 	if err := m.validateLogIndex(formats); err != nil {
 		res = append(res, err)
 	}
@@ -130,6 +144,28 @@ func (m *LogEntryAnon) validateBody(formats strfmt.Registry) error {
 	return nil
 }
 
+func (m *LogEntryAnon) validateIntegratedTime(formats strfmt.Registry) error {
+
+	if err := validate.Required("integratedTime", "body", m.IntegratedTime); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *LogEntryAnon) validateLogID(formats strfmt.Registry) error {
+
+	if err := validate.Required("logID", "body", m.LogID); err != nil {
+		return err
+	}
+
+	if err := validate.Pattern("logID", "body", *m.LogID, `^[0-9a-fA-F]{64}$`); err != nil {
+		return err
+	}
+
+	return nil
+}
+
 func (m *LogEntryAnon) validateLogIndex(formats strfmt.Registry) error {
 
 	if err := validate.Required("logIndex", "body", m.LogIndex); err != nil {
@@ -214,7 +250,7 @@ type LogEntryAnonVerification struct {
 	// inclusion proof
 	InclusionProof *InclusionProof `json:"inclusionProof,omitempty"`
 
-	// Signature over the logIndex, body and integratedTime.
+	// Signature over the logID, logIndex, body and integratedTime.
 	// Format: byte
 	SignedEntryTimestamp strfmt.Base64 `json:"signedEntryTimestamp,omitempty"`
 }
diff --git a/pkg/generated/restapi/embedded_spec.go b/pkg/generated/restapi/embedded_spec.go
index 1cd7cb5..d9843d8 100644
--- a/pkg/generated/restapi/embedded_spec.go
+++ b/pkg/generated/restapi/embedded_spec.go
@@ -400,8 +400,10 @@ func init() {
       "additionalProperties": {
         "type": "object",
         "required": [
+          "logID",
           "logIndex",
-          "body"
+          "body",
+          "integratedTime"
         ],
         "properties": {
           "body": {
@@ -411,6 +413,11 @@ func init() {
           "integratedTime": {
             "type": "integer"
           },
+          "logID": {
+            "description": "This is the SHA256 hash of the DER-encoded public key for the log at the time the entry was included in the log",
+            "type": "string",
+            "pattern": "^[0-9a-fA-F]{64}$"
+          },
           "logIndex": {
             "type": "integer"
           },
@@ -421,7 +428,7 @@ func init() {
                 "$ref": "#/definitions/InclusionProof"
               },
               "signedEntryTimestamp": {
-                "description": "Signature over the logIndex, body and integratedTime.",
+                "description": "Signature over the logID, logIndex, body and integratedTime.",
                 "type": "string",
                 "format": "byte"
               }
@@ -1193,8 +1200,10 @@ func init() {
     "LogEntryAnon": {
       "type": "object",
       "required": [
+        "logID",
         "logIndex",
-        "body"
+        "body",
+        "integratedTime"
       ],
       "properties": {
         "body": {
@@ -1204,6 +1213,11 @@ func init() {
         "integratedTime": {
           "type": "integer"
         },
+        "logID": {
+          "description": "This is the SHA256 hash of the DER-encoded public key for the log at the time the entry was included in the log",
+          "type": "string",
+          "pattern": "^[0-9a-fA-F]{64}$"
+        },
         "logIndex": {
           "type": "integer",
           "minimum": 0
@@ -1215,7 +1229,7 @@ func init() {
               "$ref": "#/definitions/InclusionProof"
             },
             "signedEntryTimestamp": {
-              "description": "Signature over the logIndex, body and integratedTime.",
+              "description": "Signature over the logID, logIndex, body and integratedTime.",
               "type": "string",
               "format": "byte"
             }
@@ -1230,7 +1244,7 @@ func init() {
           "$ref": "#/definitions/InclusionProof"
         },
         "signedEntryTimestamp": {
-          "description": "Signature over the logIndex, body and integratedTime.",
+          "description": "Signature over the logID, logIndex, body and integratedTime.",
           "type": "string",
           "format": "byte"
         }
-- 
GitLab