Skip to content

Commit

Permalink
Include checkpoint (STH) in entry upload and retrieve responses
Browse files Browse the repository at this point in the history
This associates a root hash in an inclusion proof with a signed
commitment from the log. Previously, without this included, there was no
connection between an inclusion proof and the log. An inclusion proof
and checkpoint can be an alternative proof of inclusion instead of a
SET.

Ref #988

Signed-off-by: Hayden Blauzvern <hblauzvern@google.com>
  • Loading branch information
haydentherapper committed Aug 30, 2022
1 parent ae429bb commit b8c268c
Show file tree
Hide file tree
Showing 71 changed files with 394 additions and 162 deletions.
42 changes: 42 additions & 0 deletions cmd/rekor-cli/app/upload.go
Expand Up @@ -19,6 +19,9 @@ import (
"context"
"crypto/ecdsa"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"io"
"net/http"
Expand All @@ -31,6 +34,8 @@ import (
"github.com/go-openapi/swag"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/transparency-dev/merkle/proof"
"github.com/transparency-dev/merkle/rfc6962"

"github.com/sigstore/rekor/cmd/rekor-cli/app/format"
"github.com/sigstore/rekor/pkg/client"
Expand Down Expand Up @@ -185,6 +190,43 @@ func verifyLogEntry(ctx context.Context, rekorClient *genclient.Rekor, logEntry
if !ecdsa.VerifyASN1(rekorPubKey, hash[:], []byte(logEntry.Verification.SignedEntryTimestamp)) {
return false, fmt.Errorf("unable to verify")
}

// verify inclusion proof if present
if logEntry.Verification.InclusionProof != nil {
// verify inclusion proof
entryBytes, err := base64.StdEncoding.DecodeString(logEntry.Body.(string))
if err != nil {
return false, err
}
leafHash := rfc6962.DefaultHasher.HashLeaf(entryBytes)
rootHash, err := hex.DecodeString(*logEntry.Verification.InclusionProof.RootHash)
if err != nil {
return false, err
}
hashes := [][]byte{}
for _, h := range logEntry.Verification.InclusionProof.Hashes {
hb, _ := hex.DecodeString(h)
hashes = append(hashes, hb)
}
if err := proof.VerifyInclusion(rfc6962.DefaultHasher, uint64(*logEntry.LogIndex), uint64(*logEntry.Verification.InclusionProof.TreeSize),
leafHash, hashes, rootHash); err != nil {
return false, fmt.Errorf("verifying inclusion proof: %w", err)

}
// verify checkpoint
sth := &util.SignedCheckpoint{}
if err := sth.UnmarshalText([]byte(*logEntry.Verification.InclusionProof.Checkpoint)); err != nil {
return false, fmt.Errorf("unmarshalling logEntry checkpoint to SignedCheckpoint: %w", err)
}
verifier, err := loadVerifier(rekorClient)
if err != nil {
return false, err
}
if !sth.Verify(verifier) {
return false, errors.New("error verifying signed checkpoint")
}
}

return true, nil
}

Expand Down
38 changes: 32 additions & 6 deletions cmd/rekor-cli/app/verify.go
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"math/bits"
"strconv"
Expand All @@ -36,21 +37,28 @@ 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"
)

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 @@ -148,6 +156,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
}
entryBytes, err = base64.StdEncoding.DecodeString(v.Body.(string))
if err != nil {
return nil, err
Expand Down Expand Up @@ -181,6 +192,21 @@ var verifyCmd = &cobra.Command{
if err := proof.VerifyInclusion(rfc6962.DefaultHasher, uint64(o.Index), uint64(o.Size), leafHash, hashes, rootHash); err != nil {
return nil, err
}

if len(o.Checkpoint) > 0 {
sth := &util.SignedCheckpoint{}
if err := sth.UnmarshalText([]byte(o.Checkpoint)); err != nil {
return nil, fmt.Errorf("unmarshalling o.Checkpoint to SignedCheckpoint: %w", err)
}
verifier, err := loadVerifier(rekorClient)
if err != nil {
return nil, err
}
if !sth.Verify(verifier) {
return nil, errors.New("error verifying signed checkpoint")
}
}

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
68 changes: 64 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,32 @@ func logEntryFromLeaf(ctx context.Context, signer signature.Signer, tc TrillianC
return nil, fmt.Errorf("signing entry error: %w", err)
}

// sign a checkpoint as a commitment to the current root hash
sth, err := util.CreateSignedCheckpoint(util.Checkpoint{
Origin: fmt.Sprintf("%s - %d", viper.GetString("rekor_server.hostname"), tc.logID),
Size: root.TreeSize,
Hash: root.RootHash,
})
if err != nil {
return nil, fmt.Errorf("error marshalling checkpoint: %w", err)
}
sth.SetTimestamp(uint64(*logEntryAnon.IntegratedTime))
_, err = sth.Sign(viper.GetString("rekor_server.hostname"), api.signer, options.WithContext(ctx))
if err != nil {
return nil, fmt.Errorf("error signing checkpoint: %w", err)
}
scBytes, err := sth.SignedNote.MarshalText()
if err != nil {
return nil, fmt.Errorf("error marshalling checkpoint: %w", err)
}
scString := string(scBytes)

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: &scString,
}

uuid := hex.EncodeToString(leaf.MerkleLeafHash)
Expand Down Expand Up @@ -261,7 +283,45 @@ 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))
}

// sign a checkpoint as a commitment to the current root hash
sth, err := util.CreateSignedCheckpoint(util.Checkpoint{
Origin: fmt.Sprintf("%s - %d", viper.GetString("rekor_server.hostname"), tc.logID),
Size: root.TreeSize,
Hash: root.RootHash,
})
if err != nil {
return nil, handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("error creating checkpoint: %v", err), sthGenerateError)
}
sth.SetTimestamp(uint64(*logEntryAnon.IntegratedTime))
_, err = sth.Sign(viper.GetString("rekor_server.hostname"), api.signer, options.WithContext(ctx))
if err != nil {
return nil, handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("error signing checkpoint: %v", err), sthGenerateError)
}
scBytes, err := sth.SignedNote.MarshalText()
if err != nil {
return nil, handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("error marshalling checkpoint: %v", err), sthGenerateError)
}
scString := string(scBytes)

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

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

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
8 changes: 5 additions & 3 deletions pkg/generated/client/entries/create_log_entry_parameters.go

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

12 changes: 8 additions & 4 deletions pkg/generated/client/entries/create_log_entry_responses.go

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

13 changes: 6 additions & 7 deletions pkg/generated/client/entries/entries_client.go

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

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

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

0 comments on commit b8c268c

Please sign in to comment.