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