Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include checkpoint (STH) in entry upload and retrieve responses #1015

Merged
merged 5 commits into from Sep 1, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions cmd/rekor-cli/app/get.go
Expand Up @@ -118,6 +118,13 @@ var getCmd = &cobra.Command{
return nil, fmt.Errorf("unable to verify entry was added to log: %w", err)
}

// verify checkpoint
if entry.Verification.InclusionProof.Checkpoint != nil {
if err := verify.VerifyCheckpointSignature(&e, verifier); err != nil {
return nil, err
}
}

return parseEntry(ix, entry)
}
}
Expand Down
10 changes: 10 additions & 0 deletions cmd/rekor-cli/app/upload.go
Expand Up @@ -141,6 +141,16 @@ var uploadCmd = &cobra.Command{
if err := verify.VerifySignedEntryTimestamp(ctx, &logEntry, verifier); err != nil {
return nil, fmt.Errorf("unable to verify entry was added to log: %w", err)
}
if logEntry.Verification.InclusionProof != nil {
// verify inclusion proof
if err := verify.VerifyInclusion(ctx, &logEntry); err != nil {
return nil, fmt.Errorf("error verifying inclusion proof: %w", err)
}
// verify checkpoint
if err := verify.VerifyCheckpointSignature(&logEntry, verifier); err != nil {
asraa marked this conversation as resolved.
Show resolved Hide resolved
return nil, err
}
}

return &uploadCmdOutput{
Location: string(resp.Location),
Expand Down
28 changes: 22 additions & 6 deletions cmd/rekor-cli/app/verify.go
Expand Up @@ -37,18 +37,24 @@ import (
)

type verifyCmdOutput struct {
RootHash string
EntryUUID string
Index int64
Size int64
Hashes []string
RootHash string
EntryUUID string
Index int64
Size int64
Hashes []string
Checkpoint string
}

func (v *verifyCmdOutput) String() string {
s := fmt.Sprintf("Current Root Hash: %v\n", v.RootHash)
s += fmt.Sprintf("Entry Hash: %v\n", v.EntryUUID)
s += fmt.Sprintf("Entry Index: %v\n", v.Index)
s += fmt.Sprintf("Current Tree Size: %v\n\n", v.Size)
s += fmt.Sprintf("Current Tree Size: %v\n", v.Size)
if len(v.Checkpoint) > 0 {
s += fmt.Sprintf("Checkpoint:\n%v\n\n", v.Checkpoint)
} else {
s += "\n"
}

s += "Inclusion Proof:\n"
hasher := rfc6962.DefaultHasher
Expand Down Expand Up @@ -147,6 +153,9 @@ var verifyCmd = &cobra.Command{
Size: *v.Verification.InclusionProof.TreeSize,
Hashes: v.Verification.InclusionProof.Hashes,
}
if v.Verification.InclusionProof.Checkpoint != nil {
o.Checkpoint = *v.Verification.InclusionProof.Checkpoint
}
entry = v
}

Expand All @@ -171,6 +180,13 @@ var verifyCmd = &cobra.Command{
return nil, fmt.Errorf("validating entry: %w", err)
}

// verify checkpoint
if entry.Verification.InclusionProof.Checkpoint != nil {
if err := verify.VerifyCheckpointSignature(&entry, verifier); err != nil {
return nil, err
}
}

return o, err
}),
}
Expand Down
5 changes: 5 additions & 0 deletions openapi.yaml
Expand Up @@ -631,11 +631,16 @@ definitions:
type: string
description: SHA256 hash value expressed in hexadecimal format
pattern: '^[0-9a-fA-F]{64}$'
checkpoint:
type: string
format: signedCheckpoint
description: The checkpoint (signed tree head) that the inclusion proof is based on
required:
- logIndex
- rootHash
- treeSize
- hashes
- checkpoint

