Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions verifier_tools/verify/cmd/verifier/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const (
KeyNameForVerifierG1PAPK = "gstatic.com/android/binary_transparency/google1p/apk/2026/0"
LogBaseURLPixel = "https://developers.google.com/android/binary_transparency"
LogBaseURLG1PJWT = "https://developers.google.com/android/binary_transparency/google1p"
LogBaseURLG1PAPK = "https://www.gstatic.com/android/binary_transparency/google1p/apk/2026/01/"
LogBaseURLG1PAPK = "https://www.gstatic.com/android/binary_transparency/google1p/apk/2026/01"
ImageInfoFilename = "image_info.txt"
PackageInfoFilename = "package_info.txt"
)
Expand Down Expand Up @@ -127,7 +127,9 @@ func main() {
os.Exit(1)
}

m, err := tiles.BinaryInfosIndex(logBaseURL, binaryInfoFilename)
logSize := int64(root.Size)

m, err := tiles.BinaryInfosIndex(logBaseURL, binaryInfoFilename, logSize)
if err != nil {
slog.Error("failed to load binary info map to find log index", "error", err)
os.Exit(1)
Expand All @@ -141,7 +143,6 @@ func main() {
var th tlog.Hash
copy(th[:], root.Hash)

logSize := int64(root.Size)
r := tiles.HashReader{URL: logBaseURL, TileHeight: tileHeight, TreeSize: logSize}
slog.Debug("tlog.ProveRecord", "logSize", logSize, "binaryInfoIndex", binaryInfoIndex)
rp, err := tlog.ProveRecord(logSize, binaryInfoIndex, r)
Expand Down
114 changes: 112 additions & 2 deletions verifier_tools/verify/internal/tiles/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ import (
"log/slog"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"time"

"golang.org/x/mod/sumdb/tlog"
)
Expand Down Expand Up @@ -96,8 +99,8 @@ func (h HashReader) ReadHashes(indices []int64) ([]tlog.Hash, error) {

// BinaryInfosIndex returns a map from payload to its index in the
// transparency log according to the `binaryInfoFilename` value.
func BinaryInfosIndex(logBaseURL string, binaryInfoFilename string) (map[string]int64, error) {
b, err := readFromURL(logBaseURL, binaryInfoFilename)
func BinaryInfosIndex(logBaseURL string, binaryInfoFilename string, treeSize int64) (map[string]int64, error) {
b, err := readCachedInfoFile(logBaseURL, binaryInfoFilename, treeSize)
if err != nil {
return nil, err
}
Expand All @@ -106,6 +109,113 @@ func BinaryInfosIndex(logBaseURL string, binaryInfoFilename string) (map[string]
return parseBinaryInfosIndex(binaryInfos, binaryInfoFilename)
}

func readCachedInfoFile(logBaseURL string, binaryInfoFilename string, treeSize int64) ([]byte, error) {
cacheDir, err := os.UserCacheDir()
if err != nil {
slog.Warn("Failed to get user cache dir, falling back to direct download", "error", err)
return readFromURL(logBaseURL, binaryInfoFilename)
}

abtCacheDir := filepath.Join(cacheDir, "android-binary-transparency")
if err := os.MkdirAll(abtCacheDir, 0755); err != nil {
slog.Warn("Failed to create cache dir, falling back to direct download", "error", err)
return readFromURL(logBaseURL, binaryInfoFilename)
}

urlHash := sha256.Sum256([]byte(logBaseURL))
basePrefix := fmt.Sprintf("%x_%s", urlHash[:8], binaryInfoFilename)
cacheFilename := fmt.Sprintf("%s_%d", basePrefix, treeSize)
cachePath := filepath.Join(abtCacheDir, cacheFilename)
Comment thread
billy-lau marked this conversation as resolved.

// Try reading from cache
if b, err := os.ReadFile(cachePath); err == nil {
slog.Debug("Loaded info file from local cache", "path", cachePath)
return b, nil
}

// Cache miss, download from URL
slog.Info("Downloading new info file", "url", logBaseURL+"/"+binaryInfoFilename)
b, err := readFromURL(logBaseURL, binaryInfoFilename)
if err != nil {
return nil, err
}

// Save to cache atomically
tmpFile, err := os.CreateTemp(abtCacheDir, cacheFilename+".*.tmp")
if err != nil {
slog.Warn("Failed to create cache tmp file", "error", err)
return b, nil
}
tmpPath := tmpFile.Name()

// Clean up tmp file on exit if it hasn't been renamed
defer os.Remove(tmpPath)

slog.Info("Writing info file to cache", "path", tmpPath)
if _, err := tmpFile.Write(b); err != nil {
slog.Warn("Failed to write to cache tmp file", "error", err)
tmpFile.Close()
return b, nil
}
if err := tmpFile.Close(); err != nil {
slog.Warn("Failed to close cache tmp file", "error", err)
return b, nil
}

slog.Info("Renaming cache file", "from", tmpPath, "to", cachePath)
if err := os.Rename(tmpPath, cachePath); err != nil {
slog.Warn("Failed to move cache file to final destination", "error", err)
return b, nil
}

slog.Debug("Saved info file to local cache", "path", cachePath)

// Cleanup old cache files for this specific log URL and filename safely
slog.Info("Cleaning up old cache files", "prefix", basePrefix)
if entries, err := os.ReadDir(abtCacheDir); err == nil {
for _, entry := range entries {
if entry.IsDir() {
continue
}

// Only process files that match our specific basePrefix
if !strings.HasPrefix(entry.Name(), basePrefix+"_") {
continue
}

f := filepath.Join(abtCacheDir, entry.Name())
if f == cachePath {
continue
}

info, err := entry.Info()
if err != nil {
continue
}

// Delete old temp files (older than 1 hour) left over from hard crashes.
// Otherwise, keep current temp files to avoid breaking active concurrent downloads.
if strings.HasSuffix(entry.Name(), ".tmp") {
if time.Since(info.ModTime()) > time.Hour {
if err := os.Remove(f); err == nil {
slog.Debug("Cleaned up orphaned cache temp file", "path", f)
}
}
continue
}

// Delete old cache files (older than 24 hours to prevent cache invalidation storms)
if time.Since(info.ModTime()) > 24*time.Hour {
if err := os.Remove(f); err == nil {
slog.Debug("Cleaned up old cache file", "path", f)
}
}
}
}

return b, nil
}

func parseBinaryInfosIndex(binaryInfos string, binaryInfoFilename string) (map[string]int64, error) {
m := make(map[string]int64)

Expand Down
4 changes: 2 additions & 2 deletions verifier_tools/verify/internal/tiles/reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func TestReadHashesWithReadTileData(t *testing.T) {
},
} {
t.Run(tc.desc, func(t *testing.T) {
r := HashReader{URL: s.URL}
r := HashReader{URL: s.URL, TileHeight: tileHeight, TreeSize: int64(tc.size)}

// Read hashes.
for i, want := range tc.want {
Expand All @@ -116,7 +116,7 @@ func TestReadHashesCachedTile(t *testing.T) {
defer s.Close()

wantHash := nodeHashes[0][0]
r := HashReader{URL: s.URL}
r := HashReader{URL: s.URL, TileHeight: tileHeight, TreeSize: 3}

// Read hash at index 0 twice, to exercise the caching of tiles.
// On the first pass, the read is fresh and readFromURL is called.
Expand Down