package usage import ( "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" "crypto/x509" "encoding/base64" "encoding/json" "encoding/pem" "fmt" "net/http" "net/http/httptest" "testing" "time" ) // adMobTestKeyServer spins up an httptest server that serves a single ECDSA // public key in the AdMob verifier-keys.json format, and returns the server, // the private key, and the key id. func adMobTestKeyServer(t *testing.T) (*httptest.Server, *ecdsa.PrivateKey, string) { t.Helper() priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatalf("generate key: %v", err) } der, err := x509.MarshalPKIXPublicKey(&priv.PublicKey) if err != nil { t.Fatalf("marshal pub: %v", err) } pemBytes := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der}) const keyID = "3335741209" body := map[string]any{ "keys": []map[string]any{ {"keyId": 3335741209, "pem": string(pemBytes), "base64": base64.StdEncoding.EncodeToString(der)}, }, } srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _ = json.NewEncoder(w).Encode(body) })) t.Cleanup(srv.Close) return srv, priv, keyID } // signAdMob builds a signed AdMob SSV callback query string. func signAdMob(t *testing.T, priv *ecdsa.PrivateKey, keyID, content string) string { t.Helper() digest := sha256.Sum256([]byte(content)) sig, err := ecdsa.SignASN1(rand.Reader, priv, digest[:]) if err != nil { t.Fatalf("sign: %v", err) } sigB64 := base64.RawURLEncoding.EncodeToString(sig) return fmt.Sprintf("%s&signature=%s&key_id=%s", content, sigB64, keyID) } func TestAdMobVerifyValid(t *testing.T) { srv, priv, keyID := adMobTestKeyServer(t) v := NewAdMobVerifier(srv.URL, srv.Client(), time.Hour, []string{"ca-app-pub-123/456"}) content := "ad_network=admob&ad_unit=ca-app-pub-123/456&custom_data=user-uuid-42&reward_amount=1&reward_item=minutes×tamp=1700000000&transaction_id=abc123&user_id=device-1" token := signAdMob(t, priv, keyID, content) err := v.Verify(context.Background(), AdVerifyRequest{ UserID: 42, DeviceID: "device-1", AdToken: token, ExpectedCustomData: "user-uuid-42", }) if err != nil { t.Fatalf("expected valid receipt, got %v", err) } } func TestAdMobVerifyForgedSignature(t *testing.T) { srv, priv, keyID := adMobTestKeyServer(t) v := NewAdMobVerifier(srv.URL, srv.Client(), time.Hour, nil) content := "ad_unit=ca-app-pub-123/456&custom_data=u1&transaction_id=abc" token := signAdMob(t, priv, keyID, content) // Tamper with the signed content after signing → signature no longer matches. tampered := "ad_unit=ca-app-pub-123/456&custom_data=u1&transaction_id=EVIL" + token[len(content):] if err := v.Verify(context.Background(), AdVerifyRequest{AdToken: tampered}); err == nil { t.Fatal("expected forged signature to be rejected") } } func TestAdMobVerifyCustomDataMismatch(t *testing.T) { srv, priv, keyID := adMobTestKeyServer(t) v := NewAdMobVerifier(srv.URL, srv.Client(), time.Hour, nil) content := "ad_unit=ca-app-pub-123/456&custom_data=u1&transaction_id=abc" token := signAdMob(t, priv, keyID, content) if err := v.Verify(context.Background(), AdVerifyRequest{ AdToken: token, ExpectedCustomData: "someone-else", }); err == nil { t.Fatal("expected custom_data mismatch to be rejected") } } func TestAdMobVerifyDisallowedAdUnit(t *testing.T) { srv, priv, keyID := adMobTestKeyServer(t) v := NewAdMobVerifier(srv.URL, srv.Client(), time.Hour, []string{"ca-app-pub-allowed/1"}) content := "ad_unit=ca-app-pub-OTHER/9&custom_data=u1&transaction_id=abc" token := signAdMob(t, priv, keyID, content) if err := v.Verify(context.Background(), AdVerifyRequest{AdToken: token}); err == nil { t.Fatal("expected disallowed ad_unit to be rejected") } } func TestAdMobVerifyMalformedToken(t *testing.T) { srv, _, _ := adMobTestKeyServer(t) v := NewAdMobVerifier(srv.URL, srv.Client(), time.Hour, nil) for _, tok := range []string{"", "no-signature-here", "?garbage"} { if err := v.Verify(context.Background(), AdVerifyRequest{AdToken: tok}); err == nil { t.Errorf("expected malformed token %q to be rejected", tok) } } } func TestUnityVerifierUnimplemented(t *testing.T) { var v AdVerifier = UnityVerifier{} if v.Provider() != "unity" { t.Errorf("provider = %q, want unity", v.Provider()) } if err := v.Verify(context.Background(), AdVerifyRequest{}); err == nil { t.Error("expected Unity verifier to report unimplemented") } }