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

feat: add verification functions #986

Merged
merged 5 commits into from Aug 30, 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
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