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  }