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
This commit is contained in:
Winson
2021-03-31 12:35:51 -07:00
parent 091a4e9d05
commit d65f0926dc
5 changed files with 206 additions and 51 deletions

View File

@@ -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)

View File

@@ -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,9 +1022,22 @@ public class DomainVerificationService extends SystemService
@Override
public void writeSettings(@NonNull TypedXmlSerializer serializer) throws IOException {
synchronized (mLock) {
mSettings.writeSettings(serializer, mAttachedPkgStates);
}
mConnection.withPackageSettingsThrowing(pkgSettings -> {
synchronized (mLock) {
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);
}

View File

@@ -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)

View File

@@ -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() {}

View File

@@ -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,20 +286,30 @@ 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 {
stateMap["$packageName.com"] = id
userStates[id] = DomainVerificationInternalUserState(id).apply {
addHosts(setOf("$packageName-user.com"))
isLinkHandlingAllowed = true
}
}
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"))
isLinkHandlingAllowed = true
}
}
private fun pkgName(id: Int) = "$PKG_PREFIX.pkg$id"
}