github.com/google/trillian-examples@v0.0.0-20240520080811-0d40d35cef0e/binary_transparency/firmware/internal/client/mapclient.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 "context" 19 "crypto/sha512" 20 "encoding/base64" 21 "encoding/json" 22 "fmt" 23 "io" 24 "net/http" 25 "net/url" 26 27 "github.com/golang/glog" 28 "github.com/google/trillian-examples/binary_transparency/firmware/api" 29 "github.com/google/trillian/merkle/coniks" 30 "github.com/google/trillian/merkle/smt" 31 "github.com/google/trillian/merkle/smt/node" 32 "golang.org/x/sync/errgroup" 33 ) 34 35 // MapClient is a client that exposes the operations on a remote map. 36 type MapClient struct { 37 mapURL *url.URL 38 } 39 40 // NewMapClient creates a MapClient for a map hosted at the given URL. 41 func NewMapClient(mapURL string) (*MapClient, error) { 42 u, err := url.Parse(mapURL) 43 if err != nil { 44 return nil, err 45 } 46 return &MapClient{ 47 mapURL: u, 48 }, nil 49 } 50 51 // MapCheckpoint returns the Checkpoint for the latest map revision. 52 // This map root needs to be taken on trust that it isn't forked etc. 53 // To remove this trust, the map roots should be stored in a log, and 54 // this would further return: 55 // * A Log Checkpoint for the MapCheckpointLog 56 // * An inclusion proof for this checkpoint within it 57 func (c *MapClient) MapCheckpoint() (api.MapCheckpoint, error) { 58 mcp := api.MapCheckpoint{} 59 u, err := c.mapURL.Parse(api.MapHTTPGetCheckpoint) 60 if err != nil { 61 return mcp, err 62 } 63 r, err := http.Get(u.String()) 64 if err != nil { 65 return mcp, err 66 } 67 if r.StatusCode != http.StatusOK { 68 return mcp, errFromResponse("failed to fetch checkpoint", r) 69 } 70 71 if err := json.NewDecoder(r.Body).Decode(&mcp); err != nil { 72 return mcp, err 73 } 74 // TODO(mhutchinson): Check signature 75 return mcp, nil 76 } 77 78 // Aggregation returns the value committed to by the map under the given key, 79 // with an inclusion proof. 80 func (c *MapClient) Aggregation(ctx context.Context, rev uint64, fwIndex uint64) ([]byte, api.MapInclusionProof, error) { 81 errs, _ := errgroup.WithContext(ctx) 82 // Simultaneously fetch all tiles: 83 tiles := make([]api.MapTile, api.MapPrefixStrata+1) 84 kbs := sha512.Sum512_256([]byte(fmt.Sprintf("summary:%d", fwIndex))) 85 for i := range tiles { 86 i := i 87 errs.Go(func() error { 88 path := kbs[:i] 89 var t api.MapTile 90 tbs, err := c.fetch(fmt.Sprintf("%s/in-revision/%d/at-path/%s", api.MapHTTPGetTile, rev, base64.URLEncoding.EncodeToString(path))) 91 if err != nil { 92 return err 93 } 94 if err := json.Unmarshal(tbs, &t); err != nil { 95 return err 96 } 97 tiles[i] = t 98 return nil 99 }) 100 } 101 102 var agg []byte 103 errs.Go(func() error { 104 var err error 105 agg, err = c.fetch(fmt.Sprintf("%s/in-revision/%d/for-firmware-at-index/%d", api.MapHTTPGetAggregation, rev, fwIndex)) 106 return err 107 }) 108 109 if err := errs.Wait(); err != nil { 110 return nil, api.MapInclusionProof{}, err 111 } 112 113 ipt := newInclusionProofTree(api.MapTreeID, coniks.Default, kbs[:]) 114 for i := api.MapPrefixStrata; i >= 0; i-- { 115 tile := tiles[i] 116 nodes := make([]smt.Node, len(tile.Leaves)) 117 for j, l := range tile.Leaves { 118 nodes[j] = toNode(tile.Path, l) 119 } 120 hs, err := smt.NewHStar3(nodes, ipt.hasher.HashChildren, 121 uint(len(tile.Path)+len(tile.Leaves[0].Path))*8, uint(len(tile.Path))*8) 122 if err != nil { 123 return agg, api.MapInclusionProof{}, fmt.Errorf("failed to create HStar3 for tile %x: %v", tile.Path, err) 124 } 125 res, err := hs.Update(ipt) 126 if err != nil { 127 return agg, api.MapInclusionProof{}, fmt.Errorf("failed to hash tile %x: %v", tile.Path, err) 128 } else if got, want := len(res), 1; got != want { 129 return agg, api.MapInclusionProof{}, fmt.Errorf("wrong number of roots for tile %x: got %v, want %v", tile.Path, got, want) 130 } 131 } 132 133 return agg, *ipt.proof, nil 134 } 135 136 // fetch gets the body from the given path. 137 func (c *MapClient) fetch(path string) ([]byte, error) { 138 u, err := c.mapURL.Parse(path) 139 if err != nil { 140 return nil, err 141 } 142 r, err := http.Get(u.String()) 143 if err != nil { 144 return nil, err 145 } 146 if r.StatusCode != http.StatusOK { 147 return nil, errFromResponse(fmt.Sprintf("failed to fetch %s", path), r) 148 } 149 body := r.Body 150 defer func() { 151 if err := body.Close(); err != nil { 152 glog.Errorf("body.Close(): %v", err) 153 } 154 }() 155 return io.ReadAll(body) 156 } 157 158 // toNode converts a MapTileLeaf into the equivalent Node for HStar3. 159 func toNode(prefix []byte, l api.MapTileLeaf) smt.Node { 160 path := make([]byte, 0, len(prefix)+len(l.Path)) 161 path = append(append(path, prefix...), l.Path...) 162 return smt.Node{ 163 ID: node.NewID(string(path), uint(len(path))*8), 164 Hash: l.Hash, 165 } 166 } 167 168 // inclusionProofTree is a NodeAccessor for an empty tree with the given ID. 169 // As values are set on the tree, an inclusion proof is generated containing 170 // the siblings computed. 171 type inclusionProofTree struct { 172 treeID int64 173 hasher *coniks.Hasher 174 target node.ID 175 proof *api.MapInclusionProof 176 } 177 178 func newInclusionProofTree(treeID int64, hasher *coniks.Hasher, target []byte) inclusionProofTree { 179 return inclusionProofTree{ 180 treeID: treeID, 181 hasher: hasher, 182 target: node.NewID(string(target), 256), 183 proof: &api.MapInclusionProof{ 184 Key: target, 185 Proof: make([][]byte, 256), 186 }, 187 } 188 } 189 190 func (e inclusionProofTree) Get(id node.ID) ([]byte, error) { 191 return e.hasher.HashEmpty(e.treeID, id), nil 192 } 193 194 func (e inclusionProofTree) Set(id node.ID, hash []byte) { 195 if id == e.target { 196 e.proof.Value = hash 197 glog.V(2).Infof("inclusionProofTree: set value for target: %x", hash) 198 return 199 } 200 stem := e.target.Prefix(id.BitLen()) 201 if stem == id.Sibling() { 202 e.proof.Proof[id.BitLen()-1] = hash 203 glog.V(2).Infof("inclusionProofTree: set sibling at depth %d: %x", id.BitLen(), hash) 204 } 205 }