177 lines
5.5 KiB
Go
177 lines
5.5 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestAuditMiniAPK is a manual smoke test against a real APK on disk.
|
|
// Run: go test -run TestAuditMiniAPK -v
|
|
func TestAuditMiniAPK(t *testing.T) {
|
|
apk := os.Getenv("AUDIT_APK")
|
|
if apk == "" {
|
|
apk = "/home/xyz/.local/share/apktool/framework/1.apk"
|
|
}
|
|
if _, err := os.Stat(apk); err != nil {
|
|
t.Skipf("test apk not present: %v", err)
|
|
}
|
|
|
|
app := NewApp()
|
|
res, err := app.AuditAPK(apk)
|
|
if err != nil {
|
|
t.Fatalf("AuditAPK error: %v", err)
|
|
}
|
|
dumpAudit(t, res)
|
|
|
|
// Explorer: read the (binary) manifest and a resource entry.
|
|
ent, err := app.ReadAPKEntry(res.LocalPath, "AndroidManifest.xml")
|
|
if err != nil {
|
|
t.Errorf("ReadAPKEntry manifest: %v", err)
|
|
} else {
|
|
fmt.Printf("\nentry AndroidManifest.xml: kind=%s size=%d truncated=%v hexlines=%d\n",
|
|
ent.Kind, ent.Size, ent.Truncated, len(splitLines(ent.Hex)))
|
|
}
|
|
|
|
// Export builders (skip the GUI save dialog; just validate serialization).
|
|
csv := auditToCSV(res)
|
|
fmt.Printf("CSV rows=%d firstline=%q\n", len(splitLines(csv)), firstLine(csv))
|
|
if b, err := json.Marshal(auditToSARIF(res)); err != nil {
|
|
t.Errorf("SARIF marshal: %v", err)
|
|
} else {
|
|
fmt.Printf("SARIF bytes=%d\n", len(b))
|
|
}
|
|
}
|
|
|
|
// TestParity compares the reference (aapt2/apksigner) parse against the pure-Go
|
|
// fallback on the same APK, so we can confirm the hybrid behaves the same.
|
|
// Run: go test -run TestParity -v (uses AUDIT_APK or the framework apk)
|
|
func TestParity(t *testing.T) {
|
|
apk := os.Getenv("AUDIT_APK")
|
|
if apk == "" {
|
|
apk = "/home/xyz/.local/share/apktool/framework/1.apk"
|
|
}
|
|
if _, err := os.Stat(apk); err != nil {
|
|
t.Skipf("test apk not present: %v", err)
|
|
}
|
|
app := NewApp()
|
|
|
|
ref, err := app.AuditAPK(apk) // reference path (tools present on this box)
|
|
if err != nil {
|
|
t.Fatalf("reference audit: %v", err)
|
|
}
|
|
|
|
var go_ APKAudit
|
|
go_.Counts = map[string]int{}
|
|
parseManifestGo(apk, &go_)
|
|
parseCertGo(apk, &go_)
|
|
finalizeCert(&go_)
|
|
|
|
expCount := func(a APKAudit) (n int) {
|
|
for _, c := range a.Components {
|
|
if c.Exported || c.ExportedImplicit {
|
|
n++
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
fmt.Printf("\n%-16s | %-28s | %-28s\n", "field", "aapt2/apksigner (ref)", "pure-Go (fallback)")
|
|
row := func(label, a, b string) {
|
|
flag := ""
|
|
if a != b {
|
|
flag = " <-- DIFF"
|
|
}
|
|
fmt.Printf("%-16s | %-28s | %-28s%s\n", label, a, b, flag)
|
|
}
|
|
row("package", ref.PackageName, go_.PackageName)
|
|
row("versionName", ref.VersionName, go_.VersionName)
|
|
row("versionCode", ref.VersionCode, go_.VersionCode)
|
|
row("minSdk", ref.MinSDK, go_.MinSDK)
|
|
row("targetSdk", ref.TargetSDK, go_.TargetSDK)
|
|
row("permissions", itoa(len(ref.Permissions)), itoa(len(go_.Permissions)))
|
|
row("components", itoa(len(ref.Components)), itoa(len(go_.Components)))
|
|
row("exported", itoa(expCount(ref)), itoa(expCount(go_)))
|
|
row("debuggable", b2s(ref.Debuggable), b2s(go_.Debuggable))
|
|
row("allowBackup", b2s(ref.AllowBackup), b2s(go_.AllowBackup))
|
|
row("cert.verified", b2s(ref.Cert.Verified), b2s(go_.Cert.Verified))
|
|
row("cert.v1/v2/v3", schemes(ref.Cert), schemes(go_.Cert))
|
|
row("cert.sha256", trunc16(ref.Cert.SHA256), trunc16(go_.Cert.SHA256))
|
|
|
|
if ref.PackageName != go_.PackageName {
|
|
t.Errorf("package mismatch: %q vs %q", ref.PackageName, go_.PackageName)
|
|
}
|
|
if ref.Cert.SHA256 != "" && go_.Cert.SHA256 != "" && ref.Cert.SHA256 != go_.Cert.SHA256 {
|
|
t.Errorf("cert SHA-256 mismatch: %q vs %q", ref.Cert.SHA256, go_.Cert.SHA256)
|
|
}
|
|
}
|
|
|
|
func itoa(n int) string { return fmt.Sprintf("%d", n) }
|
|
func b2s(b bool) string { return fmt.Sprintf("%v", b) }
|
|
func schemes(c APKCertInfo) string { return fmt.Sprintf("%v/%v/%v", c.V1, c.V2, c.V3) }
|
|
func trunc16(s string) string {
|
|
if len(s) > 16 {
|
|
return s[:16] + "…"
|
|
}
|
|
return s
|
|
}
|
|
|
|
func splitLines(s string) []string {
|
|
if s == "" {
|
|
return nil
|
|
}
|
|
var n []string
|
|
for _, l := range strings.Split(s, "\n") {
|
|
if l != "" {
|
|
n = append(n, l)
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
// TestAuditInstalled audits a package off the connected device.
|
|
// Run: AUDIT_PKG=com.android.settings go test -run TestAuditInstalled -v
|
|
func TestAuditInstalled(t *testing.T) {
|
|
pkg := os.Getenv("AUDIT_PKG")
|
|
if pkg == "" {
|
|
t.Skip("set AUDIT_PKG to audit an installed package")
|
|
}
|
|
app := NewApp()
|
|
res, err := app.AuditInstalledApp(pkg)
|
|
if err != nil {
|
|
t.Fatalf("AuditInstalledApp error: %v", err)
|
|
}
|
|
dumpAudit(t, res)
|
|
}
|
|
|
|
func dumpAudit(t *testing.T, res APKAudit) {
|
|
t.Helper()
|
|
|
|
fmt.Printf("\n=== %s (%s v%s)\n", res.AppLabel, res.PackageName, res.VersionName)
|
|
fmt.Printf("score=%d grade=%s min=%s target=%s\n", res.Score, res.Grade, res.MinSDK, res.TargetSDK)
|
|
fmt.Printf("perms=%d components=%d files=%d trackers=%d findings=%d\n",
|
|
len(res.Permissions), len(res.Components), len(res.Files), len(res.Trackers), len(res.Findings))
|
|
fmt.Printf("cert: verified=%v v1=%v v2=%v v3=%v debug=%v err=%q\n",
|
|
res.Cert.Verified, res.Cert.V1, res.Cert.V2, res.Cert.V3, res.Cert.IsDebug, res.Cert.Error)
|
|
fmt.Printf("flags: debuggable=%v allowBackup=%v cleartext=%v nsc=%v\n",
|
|
res.Debuggable, res.AllowBackup, res.UsesCleartext, res.HasNSC)
|
|
fmt.Println("counts:", res.Counts)
|
|
fmt.Println("--- findings ---")
|
|
for _, f := range res.Findings {
|
|
fmt.Printf("[%-8s] %-40s (%d matches) %s %s\n", f.Severity, f.Title, len(f.Matches), f.CWE, f.Masvs)
|
|
}
|
|
fmt.Println("--- trackers ---")
|
|
for _, tr := range res.Trackers {
|
|
fmt.Printf(" %-22s %-16s x%d\n", tr.Name, tr.Category, tr.Matches)
|
|
}
|
|
|
|
if res.PackageName == "" {
|
|
t.Error("expected a package name from aapt2")
|
|
}
|
|
// quick JSON round-trip to ensure it serializes for the frontend
|
|
if _, err := json.Marshal(res); err != nil {
|
|
t.Errorf("json marshal failed: %v", err)
|
|
}
|
|
}
|