Error:
type: object
Expand Down
38 changes: 34 additions & 4 deletions pkg/api/entries.go
Expand Up @@ -42,6 +42,7 @@ import (
"github.com/sigstore/rekor/pkg/log"
"github.com/sigstore/rekor/pkg/sharding"
"github.com/sigstore/rekor/pkg/types"
"github.com/sigstore/rekor/pkg/util"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/options"
)
Expand Down Expand Up @@ -92,11 +93,17 @@ func logEntryFromLeaf(ctx context.Context, signer signature.Signer, tc TrillianC
return nil, fmt.Errorf("signing entry error: %w", err)
}

scBytes, err := util.CreateAndSignCheckpoint(ctx, viper.GetString("rekor_server.hostname"), tc.logID, root, api.signer)
if err != nil {
return nil, err
}

inclusionProof := models.InclusionProof{
TreeSize: swag.Int64(int64(root.TreeSize)),
RootHash: swag.String(hex.EncodeToString(root.RootHash)),
LogIndex: swag.Int64(proof.GetLeafIndex()),
Hashes: hashes,
TreeSize: swag.Int64(int64(root.TreeSize)),
RootHash: swag.String(hex.EncodeToString(root.RootHash)),
LogIndex: swag.Int64(proof.GetLeafIndex()),
Hashes: hashes,
Checkpoint: stringPointer(string(scBytes)),
}

uuid := hex.EncodeToString(leaf.MerkleLeafHash)
Expand Down Expand Up @@ -261,7 +268,30 @@ func createLogEntry(params entries.CreateLogEntryParams) (models.LogEntry, middl
return nil, handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("signing entry error: %v", err), signingError)
}

root := &ttypes.LogRootV1{}
if err := root.UnmarshalBinary(resp.getLeafAndProofResult.SignedLogRoot.LogRoot); err != nil {
return nil, handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("error unmarshalling log root: %v", err), sthGenerateError)
}
hashes := []string{}
for _, hash := range resp.getLeafAndProofResult.Proof.Hashes {
hashes = append(hashes, hex.EncodeToString(hash))
}

scBytes, err := util.CreateAndSignCheckpoint(ctx, viper.GetString("rekor_server.hostname"), tc.logID, root, api.signer)
if err != nil {
return nil, handleRekorAPIError(params, http.StatusInternalServerError, err, sthGenerateError)
}

inclusionProof := models.InclusionProof{
TreeSize: swag.Int64(int64(root.TreeSize)),
RootHash: swag.String(hex.EncodeToString(root.RootHash)),
LogIndex: swag.Int64(queuedLeaf.LeafIndex),
Hashes: hashes,
Checkpoint: stringPointer(string(scBytes)),
}

logEntryAnon.Verification = &models.LogEntryAnonVerification{
InclusionProof: &inclusionProof,
SignedEntryTimestamp: strfmt.Base64(signature),
}

Expand Down
42 changes: 5 additions & 37 deletions pkg/api/tlog.go
Expand Up @@ -21,7 +21,6 @@ import (
"fmt"
"net/http"
"strconv"
"time"

"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/swag"
Expand All @@ -33,7 +32,6 @@ import (
"github.com/sigstore/rekor/pkg/generated/restapi/operations/tlog"
"github.com/sigstore/rekor/pkg/log"
"github.com/sigstore/rekor/pkg/util"
"github.com/sigstore/sigstore/pkg/signature/options"
)

// GetLogInfoHandler returns the current size of the tree and the STH
Expand Down Expand Up @@ -68,32 +66,16 @@ func GetLogInfoHandler(params tlog.GetLogInfoParams) middleware.Responder {
hashString := hex.EncodeToString(root.RootHash)
treeSize := int64(root.TreeSize)

sth, err := util.CreateSignedCheckpoint(util.Checkpoint{
Origin: fmt.Sprintf("%s - %d", viper.GetString("rekor_server.hostname"), tc.ranges.ActiveTreeID()),
Size: root.TreeSize,
Hash: root.RootHash,
})
scBytes, err := util.CreateAndSignCheckpoint(params.HTTPRequest.Context(),
viper.GetString("rekor_server.hostname"), tc.ranges.ActiveTreeID(), root, api.signer)
if err != nil {
return handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("marshalling error: %w", err), sthGenerateError)
return handleRekorAPIError(params, http.StatusInternalServerError, err, sthGenerateError)
}
sth.SetTimestamp(uint64(time.Now().UnixNano()))

// sign the log root ourselves to get the log root signature
_, err = sth.Sign(viper.GetString("rekor_server.hostname"), api.signer, options.WithContext(params.HTTPRequest.Context()))
if err != nil {
return handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("signing error: %w", err), signingError)
}

