...

Source file src/github.com/sigstore/rekor/pkg/verify/verify.go

Documentation: github.com/sigstore/rekor/pkg/verify

     1  //
     2  // Copyright 2022 The Sigstore Authors.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  package verify
    17  
    18  import (
    19  	"bytes"
    20  	"context"
    21  	"encoding/base64"
    22  	"encoding/hex"
    23  	"encoding/json"
    24  	"errors"
    25  	"fmt"
    26  
    27  	"github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer"
    28  	"github.com/sigstore/rekor/pkg/generated/client"
    29  	"github.com/sigstore/rekor/pkg/generated/client/tlog"
    30  	"github.com/sigstore/rekor/pkg/generated/models"
    31  	"github.com/sigstore/rekor/pkg/util"
    32  	"github.com/sigstore/sigstore/pkg/signature"
    33  	"github.com/sigstore/sigstore/pkg/signature/options"
    34  	"github.com/transparency-dev/merkle/proof"
    35  	"github.com/transparency-dev/merkle/rfc6962"
    36  )
    37  
    38  // ProveConsistency verifies consistency between an initial, trusted STH
    39  // and a second new STH. Callers MUST verify signature on the STHs'.
    40  func ProveConsistency(ctx context.Context, rClient *client.Rekor,
    41  	oldSTH *util.SignedCheckpoint, newSTH *util.SignedCheckpoint, treeID string) error {
    42  	oldTreeSize := int64(oldSTH.Size)
    43  	switch {
    44  	case oldTreeSize == 0:
    45  		return errors.New("consistency proofs can not be computed starting from an empty log")
    46  	case oldTreeSize == int64(newSTH.Size):
    47  		if !bytes.Equal(oldSTH.Hash, newSTH.Hash) {
    48  			return errors.New("old root hash does not match STH hash")
    49  		}
    50  	case oldTreeSize < int64(newSTH.Size):
    51  		consistencyParams := tlog.NewGetLogProofParamsWithContext(ctx)
    52  		consistencyParams.FirstSize = &oldTreeSize      // Root size at the old, or trusted state.
    53  		consistencyParams.LastSize = int64(newSTH.Size) // Root size at the new state to verify against.
    54  		consistencyParams.TreeID = &treeID
    55  		consistencyProof, err := rClient.Tlog.GetLogProof(consistencyParams)
    56  		if err != nil {
    57  			return err
    58  		}
    59  		var hashes [][]byte
    60  		for _, h := range consistencyProof.Payload.Hashes {
    61  			b, err := hex.DecodeString(h)
    62  			if err != nil {
    63  				return errors.New("error decoding consistency proof hashes")
    64  			}
    65  			hashes = append(hashes, b)
    66  		}
    67  		if err := proof.VerifyConsistency(rfc6962.DefaultHasher,
    68  			oldSTH.Size, newSTH.Size, hashes, oldSTH.Hash, newSTH.Hash); err != nil {
    69  			return err
    70  		}
    71  	case oldTreeSize > int64(newSTH.Size):
    72  		return errors.New("inclusion proof returned a tree size larger than the verified tree size")
    73  	}
    74  	return nil
    75  
    76  }
    77  
    78  // VerifyCurrentCheckpoint verifies the provided checkpoint by verifying consistency
    79  // against a newly fetched Checkpoint.
    80  // nolint
    81  func VerifyCurrentCheckpoint(ctx context.Context, rClient *client.Rekor, verifier signature.Verifier,
    82  	oldSTH *util.SignedCheckpoint) (*util.SignedCheckpoint, error) {
    83  	// The oldSTH should already be verified, but check for robustness.
    84  	if !oldSTH.Verify(verifier) {
    85  		return nil, errors.New("signature on old tree head did not verify")
    86  	}
    87  
    88  	// Get and verify against the current STH.
    89  	infoParams := tlog.NewGetLogInfoParamsWithContext(ctx)
    90  	result, err := rClient.Tlog.GetLogInfo(infoParams)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	logInfo := result.GetPayload()
    96  	sth := util.SignedCheckpoint{}
    97  	if err := sth.UnmarshalText([]byte(*logInfo.SignedTreeHead)); err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	// Verify the signature on the SignedCheckpoint.
   102  	if !sth.Verify(verifier) {
   103  		return nil, errors.New("signature on tree head did not verify")
   104  	}
   105  
   106  	// Now verify consistency up to the STH.
   107  	if err := ProveConsistency(ctx, rClient, oldSTH, &sth, *logInfo.TreeID); err != nil {
   108  		return nil, err
   109  	}
   110  	return &sth, nil
   111  }
   112  
   113  // VerifyCheckpointSignature verifies the signature on a checkpoint (signed tree head). It does
   114  // not verify consistency against other checkpoints.
   115  // nolint
   116  func VerifyCheckpointSignature(e *models.LogEntryAnon, verifier signature.Verifier) error {
   117  	sth := &util.SignedCheckpoint{}
   118  	if err := sth.UnmarshalText([]byte(*e.Verification.InclusionProof.Checkpoint)); err != nil {
   119  		return fmt.Errorf("unmarshalling log entry checkpoint to SignedCheckpoint: %w", err)
   120  	}
   121  	if !sth.Verify(verifier) {
   122  		return errors.New("signature on checkpoint did not verify")
   123  	}
   124  	rootHash, err := hex.DecodeString(*e.Verification.InclusionProof.RootHash)
   125  	if err != nil {
   126  		return errors.New("decoding inclusion proof root has")
   127  	}
   128  
   129  	if !bytes.EqualFold(rootHash, sth.Hash) {
   130  		return fmt.Errorf("proof root hash does not match signed tree head, expected %s got %s",
   131  			*e.Verification.InclusionProof.RootHash,
   132  			hex.EncodeToString(sth.Hash))
   133  	}
   134  	return nil
   135  }
   136  
   137  // VerifyInclusion verifies an entry's inclusion proof. Clients MUST either verify
   138  // the root hash against a new STH (via VerifyCurrentCheckpoint) or against a
   139  // trusted, existing STH (via ProveConsistency).
   140  // nolint
   141  func VerifyInclusion(ctx context.Context, e *models.LogEntryAnon) error {
   142  	if e.Verification == nil || e.Verification.InclusionProof == nil {
   143  		return errors.New("inclusion proof not provided")
   144  	}
   145  
   146  	hashes := [][]byte{}
   147  	for _, h := range e.Verification.InclusionProof.Hashes {
   148  		hb, _ := hex.DecodeString(h)
   149  		hashes = append(hashes, hb)
   150  	}
   151  
   152  	rootHash, err := hex.DecodeString(*e.Verification.InclusionProof.RootHash)
   153  	if err != nil {
   154  		return err
   155  	}
   156  
   157  	// Verify the inclusion proof.
   158  	entryBytes, err := base64.StdEncoding.DecodeString(e.Body.(string))
   159  	if err != nil {
   160  		return err
   161  	}
   162  	leafHash := rfc6962.DefaultHasher.HashLeaf(entryBytes)
   163  
   164  	if err := proof.VerifyInclusion(rfc6962.DefaultHasher, uint64(*e.Verification.InclusionProof.LogIndex),
   165  		uint64(*e.Verification.InclusionProof.TreeSize), leafHash, hashes, rootHash); err != nil {
   166  		return err
   167  	}
   168  
   169  	return nil
   170  }
   171  
   172  // VerifySignedEntryTimestamp verifies the entry's SET against the provided
   173  // public key.
   174  // nolint
   175  func VerifySignedEntryTimestamp(ctx context.Context, e *models.LogEntryAnon, verifier signature.Verifier) error {
   176  	if e.Verification == nil {
   177  		return errors.New("missing verification")
   178  	}
   179  	if e.Verification.SignedEntryTimestamp == nil {
   180  		return errors.New("signature missing")
   181  	}
   182  
   183  	type bundle struct {
   184  		Body           interface{} `json:"body"`
   185  		IntegratedTime int64       `json:"integratedTime"`
   186  		// Note that this is the virtual index.
   187  		LogIndex int64  `json:"logIndex"`
   188  		LogID    string `json:"logID"`
   189  	}
   190  	bundlePayload := bundle{
   191  		Body:           e.Body,
   192  		IntegratedTime: *e.IntegratedTime,
   193  		LogIndex:       *e.LogIndex,
   194  		LogID:          *e.LogID,
   195  	}
   196  	contents, err := json.Marshal(bundlePayload)
   197  	if err != nil {
   198  		return fmt.Errorf("marshaling bundle: %w", err)
   199  	}
   200  	canonicalized, err := jsoncanonicalizer.Transform(contents)
   201  	if err != nil {
   202  		return fmt.Errorf("canonicalizing bundle: %w", err)
   203  	}
   204  
   205  	// verify the SET against the public key
   206  	if err := verifier.VerifySignature(bytes.NewReader(e.Verification.SignedEntryTimestamp),
   207  		bytes.NewReader(canonicalized), options.WithContext(ctx)); err != nil {
   208  		return fmt.Errorf("unable to verify bundle: %w", err)
   209  	}
   210  	return nil
   211  }
   212  
   213  // VerifyLogEntry performs verification of a LogEntry given a Rekor verifier.
   214  // Performs inclusion proof verification up to a verified root hash,
   215  // SignedEntryTimestamp verification, and checkpoint verification.
   216  // nolint
   217  func VerifyLogEntry(ctx context.Context, e *models.LogEntryAnon, verifier signature.Verifier) error {
   218  	// Verify the inclusion proof using the body's leaf hash.
   219  	if err := VerifyInclusion(ctx, e); err != nil {
   220  		return err
   221  	}
   222  
   223  	// Verify checkpoint, which includes a signed root hash.
   224  	if err := VerifyCheckpointSignature(e, verifier); err != nil {
   225  		return err
   226  	}
   227  
   228  	// Verify the Signed Entry Timestamp.
   229  	if err := VerifySignedEntryTimestamp(ctx, e, verifier); err != nil {
   230  		return err
   231  	}
   232  
   233  	return nil
   234  }
   235  

View as plain text