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
26 changes: 22 additions & 4 deletions verifier_tools/verify/cmd/verifier/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,12 @@ const (
LeafHashPrefix = 0
KeyNameForVerifierPixel = "pixel_transparency_log"
KeyNameForVerifierG1PJWT = "developers.google.com/android/binary_transparency/google1p/0"
KeyNameForVerifierG1PJWT202601 = "gstatic.com/android/binary_transparency/google1p/jwt/0"
KeyNameForVerifierG1PAPK = "gstatic.com/android/binary_transparency/google1p/apk/2026/0"
KeyNameForVerifierMainlineModule = "gstatic.com/android/binary_transparency/mainline/modules/2026/0"
LogBaseURLPixel = "https://developers.google.com/android/binary_transparency"
LogBaseURLG1PJWT = "https://developers.google.com/android/binary_transparency/google1p"
LogBaseURLG1PJWT202601 = "https://www.gstatic.com/android/binary_transparency/google1p/jwt/2026/01"
LogBaseURLG1PAPK202601 = "https://www.gstatic.com/android/binary_transparency/google1p/apk/2026/01"
LogBaseURLG1PAPK202602 = "https://www.gstatic.com/android/binary_transparency/google1p/apk/2026/02"
NoteVerifierG1PAPK202602 = "android.transparency.goog/google1p/apk/2026/1+fc654374+ATr9NQE0gvOtVfj5cCStUzdlflEp3oZoNHD8pImzPj5O"
Expand Down Expand Up @@ -142,15 +144,31 @@ func resolveTargets(logType string) ([]logTarget, error) {
binaryInfoFilenames: []string{ImageInfoFilename},
})
case "google_1p_code":
v, err := checkpoint.NewVerifier(googleSystemAppLogPubKey, KeyNameForVerifierG1PJWT)
// Shard 2026/01: Latest sharded log
v202601, err := checkpoint.NewVerifier(googleSystemAppLogPubKey, KeyNameForVerifierG1PJWT202601)
if err != nil {
return nil, fmt.Errorf("error creating verifier for google_1p_code log: %w", err)
return nil, fmt.Errorf("error creating verifier for 2026/01 google_1p_code log: %w", err)
}
targets = append(targets, logTarget{
name: "google_1p_code",
name: "google_1p_code (2026/01)",
baseURL: LogBaseURLG1PJWT202601,
checkpointPath: "checkpoint.txt",
verifier: v202601,
tileHeight: 8,
isTessera: false,
binaryInfoFilenames: []string{PackageInfoFilename},
})