scBytes, err := sth.SignedNote.MarshalText()
if err != nil {
return handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("marshalling error: %w", err), sthGenerateError)
}
scString := string(scBytes)

logInfo := models.LogInfo{
RootHash: &hashString,
TreeSize: &treeSize,
SignedTreeHead: &scString,
SignedTreeHead: stringPointer(string(scBytes)),
TreeID: stringPointer(fmt.Sprintf("%d", tc.logID)),
InactiveShards: inactiveShards,
}
Expand Down Expand Up @@ -169,25 +151,11 @@ func inactiveShardLogInfo(ctx context.Context, tid int64) (*models.InactiveShard
hashString := hex.EncodeToString(root.RootHash)
treeSize := int64(root.TreeSize)

sth, err := util.CreateSignedCheckpoint(util.Checkpoint{
Origin: fmt.Sprintf("%s - %d", viper.GetString("rekor_server.hostname"), tid),
Size: root.TreeSize,
Hash: root.RootHash,
})
scBytes, err := util.CreateAndSignCheckpoint(ctx, viper.GetString("rekor_server.hostname"), tid, root, api.signer)
if err != nil {
return nil, err
}
sth.SetTimestamp(uint64(time.Now().UnixNano()))

// sign the log root ourselves to get the log root signature
if _, err := sth.Sign(viper.GetString("rekor_server.hostname"), api.signer, options.WithContext(ctx)); err != nil {
return nil, err
}

scBytes, err := sth.SignedNote.MarshalText()
if err != nil {
return nil, err
}
m := models.InactiveShardLogInfo{
RootHash: &hashString,
TreeSize: &treeSize,
Expand Down
2 changes: 2 additions & 0 deletions pkg/api/trillian_client.go
Expand Up @@ -185,6 +185,8 @@ func (t *TrillianClient) addLeaf(byteValue []byte) *Response {
status: status.Code(err),
err: err,
getAddResult: resp,
// include getLeafAndProofResult for inclusion proof
getLeafAndProofResult: leafResp.getLeafAndProofResult,
}
}

Expand Down
17 changes: 17 additions & 0 deletions pkg/generated/models/inclusion_proof.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 14 additions & 2 deletions pkg/generated/restapi/embedded_spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions pkg/util/checkpoint.go
Expand Up @@ -17,11 +17,17 @@ package util

import (
"bytes"
"context"
"encoding/base64"
"errors"
"fmt"
"strconv"
"strings"
"time"

"github.com/google/trillian/types"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/options"
)

// heavily borrowed from https://github.com/google/trillian-examples/blob/master/formats/log/checkpoint.go
Expand Down Expand Up @@ -160,3 +166,24 @@ func (r *SignedCheckpoint) GetTimestamp() uint64 {
}
return ts
}

// CreateAndSignCheckpoint creates a signed checkpoint as a commitment to the current root hash
func CreateAndSignCheckpoint(ctx context.Context, hostname string, treeID int64, root *types.LogRootV1, signer signature.Signer) ([]byte, error) {
sth, err := CreateSignedCheckpoint(Checkpoint{
Origin: fmt.Sprintf("%s - %d", hostname, treeID),
Size: root.TreeSize,
Hash: root.RootHash,
})
if err != nil {
return []byte{}, fmt.Errorf("error creating checkpoint: %v", err)
haydentherapper marked this conversation as resolved.
Show resolved Hide resolved
}
sth.SetTimestamp(uint64(time.Now().UnixNano()))
if _, err := sth.Sign(hostname, signer, options.WithContext(ctx)); err != nil {
return []byte{}, fmt.Errorf("error signing checkpoint: %v", err)
}
scBytes, err := sth.SignedNote.MarshalText()
if err != nil {
return []byte{}, fmt.Errorf("error marshalling checkpoint: %v", err)
}
return scBytes, nil
}