Skip to content

Commit

Permalink
feat: add verification functions (#986)
Browse files Browse the repository at this point in the history
* feat: add verification functions

Signed-off-by: Asra Ali <asraa@google.com>
  • Loading branch information
asraa committed Aug 30, 2022
1 parent 7492834 commit 5159d01
Show file tree
Hide file tree
Showing 6 changed files with 481 additions and 120 deletions.
21 changes: 17 additions & 4 deletions cmd/rekor-cli/app/get.go
Expand Up @@ -36,6 +36,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/verify"
)

type getCmdOutput struct {
Expand Down Expand Up @@ -79,6 +80,7 @@ var getCmd = &cobra.Command{
}
},
Run: format.WrapCmd(func(args []string) (interface{}, error) {
ctx := context.Background()
rekorClient, err := client.GetRekorClient(viper.GetString("rekor_server"), client.WithUserAgent(UserAgent()))
if err != nil {
return nil, err
Expand All @@ -89,6 +91,11 @@ var getCmd = &cobra.Command{
if logIndex == "" && uuid == "" {
return nil, errors.New("either --uuid or --log-index must be specified")
}
// retrieve rekor pubkey for verification
verifier, err := loadVerifier(rekorClient)
if err != nil {
return nil, fmt.Errorf("retrieving rekor public key")
}

if logIndex != "" {
params := entries.NewGetLogEntryByIndexParams()
Expand All @@ -103,9 +110,12 @@ var getCmd = &cobra.Command{
if err != nil {
return nil, err
}
var e models.LogEntryAnon
for ix, entry := range resp.Payload {
if verified, err := verifyLogEntry(context.Background(), rekorClient, entry); err != nil || !verified {
return nil, fmt.Errorf("unable to verify entry was added to log %w", err)
// verify log entry
e = entry
if err := verify.VerifyLogEntry(ctx, &e, verifier); err != nil {
return nil, fmt.Errorf("unable to verify entry was added to log: %w", err)
}

return parseEntry(ix, entry)
Expand All @@ -132,13 +142,16 @@ var getCmd = &cobra.Command{
return nil, err
}

var e models.LogEntryAnon
for k, entry := range resp.Payload {
if k != u {
continue
}

if verified, err := verifyLogEntry(context.Background(), rekorClient, entry); err != nil || !verified {
return nil, fmt.Errorf("unable to verify entry was added to log %w", err)
// verify log entry
e = entry
if err := verify.VerifyLogEntry(ctx, &e, verifier); err != nil {
return nil, fmt.Errorf("unable to verify entry was added to log: %w", err)
}

return parseEntry(k, entry)
Expand Down
65 changes: 15 additions & 50 deletions cmd/rekor-cli/app/log_info.go
Expand Up @@ -16,10 +16,9 @@
package app

import (
"bytes"
"context"
"crypto"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
Expand All @@ -28,10 +27,10 @@ import (
"github.com/go-openapi/swag"
rclient "github.com/sigstore/rekor/pkg/generated/client"
"github.com/sigstore/rekor/pkg/generated/models"

"github.com/sigstore/rekor/pkg/verify"
"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/cmd/rekor-cli/app/state"
Expand Down Expand Up @@ -70,6 +69,7 @@ var logInfoCmd = &cobra.Command{
Long: `Prints info about the transparency log`,
Run: format.WrapCmd(func(args []string) (interface{}, error) {
serverURL := viper.GetString("rekor_server")
ctx := context.Background()
rekorClient, err := client.GetRekorClient(serverURL, client.WithUserAgent(UserAgent()))
if err != nil {
return nil, err
Expand All @@ -85,7 +85,7 @@ var logInfoCmd = &cobra.Command{
logInfo := result.GetPayload()

// Verify inactive shards
if err := verifyInactiveTrees(rekorClient, serverURL, logInfo.InactiveShards); err != nil {
if err := verifyInactiveTrees(ctx, rekorClient, serverURL, logInfo.InactiveShards); err != nil {
return nil, err
}

Expand All @@ -97,7 +97,7 @@ var logInfoCmd = &cobra.Command{
}
treeID := swag.StringValue(logInfo.TreeID)

if err := verifyTree(rekorClient, signedTreeHead, serverURL, treeID); err != nil {
if err := verifyTree(ctx, rekorClient, signedTreeHead, serverURL, treeID); err != nil {
return nil, err
}

Expand All @@ -112,23 +112,23 @@ var logInfoCmd = &cobra.Command{
}),
}

func verifyInactiveTrees(rekorClient *rclient.Rekor, serverURL string, inactiveShards []*models.InactiveShardLogInfo) error {
func verifyInactiveTrees(ctx context.Context, rekorClient *rclient.Rekor, serverURL string, inactiveShards []*models.InactiveShardLogInfo) error {
if inactiveShards == nil {
return nil
}
log.CliLogger.Infof("Validating inactive shards...")
for _, shard := range inactiveShards {
signedTreeHead := swag.StringValue(shard.SignedTreeHead)
treeID := swag.StringValue(shard.TreeID)
if err := verifyTree(rekorClient, signedTreeHead, serverURL, treeID); err != nil {
if err := verifyTree(ctx, rekorClient, signedTreeHead, serverURL, treeID); err != nil {
return fmt.Errorf("verifying inactive shard with ID %s: %w", treeID, err)
}
}
log.CliLogger.Infof("Successfully validated inactive shards")
return nil
}

func verifyTree(rekorClient *rclient.Rekor, signedTreeHead, serverURL, treeID string) error {
func verifyTree(ctx context.Context, rekorClient *rclient.Rekor, signedTreeHead, serverURL, treeID string) error {
oldState := state.Load(serverURL)
if treeID != "" {
oldState = state.Load(treeID)
Expand All @@ -145,8 +145,12 @@ func verifyTree(rekorClient *rclient.Rekor, signedTreeHead, serverURL, treeID st
return errors.New("signature on tree head did not verify")
}

if err := proveConsistency(rekorClient, oldState, sth, treeID); err != nil {
return err
if oldState != nil {
if err := verify.ProveConsistency(ctx, rekorClient, oldState, &sth, treeID); err != nil {
return err
}
} else {
log.CliLogger.Infof("No previous log state stored, unable to prove consistency")
}

if viper.GetBool("store_tree_state") {
Expand All @@ -162,45 +166,6 @@ func verifyTree(rekorClient *rclient.Rekor, signedTreeHead, serverURL, treeID st
return nil
}

func proveConsistency(rekorClient *rclient.Rekor, oldState *util.SignedCheckpoint, sth util.SignedCheckpoint, treeID string) error {
if oldState == nil {
log.CliLogger.Infof("No previous log state stored, unable to prove consistency")
return nil
}
persistedSize := oldState.Size
switch {
case persistedSize < sth.Size:
log.CliLogger.Infof("Found previous log state, proving consistency between %d and %d", oldState.Size, sth.Size)
params := tlog.NewGetLogProofParams()
firstSize := int64(persistedSize)
params.FirstSize = &firstSize
params.LastSize = int64(sth.Size)
params.TreeID = &treeID
logProof, err := rekorClient.Tlog.GetLogProof(params)
if err != nil {
return err
}
hashes := [][]byte{}
for _, h := range logProof.Payload.Hashes {
b, _ := hex.DecodeString(h)
hashes = append(hashes, b)
}
if err := proof.VerifyConsistency(rfc6962.DefaultHasher, persistedSize, sth.Size, hashes, oldState.Hash,
sth.Hash); err != nil {
return err
}
log.CliLogger.Infof("Consistency proof valid!")
case persistedSize == sth.Size:
if !bytes.Equal(oldState.Hash, sth.Hash) {
return errors.New("root hash returned from server does not match previously persisted state")
}
log.CliLogger.Infof("Persisted log state matches the current state of the log")
default:
return fmt.Errorf("current size of tree reported from server %d is less than previously persisted state %d", sth.Size, persistedSize)
}
return nil
}

func loadVerifier(rekorClient *rclient.Rekor) (signature.Verifier, error) {
publicKey := viper.GetString("rekor_server_public_key")
if publicKey == "" {
Expand Down
51 changes: 6 additions & 45 deletions cmd/rekor-cli/app/upload.go
Expand Up @@ -17,29 +17,25 @@ package app

import (
"context"
"crypto/ecdsa"
"crypto/sha256"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"

"github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer"
"github.com/go-openapi/runtime"
"github.com/go-openapi/swag"
"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/sigstore/rekor/cmd/rekor-cli/app/format"
"github.com/sigstore/rekor/pkg/client"
genclient "github.com/sigstore/rekor/pkg/generated/client"
"github.com/sigstore/rekor/pkg/generated/client/entries"
"github.com/sigstore/rekor/pkg/generated/models"
"github.com/sigstore/rekor/pkg/log"
"github.com/sigstore/rekor/pkg/types"
"github.com/sigstore/rekor/pkg/util"
"github.com/sigstore/rekor/pkg/verify"
)

type uploadCmdOutput struct {
Expand Down Expand Up @@ -138,7 +134,11 @@ var uploadCmd = &cobra.Command{
}

// verify log entry
if verified, err := verifyLogEntry(ctx, rekorClient, logEntry); err != nil || !verified {
verifier, err := loadVerifier(rekorClient)
if err != nil {
return nil, fmt.Errorf("retrieving rekor public key")
}
if err := verify.VerifySignedEntryTimestamp(ctx, &logEntry, verifier); err != nil {
return nil, fmt.Errorf("unable to verify entry was added to log: %w", err)
}

Expand All @@ -149,45 +149,6 @@ var uploadCmd = &cobra.Command{
}),
}

func verifyLogEntry(ctx context.Context, rekorClient *genclient.Rekor, logEntry models.LogEntryAnon) (bool, error) {
if logEntry.Verification == nil {
return false, nil
}
// verify the entry
if logEntry.Verification.SignedEntryTimestamp == nil {
return false, fmt.Errorf("signature missing")
}

le := &models.LogEntryAnon{
IntegratedTime: logEntry.IntegratedTime,
LogIndex: logEntry.LogIndex,
Body: logEntry.Body,
LogID: logEntry.LogID,
}

payload, err := le.MarshalBinary()
if err != nil {
return false, err
}
canonicalized, err := jsoncanonicalizer.Transform(payload)
if err != nil {
return false, err
}

// get rekor's public key
rekorPubKey, err := util.PublicKey(ctx, rekorClient)
if err != nil {
return false, err
}

// verify the SET against the public key
hash := sha256.Sum256(canonicalized)
if !ecdsa.VerifyASN1(rekorPubKey, hash[:], []byte(logEntry.Verification.SignedEntryTimestamp)) {
return false, fmt.Errorf("unable to verify")
}
return true, nil
}

func init() {
initializePFlagMap()
if err := addArtifactPFlags(uploadCmd); err != nil {
Expand Down
32 changes: 11 additions & 21 deletions cmd/rekor-cli/app/verify.go
Expand Up @@ -16,17 +16,14 @@
package app

import (
"bytes"
"context"
"encoding/base64"
"encoding/hex"
"fmt"
"math/bits"
"strconv"

"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"
Expand All @@ -36,6 +33,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/verify"
)

type verifyCmdOutput struct {
Expand Down Expand Up @@ -88,6 +86,7 @@ var verifyCmd = &cobra.Command{
return nil
},
Run: format.WrapCmd(func(args []string) (interface{}, error) {
ctx := context.Background()
rekorClient, err := client.GetRekorClient(viper.GetString("rekor_server"), client.WithUserAgent(UserAgent()))
if err != nil {
return nil, err
Expand Down Expand Up @@ -139,7 +138,7 @@ var verifyCmd = &cobra.Command{
logEntry := resp.Payload[0]

var o *verifyCmdOutput
var entryBytes []byte
var entry models.LogEntryAnon
for k, v := range logEntry {
o = &verifyCmdOutput{
RootHash: *v.Verification.InclusionProof.RootHash,
Expand All @@ -148,10 +147,7 @@ var verifyCmd = &cobra.Command{
Size: *v.Verification.InclusionProof.TreeSize,
Hashes: v.Verification.InclusionProof.Hashes,
}
entryBytes, err = base64.StdEncoding.DecodeString(v.Body.(string))
if err != nil {
return nil, err
}
entry = v
}

if viper.IsSet("uuid") {
Expand All @@ -164,23 +160,17 @@ var verifyCmd = &cobra.Command{
}
}

// Note: the returned entry UUID is the UUID (not include the Tree ID)
leafHash, _ := hex.DecodeString(o.EntryUUID)
if !bytes.Equal(rfc6962.DefaultHasher.HashLeaf(entryBytes), leafHash) {
return nil, fmt.Errorf("computed leaf hash did not match entry UUID")
// Get Rekor Pub
// TODO(asraa): Replace with sigstore's GetRekorPubs to use TUF.
verifier, err := loadVerifier(rekorClient)
if err != nil {
return nil, err
}

hashes := [][]byte{}
for _, h := range o.Hashes {
hb, _ := hex.DecodeString(h)
hashes = append(hashes, hb)
if err := verify.VerifyLogEntry(ctx, &entry, verifier); err != nil {
return nil, fmt.Errorf("validating entry: %w", err)
}

rootHash, _ := hex.DecodeString(o.RootHash)

if err := proof.VerifyInclusion(rfc6962.DefaultHasher, uint64(o.Index), uint64(o.Size), leafHash, hashes, rootHash); err != nil {
return nil, err
}
return o, err
}),
}
Expand Down

0 comments on commit 5159d01

Please sign in to comment.