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 }