xref: /aosp_15_r20/external/avb/tools/transparency/verify/internal/tiles/reader.go (revision d289c2ba6de359471b23d594623b906876bc48a0)
1// Package tiles contains methods to work with tlog based verifiable logs.
2package tiles
3
4import (
5	"crypto/sha256"
6	"fmt"
7	"io"
8	"net/http"
9	"net/url"
10	"path"
11	"strconv"
12	"strings"
13
14	"golang.org/x/mod/sumdb/tlog"
15)
16
17// HashReader implements tlog.HashReader, reading from tlog-based log located at
18// URL.
19type HashReader struct {
20	URL string
21}
22
23// Domain separation prefix for Merkle tree hashing with second preimage
24// resistance similar to that used in RFC 6962.
25const (
26	leafHashPrefix = 0
27)
28
29// ReadHashes implements tlog.HashReader's ReadHashes.
30// See: https://pkg.go.dev/golang.org/x/mod/sumdb/tlog#HashReader.
31func (h HashReader) ReadHashes(indices []int64) ([]tlog.Hash, error) {
32	tiles := make(map[string][]byte)
33	hashes := make([]tlog.Hash, 0, len(indices))
34	for _, index := range indices {
35		// The PixelBT log is tiled at height = 1.
36		tile := tlog.TileForIndex(1, index)
37
38		var content []byte
39		var exists bool
40		var err error
41		content, exists = tiles[tile.Path()]
42		if !exists {
43			content, err = readFromURL(h.URL, tile.Path())
44			if err != nil {
45				return nil, fmt.Errorf("failed to read from %s: %v", tile.Path(), err)
46			}
47			tiles[tile.Path()] = content
48		}
49
50		hash, err := tlog.HashFromTile(tile, content, index)
51		if err != nil {
52			return nil, fmt.Errorf("failed to read data from tile for index %d: %v", index, err)
53		}
54		hashes = append(hashes, hash)
55	}
56	return hashes, nil
57}
58
59// BinaryInfosIndex returns a map from payload to its index in the
60// transparency log according to the `binaryInfoFilename` value.
61func BinaryInfosIndex(logBaseURL string, binaryInfoFilename string) (map[string]int64, error) {
62	b, err := readFromURL(logBaseURL, binaryInfoFilename)
63	if err != nil {
64		return nil, err
65	}
66
67	binaryInfos := string(b)
68	return parseBinaryInfosIndex(binaryInfos, binaryInfoFilename)
69}
70
71func parseBinaryInfosIndex(binaryInfos string, binaryInfoFilename string) (map[string]int64, error) {
72	m := make(map[string]int64)
73
74	infosStr := strings.Split(binaryInfos, "\n\n")
75	for _, infoStr := range infosStr {
76		pieces := strings.SplitN(infoStr, "\n", 2)
77		if len(pieces) != 2 {
78			return nil, fmt.Errorf("missing newline, malformed %s", binaryInfoFilename)
79		}
80
81		idx, err := strconv.ParseInt(pieces[0], 10, 64)
82		if err != nil {
83			return nil, fmt.Errorf("failed to convert %q to int64", pieces[0])
84		}
85
86		// Ensure that each log entry does not have extraneous whitespace, but
87		// also terminates with a newline.
88		logEntry := strings.TrimSpace(pieces[1]) + "\n"
89		m[logEntry] = idx
90	}
91
92	return m, nil
93}
94
95func readFromURL(base, suffix string) ([]byte, error) {
96	u, err := url.Parse(base)
97	if err != nil {
98		return nil, fmt.Errorf("invalid URL %s: %v", base, err)
99	}
100	u.Path = path.Join(u.Path, suffix)
101
102	resp, err := http.Get(u.String())
103	if err != nil {
104		return nil, fmt.Errorf("http.Get(%s): %v", u.String(), err)
105	}
106	defer resp.Body.Close()
107	if code := resp.StatusCode; code != 200 {
108		return nil, fmt.Errorf("http.Get(%s): %s", u.String(), http.StatusText(code))
109	}
110
111	return io.ReadAll(resp.Body)
112}
113
114// PayloadHash returns the hash of the payload.
115func PayloadHash(p []byte) (tlog.Hash, error) {
116	l := append([]byte{leafHashPrefix}, p...)
117	h := sha256.Sum256(l)
118
119	var hash tlog.Hash
120	copy(hash[:], h[:])
121	return hash, nil
122}
123