Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d65f0926 authored by Winson's avatar Winson
Browse files

Support serializing package signatures for domain verification state

To prepare for backup/restore changes which need to verify that the
package signatures match.

Bug: 182207215

Test: atest DomainVerificationPersistenceTest

Change-Id: I9e4bd24256604b649acf2c925cce2ac65331fa2c
parent 091a4e9d
Loading
Loading
Loading
Loading
+39 −13
Original line number Diff line number Diff line
@@ -18,10 +18,12 @@ package com.android.server.pm.verify.domain;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.Signature;
import android.content.pm.verify.domain.DomainVerificationState;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.PackageUtils;
import android.util.SparseArray;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
@@ -36,6 +38,7 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.Collection;
import java.util.UUID;
import java.util.function.Function;

public class DomainVerificationPersistence {

@@ -49,6 +52,7 @@ public class DomainVerificationPersistence {
    private static final String ATTR_PACKAGE_NAME = "packageName";
    private static final String ATTR_ID = "id";
    private static final String ATTR_HAS_AUTO_VERIFY_DOMAINS = "hasAutoVerifyDomains";
    private static final String ATTR_SIGNATURE = "signature";
    private static final String TAG_USER_STATES = "user-states";

    public static final String TAG_USER_STATE = "user-state";
@@ -62,10 +66,18 @@ public class DomainVerificationPersistence {
    public static final String ATTR_NAME = "name";
    public static final String ATTR_STATE = "state";

    /**
     * @param pkgNameToSignature Converts package name to a string representation of its signature.
     *                           Usually this is the SHA-256 hash from
     *                           {@link PackageUtils#computeSignaturesSha256Digest(Signature[])},
     *                           but can be an arbitrary string for testing purposes. Pass non-null
     *                           to write out signatures, or null to ignore.
     */
    public static void writeToXml(@NonNull TypedXmlSerializer xmlSerializer,
            @NonNull DomainVerificationStateMap<DomainVerificationPkgState> attached,
            @NonNull ArrayMap<String, DomainVerificationPkgState> pending,
            @NonNull ArrayMap<String, DomainVerificationPkgState> restored) throws IOException {
            @NonNull ArrayMap<String, DomainVerificationPkgState> restored,
            @Nullable Function<String, String> pkgNameToSignature) throws IOException {
        try (SettingsXml.Serializer serializer = SettingsXml.serializer(xmlSerializer)) {
            try (SettingsXml.WriteSection ignored = serializer.startSection(
                    TAG_DOMAIN_VERIFICATIONS)) {
@@ -86,25 +98,26 @@ public class DomainVerificationPersistence {
                }

                try (SettingsXml.WriteSection activeSection = serializer.startSection(TAG_ACTIVE)) {
                    writePackageStates(activeSection, active);
                    writePackageStates(activeSection, active, pkgNameToSignature);
                }

                try (SettingsXml.WriteSection restoredSection = serializer.startSection(
                        TAG_RESTORED)) {
                    writePackageStates(restoredSection, restored.values());
                    writePackageStates(restoredSection, restored.values(), pkgNameToSignature);
                }
            }
        }
    }

    private static void writePackageStates(@NonNull SettingsXml.WriteSection section,
            @NonNull Collection<DomainVerificationPkgState> states) throws IOException {
            @NonNull Collection<DomainVerificationPkgState> states,
            @Nullable Function<String, String> pkgNameToSignature) throws IOException {
        if (states.isEmpty()) {
            return;
        }

        for (DomainVerificationPkgState state : states) {
            writePkgStateToXml(section, state);
            writePkgStateToXml(section, state, pkgNameToSignature);
        }
    }

@@ -146,11 +159,12 @@ public class DomainVerificationPersistence {
     * been entered.
     */
    @Nullable
    public static DomainVerificationPkgState createPkgStateFromXml(
    private static DomainVerificationPkgState createPkgStateFromXml(
            @NonNull SettingsXml.ReadSection section) {
        String packageName = section.getString(ATTR_PACKAGE_NAME);
        String idString = section.getString(ATTR_ID);
        boolean hasAutoVerifyDomains = section.getBoolean(ATTR_HAS_AUTO_VERIFY_DOMAINS);
        String signature = section.getString(ATTR_SIGNATURE);
        if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(idString)) {
            return null;
        }
@@ -172,7 +186,7 @@ public class DomainVerificationPersistence {
        }

        return new DomainVerificationPkgState(packageName, id, hasAutoVerifyDomains, stateMap,
                userStates);
                userStates, signature);
    }

    private static void readUserStates(@NonNull SettingsXml.ReadSection section,
@@ -196,14 +210,26 @@ public class DomainVerificationPersistence {
        }
    }

    public static void writePkgStateToXml(@NonNull SettingsXml.WriteSection parentSection,
            @NonNull DomainVerificationPkgState pkgState) throws IOException {
    private static void writePkgStateToXml(@NonNull SettingsXml.WriteSection parentSection,
            @NonNull DomainVerificationPkgState pkgState,
            @Nullable Function<String, String> pkgNameToSignature) throws IOException {
        String packageName = pkgState.getPackageName();
        String signature = pkgNameToSignature == null
                ? null : pkgNameToSignature.apply(packageName);
        if (signature == null) {
            // If a package isn't available to get its signature, fallback to the previously stored
            // result, which can occur if the package has been marked for restore but hasn't
            // been installed on the new device yet.
            signature = pkgState.getBackupSignatureHash();
        }

        try (SettingsXml.WriteSection ignored =
                     parentSection.startSection(TAG_PACKAGE_STATE)
                             .attribute(ATTR_PACKAGE_NAME, pkgState.getPackageName())
                             .attribute(ATTR_PACKAGE_NAME, packageName)
                             .attribute(ATTR_ID, pkgState.getId().toString())
                             .attribute(ATTR_HAS_AUTO_VERIFY_DOMAINS,
                                     pkgState.isHasAutoVerifyDomains())) {
                                     pkgState.isHasAutoVerifyDomains())
                             .attribute(ATTR_SIGNATURE, signature)) {
            writeStateMap(parentSection, pkgState.getStateMap());
            writeUserStates(parentSection, pkgState.getUserStates());
        }
@@ -245,7 +271,7 @@ public class DomainVerificationPersistence {
     * entered.
     */
    @Nullable
    public static DomainVerificationInternalUserState createUserStateFromXml(
    private static DomainVerificationInternalUserState createUserStateFromXml(
            @NonNull SettingsXml.ReadSection section) {
        int userId = section.getInt(ATTR_USER_ID);
        if (userId == -1) {
@@ -274,7 +300,7 @@ public class DomainVerificationPersistence {
        }
    }

    public static void writeUserStateToXml(@NonNull SettingsXml.WriteSection parentSection,
    private static void writeUserStateToXml(@NonNull SettingsXml.WriteSection parentSection,
            @NonNull DomainVerificationInternalUserState userState) throws IOException {
        try (SettingsXml.WriteSection section =
                     parentSection.startSection(TAG_USER_STATE)
+21 −5
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.PackageUtils;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -842,7 +843,8 @@ public class DomainVerificationService extends SystemService
                        + ", new pkg = " + newPkg, new Exception());

                DomainVerificationPkgState newPkgState = new DomainVerificationPkgState(
                        pkgName, newDomainSetId, true, newStateMap, newUserStates);
                        pkgName, newDomainSetId, true, newStateMap, newUserStates,
                        null /* signature */);
                mAttachedPkgStates.put(pkgName, newDomainSetId, newPkgState);
                return;
            }
@@ -894,7 +896,8 @@ public class DomainVerificationService extends SystemService
            sendBroadcast = hasAutoVerifyDomains && needsBroadcast;

            mAttachedPkgStates.put(pkgName, newDomainSetId, new DomainVerificationPkgState(
                    pkgName, newDomainSetId, hasAutoVerifyDomains, newStateMap, newUserStates));
                    pkgName, newDomainSetId, hasAutoVerifyDomains, newStateMap, newUserStates,
                    null /* signature */));
        }

        if (sendBroadcast) {
@@ -1019,10 +1022,23 @@ public class DomainVerificationService extends SystemService

    @Override
    public void writeSettings(@NonNull TypedXmlSerializer serializer) throws IOException {
        mConnection.withPackageSettingsThrowing(pkgSettings -> {
            synchronized (mLock) {
            mSettings.writeSettings(serializer, mAttachedPkgStates);
                mSettings.writeSettings(serializer, mAttachedPkgStates, pkgName -> {
                    PackageSetting pkgSetting = pkgSettings.apply(pkgName);
                    if (pkgSetting == null) {
                        // If querying for a user restored package that isn't installed on the
                        // device yet, there will be no signature to write out. In that case,
                        // it's expected that this returns null and it falls back to the restored
                        // state's stored signature if it exists.
                        return null;
                    }

                    return PackageUtils.computeSignaturesSha256Digest(pkgSetting.getSignatures());
                });
            }
        });

        mLegacySettings.writeSettings(serializer);
    }

+6 −4
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.function.Function;

class DomainVerificationSettings {

@@ -68,18 +69,19 @@ class DomainVerificationSettings {


    public void writeSettings(@NonNull TypedXmlSerializer xmlSerializer,
            @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState)
            throws IOException {
            @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState,
            @NonNull Function<String, String> pkgSignatureFunction) throws IOException {
        synchronized (mLock) {
            DomainVerificationPersistence.writeToXml(xmlSerializer, liveState,
                    mPendingPkgStates, mRestoredPkgStates);
                    mPendingPkgStates, mRestoredPkgStates, pkgSignatureFunction);
        }
    }

    /**
     * Parses a previously stored set of states and merges them with {@param liveState}, directly
     * mutating the values. This is intended for reading settings written by {@link
     * #writeSettings(TypedXmlSerializer, DomainVerificationStateMap)} on the same device setup.
     * #writeSettings(TypedXmlSerializer, DomainVerificationStateMap, Function)} on the same device
     * setup.
     */
    public void readSettings(@NonNull TypedXmlPullParser parser,
            @NonNull DomainVerificationStateMap<DomainVerificationPkgState> liveState)
+50 −9
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.server.pm.verify.domain.models;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.pm.Signature;
import android.content.pm.verify.domain.DomainVerificationState;
import android.util.ArrayMap;
import android.util.SparseArray;
@@ -63,15 +64,28 @@ public class DomainVerificationPkgState {
    @NonNull
    private final SparseArray<DomainVerificationInternalUserState> mUserStates;

    /**
     * If previously recorded, the SHA-256 signing cert digest of the package to attach to.
     * When doing restoration of a previously backed up state, if the signature does not
     * match the package being scanned/installed on device, it will be rejected.
     *
     * It's assumed the domain verification agent will eventually re-verify this domain
     * and revoke if necessary.
     *
     * @see android.util.PackageUtils#computeSignaturesSha256Digest(Signature[])
     */
    @Nullable
    private final String mBackupSignatureHash;

    public DomainVerificationPkgState(@NonNull String packageName, @NonNull UUID id,
            boolean hasAutoVerifyDomains) {
        this(packageName, id, hasAutoVerifyDomains, new ArrayMap<>(0), new SparseArray<>(0));
        this(packageName, id, hasAutoVerifyDomains, new ArrayMap<>(0), new SparseArray<>(0), null);
    }

    public DomainVerificationPkgState(@NonNull DomainVerificationPkgState pkgState,
            @NonNull UUID id, boolean hasAutoVerifyDomains) {
        this(pkgState.getPackageName(), id, hasAutoVerifyDomains, pkgState.getStateMap(),
                pkgState.getUserStates());
                pkgState.getUserStates(), null);
    }

    @Nullable
@@ -109,7 +123,7 @@ public class DomainVerificationPkgState {



    // Code below generated by codegen v1.0.22.
    // Code below generated by codegen v1.0.23.
    //
    // DO NOT MODIFY!
    // CHECKSTYLE:OFF Generated code
@@ -137,6 +151,13 @@ public class DomainVerificationPkgState {
     *
     *   TODO(b/159952358): Hide the state map entirely from the caller, to allow optimizations,
     *    such as storing no state when the package is marked as a linked app in SystemConfig.
     * @param backupSignatureHash
     *   If previously recorded, the SHA-256 signing cert digest of the package to attach to.
     *   When doing restoration of a previously backed up state, if the signature does not
     *   match the package being scanned/installed on device, it will be rejected.
     *
     *   It's assumed the domain verification agent will eventually re-verify this domain
     *   and revoke if necessary.
     */
    @DataClass.Generated.Member
    public DomainVerificationPkgState(
@@ -144,7 +165,8 @@ public class DomainVerificationPkgState {
            @NonNull UUID id,
            boolean hasAutoVerifyDomains,
            @NonNull ArrayMap<String,Integer> stateMap,
            @NonNull SparseArray<DomainVerificationInternalUserState> userStates) {
            @NonNull SparseArray<DomainVerificationInternalUserState> userStates,
            @Nullable String backupSignatureHash) {
        this.mPackageName = packageName;
        com.android.internal.util.AnnotationValidations.validate(
                NonNull.class, null, mPackageName);
@@ -158,6 +180,7 @@ public class DomainVerificationPkgState {
        this.mUserStates = userStates;
        com.android.internal.util.AnnotationValidations.validate(
                NonNull.class, null, mUserStates);
        this.mBackupSignatureHash = backupSignatureHash;

        // onConstructed(); // You can define this method to get a callback
    }
@@ -201,6 +224,21 @@ public class DomainVerificationPkgState {
        return mUserStates;
    }

    /**
     * If previously recorded, the SHA-256 signing cert digest of the package to attach to.
     * When doing restoration of a previously backed up state, if the signature does not
     * match the package being scanned/installed on device, it will be rejected.
     *
     * It's assumed the domain verification agent will eventually re-verify this domain
     * and revoke if necessary.
     *
     * @see android.util.PackageUtils#computeSignaturesSha256Digest(Signature[])
     */
    @DataClass.Generated.Member
    public @Nullable String getBackupSignatureHash() {
        return mBackupSignatureHash;
    }

    @Override
    @DataClass.Generated.Member
    public String toString() {
@@ -212,7 +250,8 @@ public class DomainVerificationPkgState {
                "id = " + mId + ", " +
                "hasAutoVerifyDomains = " + mHasAutoVerifyDomains + ", " +
                "stateMap = " + mStateMap + ", " +
                "userStates = " + mUserStates +
                "userStates = " + mUserStates + ", " +
                "backupSignatureHash = " + mBackupSignatureHash +
        " }";
    }

@@ -233,7 +272,8 @@ public class DomainVerificationPkgState {
                && Objects.equals(mId, that.mId)
                && mHasAutoVerifyDomains == that.mHasAutoVerifyDomains
                && Objects.equals(mStateMap, that.mStateMap)
                && userStatesEquals(that.mUserStates);
                && userStatesEquals(that.mUserStates)
                && Objects.equals(mBackupSignatureHash, that.mBackupSignatureHash);
    }

    @Override
@@ -248,14 +288,15 @@ public class DomainVerificationPkgState {
        _hash = 31 * _hash + Boolean.hashCode(mHasAutoVerifyDomains);
        _hash = 31 * _hash + Objects.hashCode(mStateMap);
        _hash = 31 * _hash + userStatesHashCode();
        _hash = 31 * _hash + Objects.hashCode(mBackupSignatureHash);
        return _hash;
    }

    @DataClass.Generated(
            time = 1614818362549L,
            codegenVersion = "1.0.22",
            time = 1617315369614L,
            codegenVersion = "1.0.23",
            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationPkgState.java",
            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull java.util.UUID mId\nprivate final  boolean mHasAutoVerifyDomains\nprivate final @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.Integer> mStateMap\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState> mUserStates\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getUserState(int)\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getOrCreateUserState(int)\npublic  void setId(java.util.UUID)\npublic  void removeUser(int)\npublic  void removeAllUsers()\nprivate  int userStatesHashCode()\nprivate  boolean userStatesEquals(android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState>)\nclass DomainVerificationPkgState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate @android.annotation.NonNull java.util.UUID mId\nprivate final  boolean mHasAutoVerifyDomains\nprivate final @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.Integer> mStateMap\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState> mUserStates\nprivate final @android.annotation.Nullable java.lang.String mBackupSignatureHash\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getUserState(int)\npublic @android.annotation.Nullable com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState getOrCreateUserState(int)\npublic  void removeUser(int)\npublic  void removeAllUsers()\nprivate  int userStatesHashCode()\nprivate  boolean userStatesEquals(android.util.SparseArray<com.android.server.pm.verify.domain.models.DomainVerificationInternalUserState>)\nclass DomainVerificationPkgState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
    @Deprecated
    private void __metadata() {}

+90 −20
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.pm.test.verify.domain

import android.content.pm.verify.domain.DomainVerificationState
import android.util.ArrayMap
import android.util.SparseArray
import android.util.TypedXmlPullParser
import android.util.TypedXmlSerializer
import android.util.Xml
@@ -84,23 +85,32 @@ class DomainVerificationPersistenceTest {
    @JvmField
    val tempFolder = TemporaryFolder()

    @Test
    fun writeAndReadBackNormal() {
    private fun mockWriteValues(
        pkgNameToSignature: (String) -> String? = { null }
    ): Triple<DomainVerificationStateMap<DomainVerificationPkgState>,
            ArrayMap<String, DomainVerificationPkgState>,
            ArrayMap<String, DomainVerificationPkgState>> {
        val attached = DomainVerificationStateMap<DomainVerificationPkgState>().apply {
            mockPkgState(0).let { put(it.packageName, it.id, it) }
            mockPkgState(1).let { put(it.packageName, it.id, it) }
            mockPkgState(0, pkgNameToSignature).let { put(it.packageName, it.id, it) }
            mockPkgState(1, pkgNameToSignature).let { put(it.packageName, it.id, it) }
        }
        val pending = ArrayMap<String, DomainVerificationPkgState>().apply {
            mockPkgState(2).let { put(it.packageName, it) }
            mockPkgState(3).let { put(it.packageName, it) }
            mockPkgState(2, pkgNameToSignature).let { put(it.packageName, it) }
            mockPkgState(3, pkgNameToSignature).let { put(it.packageName, it) }
        }
        val restored = ArrayMap<String, DomainVerificationPkgState>().apply {
            mockPkgState(4).let { put(it.packageName, it) }
            mockPkgState(5).let { put(it.packageName, it) }
            mockPkgState(4, pkgNameToSignature).let { put(it.packageName, it) }
            mockPkgState(5, pkgNameToSignature).let { put(it.packageName, it) }
        }

        return Triple(attached, pending, restored)
    }

    @Test
    fun writeAndReadBackNormal() {
        val (attached, pending, restored) = mockWriteValues()
        val file = tempFolder.newFile().writeXml {
            DomainVerificationPersistence.writeToXml(it, attached, pending, restored)
            DomainVerificationPersistence.writeToXml(it, attached, pending, restored, null)
        }

        val xml = file.readText()
@@ -114,9 +124,57 @@ class DomainVerificationPersistenceTest {
        assertWithMessage(xml).that(readRestored.values).containsExactlyElementsIn(restored.values)
    }

    @Test
    fun writeAndReadBackWithSignature() {
        val (attached, pending, restored) = mockWriteValues()
        val file = tempFolder.newFile().writeXml {
            DomainVerificationPersistence.writeToXml(it, attached, pending, restored) {
                "SIGNATURE_$it"
            }
        }

        val (readActive, readRestored) = file.readXml {
            DomainVerificationPersistence.readFromXml(it)
        }

        // Assign the signatures to a fresh set of data structures, to ensure the previous write
        // call did not use the signatures from the data structure. This is because the method is
        // intended to optionally append signatures, regardless of if the existing data structures
        // contain them or not.
        val (attached2, pending2, restored2) = mockWriteValues { "SIGNATURE_$it" }

        assertThat(readActive.values)
            .containsExactlyElementsIn(attached2.values() + pending2.values)
        assertThat(readRestored.values).containsExactlyElementsIn(restored2.values)

        (readActive + readRestored).forEach { (_, value) ->
            assertThat(value.backupSignatureHash).isEqualTo("SIGNATURE_${value.packageName}")
        }
    }

    @Test
    fun writeStateSignatureIfFunctionReturnsNull() {
        val (attached, pending, restored) = mockWriteValues  { "SIGNATURE_$it" }
        val file = tempFolder.newFile().writeXml {
            DomainVerificationPersistence.writeToXml(it, attached, pending, restored) { null }
        }

        val (readActive, readRestored) = file.readXml {
            DomainVerificationPersistence.readFromXml(it)
        }

        assertThat(readActive.values)
            .containsExactlyElementsIn(attached.values() + pending.values)
        assertThat(readRestored.values).containsExactlyElementsIn(restored.values)

        (readActive + readRestored).forEach { (_, value) ->
            assertThat(value.backupSignatureHash).isEqualTo("SIGNATURE_${value.packageName}")
        }
    }

    @Test
    fun readMalformed() {
        val stateZero = mockEmptyPkgState(0).apply {
        val stateZero = mockEmptyPkgState(0, pkgNameToSignature = { "ACTIVE" }).apply {
            stateMap["example.com"] = DomainVerificationState.STATE_SUCCESS
            stateMap["example.org"] = DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED

@@ -128,7 +186,7 @@ class DomainVerificationPersistenceTest {
                isLinkHandlingAllowed = true
            }
        }
        val stateOne = mockEmptyPkgState(1).apply {
        val stateOne = mockEmptyPkgState(1, pkgNameToSignature = { "RESTORED" }).apply {
            // It's valid to have a user selection without any autoVerify domains
            userStates[1] = DomainVerificationInternalUserState(1).apply {
                addHosts(setOf("example-user1.com", "example-user1.org"))
@@ -156,6 +214,7 @@ class DomainVerificationPersistenceTest {
                        packageName="${stateZero.packageName}"
                        id="${stateZero.id}"
                        hasAutoVerifyDomains="true"
                        signature="ACTIVE"
                        >
                        <state>
                            <domain name="example.com" state="${
@@ -191,6 +250,7 @@ class DomainVerificationPersistenceTest {
                        packageName="${stateOne.packageName}"
                        id="${stateOne.id}"
                        hasAutoVerifyDomains="true"
                        signature="RESTORED"
                        >
                        <state/>
                        <user-states>
@@ -226,14 +286,24 @@ class DomainVerificationPersistenceTest {

    private fun mockEmptyPkgState(
        id: Int,
        hasAutoVerifyDomains: Boolean = true
        hasAutoVerifyDomains: Boolean = true,
        pkgNameToSignature: (String) -> String? = { null }
    ): DomainVerificationPkgState {
        val pkgName = pkgName(id)
        val domainSetId = UUID(0L, id.toLong())
        return DomainVerificationPkgState(pkgName, domainSetId, hasAutoVerifyDomains)
        return DomainVerificationPkgState(
            pkgName,
            domainSetId,
            hasAutoVerifyDomains,
            ArrayMap(),
            SparseArray(),
            pkgNameToSignature(pkgName)
        )
    }

    private fun mockPkgState(id: Int) = mockEmptyPkgState(id).apply {
    private fun mockPkgState(id: Int, pkgNameToSignature: (String) -> String? = { null }) =
        mockEmptyPkgState(id, pkgNameToSignature = pkgNameToSignature)
            .apply {
                stateMap["$packageName.com"] = id
                userStates[id] = DomainVerificationInternalUserState(id).apply {
                    addHosts(setOf("$packageName-user.com"))