// Legacy log continuation fallback
vLegacy, err := checkpoint.NewVerifier(googleSystemAppLogPubKey, KeyNameForVerifierG1PJWT)
if err != nil {
return nil, fmt.Errorf("error creating verifier for legacy google_1p_code log: %w", err)
}
targets = append(targets, logTarget{
name: "google_1p_code (legacy)",
baseURL: LogBaseURLG1PJWT,
checkpointPath: "checkpoint.txt",
verifier: v,
verifier: vLegacy,
tileHeight: 1,
isTessera: false,
binaryInfoFilenames: []string{PackageInfoFilename},
Expand Down
132 changes: 132 additions & 0 deletions verifier_tools/verify/cmd/verifier/verifier_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package main

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/android/android-binary-transparency/verifier_tools/verify/internal/checkpoint"
)

func TestResolveTargetsGoogle1PCode(t *testing.T) {
targets, err := resolveTargets("google_1p_code")
if err != nil {
t.Fatalf("resolveTargets(\"google_1p_code\") returned unexpected error: %v", err)
}

if len(targets) != 2 {
t.Fatalf("got %d targets for google_1p_code, want 2", len(targets))
}

// Target 0: 2026/01 shard (primary, tileHeight 8)
t0 := targets[0]
if t0.name != "google_1p_code (2026/01)" {
t.Errorf("targets[0].name = %q, want %q", t0.name, "google_1p_code (2026/01)")
}
if t0.baseURL != LogBaseURLG1PJWT202601 {
t.Errorf("targets[0].baseURL = %q, want %q", t0.baseURL, LogBaseURLG1PJWT202601)
}
if t0.checkpointPath != "checkpoint.txt" {
t.Errorf("targets[0].checkpointPath = %q, want %q", t0.checkpointPath, "checkpoint.txt")
}
if t0.verifier.Name() != KeyNameForVerifierG1PJWT202601 {
t.Errorf("targets[0].verifier.Name() = %q, want %q", t0.verifier.Name(), KeyNameForVerifierG1PJWT202601)
}
if t0.tileHeight != 8 {
t.Errorf("targets[0].tileHeight = %d, want 8", t0.tileHeight)
}
if t0.isTessera {
t.Errorf("targets[0].isTessera = true, want false")
}
if len(t0.binaryInfoFilenames) != 1 || t0.binaryInfoFilenames[0] != PackageInfoFilename {
t.Errorf("targets[0].binaryInfoFilenames = %v, want [%q]", t0.binaryInfoFilenames, PackageInfoFilename)
}

// Target 1: legacy developers.google.com shard (fallback, tileHeight 1)
t1 := targets[1]
if t1.name != "google_1p_code (legacy)" {
t.Errorf("targets[1].name = %q, want %q", t1.name, "google_1p_code (legacy)")
}
if t1.baseURL != LogBaseURLG1PJWT {
t.Errorf("targets[1].baseURL = %q, want %q", t1.baseURL, LogBaseURLG1PJWT)
}
if t1.checkpointPath != "checkpoint.txt" {
t.Errorf("targets[1].checkpointPath = %q, want %q", t1.checkpointPath, "checkpoint.txt")
}
if t1.verifier.Name() != KeyNameForVerifierG1PJWT {
t.Errorf("targets[1].verifier.Name() = %q, want %q", t1.verifier.Name(), KeyNameForVerifierG1PJWT)
}
if t1.tileHeight != 1 {
t.Errorf("targets[1].tileHeight = %d, want 1", t1.tileHeight)
}
if t1.isTessera {
t.Errorf("targets[1].isTessera = true, want false")
}
if len(t1.binaryInfoFilenames) != 1 || t1.binaryInfoFilenames[0] != PackageInfoFilename {
t.Errorf("targets[1].binaryInfoFilenames = %v, want [%q]", t1.binaryInfoFilenames, PackageInfoFilename)
}

// Both targets use the same embedded public key so their 4-byte key hashes must match.
if t0.verifier.KeyHash() != t1.verifier.KeyHash() {
t.Errorf("expected identical key hash for both google_1p_code targets: got %x vs %x",
t0.verifier.KeyHash(), t1.verifier.KeyHash())
}
}

func TestGoogle1PCodeCheckpointSignatureVerification(t *testing.T) {
// Real signed checkpoints from the 2026/01 shard and the legacy log.
const signedCheckpoint202601 = "gstatic.com/android/binary_transparency/google1p/jwt/0\n" +
"234\n" +
"HpW7vHFFioFiMf0IglK1B3MLk80iaGOC6Ud6Etq038U=\n" +
"\n" +
"— gstatic.com/android/binary_transparency/google1p/jwt/0 qsuhszBFAiEAomG6In9+okg+Pj1Jw4JpWfignNeNXQweJxoYf9q59GMCIEyy/Ebu096WrCsT9L0Dv5D1EBHzqNQ26A1XcnNESPKX\n"

const signedCheckpointLegacy = "developers.google.com/android/binary_transparency/google1p/0\n" +
"134\n" +
"WddUpZSJPJPm93SLwoCdKv+oJqPEqie52TZTIVNOhok=\n" +
"\n" +
"— developers.google.com/android/binary_transparency/google1p/0 qsuhszBEAiBA1NO/xnL4++iXFVwPhRsNU6AWDPEtOvDJQL3OuqCBOwIgbyMsA1l2yLvPUq8CoMNBf4E88l4XjlW4YanDLn5HRRI=\n"

targets, err := resolveTargets("google_1p_code")
if err != nil {
t.Fatalf("resolveTargets(\"google_1p_code\") failed: %v", err)
}

s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/2026/01/checkpoint.txt":
w.Write([]byte(signedCheckpoint202601))
case "/legacy/checkpoint.txt":
w.Write([]byte(signedCheckpointLegacy))
default:
http.NotFound(w, r)
}
}))
defer s.Close()

// Verify 2026/01 checkpoint with targets[0].verifier
root202601, err := checkpoint.FromURLWithPath(s.URL+"/2026/01", "checkpoint.txt", targets[0].verifier)
if err != nil {
t.Fatalf("failed to verify 2026/01 checkpoint with targets[0].verifier: %v", err)
}
if root202601.Size != 234 {
t.Errorf("root202601.Size = %d, want 234", root202601.Size)
}

// Verify legacy checkpoint with targets[1].verifier
rootLegacy, err := checkpoint.FromURLWithPath(s.URL+"/legacy", "checkpoint.txt", targets[1].verifier)
if err != nil {
t.Fatalf("failed to verify legacy checkpoint with targets[1].verifier: %v", err)
}
if rootLegacy.Size != 134 {
t.Errorf("rootLegacy.Size = %d, want 134", rootLegacy.Size)
}

