github.com/google/trillian-examples@v0.0.0-20240520080811-0d40d35cef0e/binary_transparency/firmware/internal/client/follower.go (about)

     1  // Copyright 2021 Google LLC. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package client
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"encoding/json"
    21  	"fmt"
    22  	"time"
    23  
    24  	"github.com/golang/glog"
    25  	"github.com/google/trillian-examples/binary_transparency/firmware/api"
    26  	"github.com/google/trillian-examples/binary_transparency/firmware/internal/crypto"
    27  	"github.com/transparency-dev/merkle"
    28  	"github.com/transparency-dev/merkle/proof"
    29  	"github.com/transparency-dev/merkle/rfc6962"
    30  )
    31  
    32  // ErrConsistency is returned if two logs roots are found which are inconsistent.
    33  // This allows a motivated client to extract the checkpoints to provide as evidence
    34  // if needed.
    35  type ErrConsistency struct {
    36  	Golden, Latest api.LogCheckpoint
    37  }
    38  
    39  func (e ErrConsistency) Error() string {
    40  	return fmt.Sprintf("failed to verify consistency proof from %s to %s", e.Golden, e.Latest)
    41  }
    42  
    43  // ErrInclusion is returned if a proof of inclusion does not validate.
    44  // This allows a motivated client to provide evidence if needed.
    45  type ErrInclusion struct {
    46  	Checkpoint api.LogCheckpoint
    47  	Proof      api.InclusionProof
    48  }
    49  
    50  func (e ErrInclusion) Error() string {
    51  	return fmt.Sprintf("failed to verify inclusion proof %s in root %s", e.Proof, e.Checkpoint)
    52  }
    53  
    54  // LogEntry wraps up a leaf value with its position in the log.
    55  type LogEntry struct {
    56  	Root  api.LogCheckpoint
    57  	Index uint64
    58  	Value api.SignedStatement
    59  }
    60  
    61  // LogFollower follows a log for new data becoming available.
    62  type LogFollower struct {
    63  	c ReadonlyClient
    64  	h merkle.LogHasher
    65  }
    66  
    67  // NewLogFollower creates a LogFollower that uses the given client.
    68  func NewLogFollower(c ReadonlyClient) LogFollower {
    69  	return LogFollower{
    70  		c: c,
    71  		h: rfc6962.DefaultHasher,
    72  	}
    73  }
    74  
    75  // Checkpoints polls the log according to the configured interval, returning roots consistent
    76  // with the current golden checkpoint. Should any valid roots be found which are inconsistent
    77  // then an error is returned. The log being unavailable will just cause a retry, giving it
    78  // the benefit of the doubt.
    79  func (f *LogFollower) Checkpoints(ctx context.Context, pollInterval time.Duration, golden api.LogCheckpoint) (<-chan api.LogCheckpoint, <-chan error) {
    80  	ticker := time.NewTicker(pollInterval)
    81  
    82  	outc := make(chan api.LogCheckpoint, 1)
    83  	errc := make(chan error, 1)
    84  
    85  	// Push the starting checkpoint into the channel here to kick off the pipeline
    86  	outc <- golden
    87  
    88  	go func() {
    89  		defer close(outc)
    90  		// Now keep looking for newer, consistent checkpoints
    91  		for {
    92  			select {
    93  			case <-ticker.C:
    94  				// Wait until the next tick.
    95  			case <-ctx.Done():
    96  				errc <- ctx.Err()
    97  				return
    98  			}
    99  
   100  			cp, err := f.c.GetCheckpoint()
   101  			if err != nil {
   102  				glog.Warningf("Failed to get latest Checkpoint: %q", err)
   103  				continue
   104  			}
   105  
   106  			if cp.Size <= golden.Size {
   107  				continue
   108  			}
   109  			glog.V(1).Infof("Got newer checkpoint %s", cp)
   110  
   111  			// Perform consistency check only for non-zero initial tree size
   112  			if golden.Size != 0 {
   113  				consistency, err := f.c.GetConsistencyProof(api.GetConsistencyRequest{From: golden.Size, To: cp.Size})
   114  				if err != nil {
   115  					glog.Warningf("Failed to fetch the Consistency: %q", err)
   116  					continue
   117  				}
   118  				glog.V(1).Infof("Printing the latest Consistency Proof Information")
   119  				glog.V(1).Infof("Consistency Proof = %x", consistency.Proof)
   120  
   121  				// Verify the fetched consistency proof
   122  				if err := proof.VerifyConsistency(f.h, golden.Size, cp.Size, consistency.Proof, golden.Hash, cp.Hash); err != nil {
   123  					errc <- ErrConsistency{
   124  						Golden: golden,
   125  						Latest: *cp,
   126  					}
   127  					return
   128  				}
   129  				glog.V(1).Infof("Consistency proof for Treesize %d verified", cp.Size)
   130  			}
   131  			golden = *cp
   132  			outc <- *cp
   133  		}
   134  	}()
   135  	return outc, errc
   136  }
   137  
   138  // Entries follows the log to output all of the leaves starting from the head index provided.
   139  // This is intended to be set up to consume the output of #Checkpoints(), and will output new
   140  // entries each time a Checkpoint becomes available which is larger than the current head.
   141  // The input channel should be closed in order to clean up the resources used by this method.
   142  func (f *LogFollower) Entries(ctx context.Context, cpc <-chan api.LogCheckpoint, head uint64) (<-chan LogEntry, <-chan error) {
   143  	outc := make(chan LogEntry, 1)
   144  	errc := make(chan error, 1)
   145  
   146  	go func() {
   147  		defer close(outc)
   148  		for cp := range cpc {
   149  			for ; head < cp.Size; head++ {
   150  				ep, err := f.c.GetManifestEntryAndProof(api.GetFirmwareManifestRequest{Index: head, TreeSize: cp.Size})
   151  				if err != nil {
   152  					glog.Warningf("Failed to fetch the Manifest: %q", err)
   153  					continue
   154  				}
   155  				lh := f.h.HashLeaf(ep.Value)
   156  				if err := proof.VerifyInclusion(f.h, ep.LeafIndex, cp.Size, lh, ep.Proof, cp.Hash); err != nil {
   157  					errc <- ErrInclusion{
   158  						Checkpoint: cp,
   159  						Proof:      *ep,
   160  					}
   161  					return
   162  				}
   163  				glog.V(1).Infof("Inclusion proof for leafhash 0x%x verified", lh)
   164  
   165  				statement := ep.Value
   166  				stmt := api.SignedStatement{}
   167  				if err := json.NewDecoder(bytes.NewReader(statement)).Decode(&stmt); err != nil {
   168  					errc <- fmt.Errorf("failed to decode SignedStatement: %q", err)
   169  					return
   170  				}
   171  
   172  				claimant, err := crypto.ClaimantForType(stmt.Type)
   173  				if err != nil {
   174  					errc <- err
   175  					return
   176  				}
   177  				// Verify the signature:
   178  				if err := claimant.VerifySignature(stmt.Type, stmt.Statement, stmt.Signature); err != nil {
   179  					errc <- fmt.Errorf("failed to verify signature: %q", err)
   180  					return
   181  				}
   182  				outc <- LogEntry{
   183  					Root:  cp,
   184  					Index: head,
   185  					Value: stmt,
   186  				}
   187  			}
   188  		}
   189  	}()
   190  	return outc, errc
   191  }