// Cross-check: targets[0].verifier must reject legacy checkpoint and vice versa
if _, err := checkpoint.FromURLWithPath(s.URL+"/legacy", "checkpoint.txt", targets[0].verifier); err == nil {
t.Errorf("expected targets[0].verifier to reject legacy checkpoint, got nil")
}
if _, err := checkpoint.FromURLWithPath(s.URL+"/2026/01", "checkpoint.txt", targets[1].verifier); err == nil {
t.Errorf("expected targets[1].verifier to reject 2026/01 checkpoint, got nil")
}
}
7 changes: 6 additions & 1 deletion verifier_tools/verify/internal/checkpoint/checkpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ const (
originIDPixel = "developers.google.com/android/binary_transparency/0\n"
// originIDG1P identifies a checkpoint for the Google System APK Transparency Log.
originIDG1P = "developers.google.com/android/binary_transparency/google1p/0\n"
// originIDG1PJWT202601 identifies a checkpoint for the Google System APK Transparency Log (2026/01 shard).
originIDG1PJWT202601 = "gstatic.com/android/binary_transparency/google1p/jwt/0\n"
// originIDG1PAPK identifies a checkpoint for the Google 1P APK Transparency Log.
originIDG1PAPK = "gstatic.com/android/binary_transparency/google1p/apk/2026/0\n"
// originIDG1PAPKTessera identifies a checkpoint for the Google 1P APK Transparency Log (Tessera shard).
Expand Down Expand Up @@ -123,6 +125,8 @@ func parseCheckpoint(ckpt string) (Root, error) {
body = ckpt[len(originIDPixel):]
case strings.HasPrefix(ckpt, originIDG1P):
body = ckpt[len(originIDG1P):]
case strings.HasPrefix(ckpt, originIDG1PJWT202601):
body = ckpt[len(originIDG1PJWT202601):]
case strings.HasPrefix(ckpt, originIDG1PAPK):
body = ckpt[len(originIDG1PAPK):]
case strings.HasPrefix(ckpt, originIDG1PAPKTessera):
Expand All @@ -132,9 +136,10 @@ func parseCheckpoint(ckpt string) (Root, error) {
case strings.HasPrefix(ckpt, originIDMainlineModuleTessera):
body = ckpt[len(originIDMainlineModuleTessera):]
default:
return Root{}, fmt.Errorf("invalid checkpoint - unknown origin, must be either %s, %s, %s, %s, %s, or %s",
return Root{}, fmt.Errorf("invalid checkpoint - unknown origin, must be either %s, %s, %s, %s, %s, %s, or %s",
strings.TrimSpace(originIDPixel),
strings.TrimSpace(originIDG1P),
strings.TrimSpace(originIDG1PJWT202601),
strings.TrimSpace(originIDG1PAPK),
strings.TrimSpace(originIDG1PAPKTessera),
strings.TrimSpace(originIDMainlineModule),
Expand Down
10 changes: 10 additions & 0 deletions verifier_tools/verify/internal/checkpoint/checkpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,16 @@ func TestValidCheckpointFormat(t *testing.T) {
m: "developers.google.com/android/binary_transparency/0\n10\ndGhlIHZpZXcgZnJvbSB0aGUgdHJlZSB0b3BzIGlzIGdyZWF0IQ==\n",
wantSize: 10,
},
{
desc: "google 1p legacy origin",
m: "developers.google.com/android/binary_transparency/google1p/0\n134\nWddUpZSJPJPm93SLwoCdKv+oJqPEqie52TZTIVNOhok=\n",
wantSize: 134,
},
{
desc: "google 1p jwt 2026/01 origin",
m: "gstatic.com/android/binary_transparency/google1p/jwt/0\n234\nHpW7vHFFioFiMf0IglK1B3MLk80iaGOC6Ud6Etq038U=\n",
wantSize: 234,
},
{
desc: "google 1p apk origin",
m: "gstatic.com/android/binary_transparency/google1p/apk/2026/0\n1797152\nIkmuYB2xKKEOLiaQkIho1o9/uGjrKDbk8xa3xXaFeHY=\n",
Expand Down
4 changes: 4 additions & 0 deletions verifier_tools/verify/internal/tiles/reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,10 @@ func TestLogDirFromURL(t *testing.T) {
url: "https://developers.google.com/android/binary_transparency/google1p",
want: "google1p",
},
{
url: "https://www.gstatic.com/android/binary_transparency/google1p/jwt/2026/01",
want: filepath.FromSlash("google1p/jwt/2026/01"),
},
{
url: "https://www.gstatic.com/android/binary_transparency/google1p/apk/2026/01",
want: filepath.FromSlash("google1p/apk/2026/01"),
Expand Down
Loading