Allow non-required package verifiers
* Verifiers can be specified in the AndroidManifest.xml
* Those verifiers can respond to the new Intent action
* PackageManager API for those verifiers: verifyPendingInstall
Change-Id: I4892bce2e6984871e6e93c60a1ca0dae145f5df5
diff --git a/core/java/android/content/pm/PackageInfoLite.java b/core/java/android/content/pm/PackageInfoLite.java
index da97fde0..9625944 100644
--- a/core/java/android/content/pm/PackageInfoLite.java
+++ b/core/java/android/content/pm/PackageInfoLite.java
@@ -41,6 +41,8 @@
public int recommendedInstallLocation;
public int installLocation;
+ public VerifierInfo[] verifiers;
+
public PackageInfoLite() {
}
@@ -58,6 +60,13 @@
dest.writeString(packageName);
dest.writeInt(recommendedInstallLocation);
dest.writeInt(installLocation);
+
+ if (verifiers == null || verifiers.length == 0) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(verifiers.length);
+ dest.writeTypedArray(verifiers, parcelableFlags);
+ }
}
public static final Parcelable.Creator<PackageInfoLite> CREATOR
@@ -75,5 +84,13 @@
packageName = source.readString();
recommendedInstallLocation = source.readInt();
installLocation = source.readInt();
+
+ final int verifiersLength = source.readInt();
+ if (verifiersLength == 0) {
+ verifiers = new VerifierInfo[0];
+ } else {
+ verifiers = new VerifierInfo[verifiersLength];
+ source.readTypedArray(verifiers, VerifierInfo.CREATOR);
+ }
}
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index ef7e233..d45a71a 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -725,6 +725,16 @@
public static final int MOVE_EXTERNAL_MEDIA = 0x00000002;
/**
+ * Usable by the required verifier as the {@code verificationCode} argument
+ * for {@link PackageManager#verifyPendingInstall} to indicate that it will
+ * allow the installation to proceed without any of the optional verifiers
+ * needing to vote.
+ *
+ * @hide
+ */
+ public static final int VERIFICATION_ALLOW_WITHOUT_SUFFICIENT = 2;
+
+ /**
* Used as the {@code verificationCode} argument for
* {@link PackageManager#verifyPendingInstall} to indicate that the calling
* package verifier allows the installation to proceed.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index e7b844c..c30675b 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -28,7 +28,9 @@
import android.os.Bundle;
import android.os.PatternMatcher;
import android.util.AttributeSet;
+import android.util.Base64;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.util.Slog;
import android.util.TypedValue;
import com.android.internal.util.XmlUtils;
@@ -40,11 +42,18 @@
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
+import java.security.spec.EncodedKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
+import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
@@ -150,12 +159,14 @@
* @hide
*/
public static class PackageLite {
- public String packageName;
- public int installLocation;
- public String mScanPath;
- public PackageLite(String packageName, int installLocation) {
+ public final String packageName;
+ public final int installLocation;
+ public final VerifierInfo[] verifiers;
+
+ public PackageLite(String packageName, int installLocation, List<VerifierInfo> verifiers) {
this.packageName = packageName;
this.installLocation = installLocation;
+ this.verifiers = verifiers.toArray(new VerifierInfo[verifiers.size()]);
}
}
@@ -619,8 +630,9 @@
* @return PackageLite object with package information or null on failure.
*/
public static PackageLite parsePackageLite(String packageFilePath, int flags) {
- XmlResourceParser parser = null;
AssetManager assmgr = null;
+ final XmlResourceParser parser;
+ final Resources res;
try {
assmgr = new AssetManager();
assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@@ -631,6 +643,9 @@
return null;
}
+ final DisplayMetrics metrics = new DisplayMetrics();
+ metrics.setToDefaults();
+ res = new Resources(assmgr, metrics, null);
parser = assmgr.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
} catch (Exception e) {
if (assmgr != null) assmgr.close();
@@ -638,11 +653,12 @@
+ packageFilePath, e);
return null;
}
- AttributeSet attrs = parser;
- String errors[] = new String[1];
+
+ final AttributeSet attrs = parser;
+ final String errors[] = new String[1];
PackageLite packageLite = null;
try {
- packageLite = parsePackageLite(parser, attrs, flags, errors);
+ packageLite = parsePackageLite(res, parser, attrs, flags, errors);
} catch (IOException e) {
Slog.w(TAG, packageFilePath, e);
} catch (XmlPullParserException e) {
@@ -719,9 +735,9 @@
return pkgName.intern();
}
- private static PackageLite parsePackageLite(XmlPullParser parser,
- AttributeSet attrs, int flags, String[] outError)
- throws IOException, XmlPullParserException {
+ private static PackageLite parsePackageLite(Resources res, XmlPullParser parser,
+ AttributeSet attrs, int flags, String[] outError) throws IOException,
+ XmlPullParserException {
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
@@ -759,7 +775,26 @@
break;
}
}
- return new PackageLite(pkgName.intern(), installLocation);
+
+ // Only search the tree when the tag is directly below <manifest>
+ final int searchDepth = parser.getDepth() + 1;
+
+ final List<VerifierInfo> verifiers = new ArrayList<VerifierInfo>();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() >= searchDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ if (parser.getDepth() == searchDepth && "package-verifier".equals(parser.getName())) {
+ final VerifierInfo verifier = parseVerifier(res, parser, attrs, flags, outError);
+ if (verifier != null) {
+ verifiers.add(verifier);
+ }
+ }
+ }
+
+ return new PackageLite(pkgName.intern(), installLocation, verifiers);
}
/**
@@ -2691,6 +2726,63 @@
return data;
}
+ private static VerifierInfo parseVerifier(Resources res, XmlPullParser parser,
+ AttributeSet attrs, int flags, String[] outError) throws XmlPullParserException,
+ IOException {
+ final TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestPackageVerifier);
+
+ final String packageName = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestPackageVerifier_name);
+
+ final String encodedPublicKey = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestPackageVerifier_publicKey);
+
+ sa.recycle();
+
+ if (packageName == null || packageName.length() == 0) {
+ Slog.i(TAG, "verifier package name was null; skipping");
+ return null;
+ } else if (encodedPublicKey == null) {
+ Slog.i(TAG, "verifier " + packageName + " public key was null; skipping");
+ }
+
+ EncodedKeySpec keySpec;
+ try {
+ final byte[] encoded = Base64.decode(encodedPublicKey, Base64.DEFAULT);
+ keySpec = new X509EncodedKeySpec(encoded);
+ } catch (IllegalArgumentException e) {
+ Slog.i(TAG, "Could not parse verifier " + packageName + " public key; invalid Base64");
+ return null;
+ }
+
+ /* First try the key as an RSA key. */
+ try {
+ final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ final PublicKey publicKey = keyFactory.generatePublic(keySpec);
+ return new VerifierInfo(packageName, publicKey);
+ } catch (NoSuchAlgorithmException e) {
+ Log.wtf(TAG, "Could not parse public key because RSA isn't included in build");
+ return null;
+ } catch (InvalidKeySpecException e) {
+ // Not a RSA public key.
+ }
+
+ /* Now try it as a DSA key. */
+ try {
+ final KeyFactory keyFactory = KeyFactory.getInstance("DSA");
+ final PublicKey publicKey = keyFactory.generatePublic(keySpec);
+ return new VerifierInfo(packageName, publicKey);
+ } catch (NoSuchAlgorithmException e) {
+ Log.wtf(TAG, "Could not parse public key because DSA isn't included in build");
+ return null;
+ } catch (InvalidKeySpecException e) {
+ // Not a DSA public key.
+ }
+
+ return null;
+ }
+
private static final String ANDROID_RESOURCES
= "http://schemas.android.com/apk/res/android";
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
index c6aefb8..9c9340d 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -19,7 +19,12 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.io.ByteArrayInputStream;
import java.lang.ref.SoftReference;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
import java.util.Arrays;
/**
@@ -135,6 +140,20 @@
return bytes;
}
+ /**
+ * Returns the public key for this signature.
+ *
+ * @throws CertificateException when Signature isn't a valid X.509
+ * certificate; shouldn't happen.
+ * @hide
+ */
+ public PublicKey getPublicKey() throws CertificateException {
+ final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ final ByteArrayInputStream bais = new ByteArrayInputStream(mSignature);
+ final Certificate cert = certFactory.generateCertificate(bais);
+ return cert.getPublicKey();
+ }
+
@Override
public boolean equals(Object obj) {
try {
diff --git a/core/java/android/content/pm/VerifierInfo.aidl b/core/java/android/content/pm/VerifierInfo.aidl
new file mode 100644
index 0000000..7702d38
--- /dev/null
+++ b/core/java/android/content/pm/VerifierInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2011, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+parcelable VerifierInfo;
diff --git a/core/java/android/content/pm/VerifierInfo.java b/core/java/android/content/pm/VerifierInfo.java
new file mode 100644
index 0000000..0a2b283
--- /dev/null
+++ b/core/java/android/content/pm/VerifierInfo.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.security.PublicKey;
+
+/**
+ * Contains information about a package verifier as used by
+ * {@code PackageManagerService} during package verification.
+ *
+ * @hide
+ */
+public class VerifierInfo implements Parcelable {
+ /** Package name of the verifier. */
+ public final String packageName;
+
+ /** Signatures used to sign the package verifier's package. */
+ public final PublicKey publicKey;
+
+ /**
+ * Creates an object that represents a verifier info object.
+ *
+ * @param packageName the package name in Java-style. Must not be {@code
+ * null} or empty.
+ * @param publicKey the public key for the signer encoded in Base64. Must
+ * not be {@code null} or empty.
+ * @throws IllegalArgumentException if either argument is null or empty.
+ */
+ public VerifierInfo(String packageName, PublicKey publicKey) {
+ if (packageName == null || packageName.length() == 0) {
+ throw new IllegalArgumentException("packageName must not be null or empty");
+ } else if (publicKey == null) {
+ throw new IllegalArgumentException("publicKey must not be null");
+ }
+
+ this.packageName = packageName;
+ this.publicKey = publicKey;
+ }
+
+ private VerifierInfo(Parcel source) {
+ packageName = source.readString();
+ publicKey = (PublicKey) source.readSerializable();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(packageName);
+ dest.writeSerializable(publicKey);
+ }
+
+ public static final Parcelable.Creator<VerifierInfo> CREATOR
+ = new Parcelable.Creator<VerifierInfo>() {
+ public VerifierInfo createFromParcel(Parcel source) {
+ return new VerifierInfo(source);
+ }
+
+ public VerifierInfo[] newArray(int size) {
+ return new VerifierInfo[size];
+ }
+ };
+}
\ No newline at end of file
diff --git a/core/tests/coretests/apks/install_verifier_bad/Android.mk b/core/tests/coretests/apks/install_verifier_bad/Android.mk
new file mode 100644
index 0000000..b50cfd04
--- /dev/null
+++ b/core/tests/coretests/apks/install_verifier_bad/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := FrameworkCoreTests_install_verifier_bad
+
+include $(BUILD_PACKAGE)
+
diff --git a/core/tests/coretests/apks/install_verifier_bad/AndroidManifest.xml b/core/tests/coretests/apks/install_verifier_bad/AndroidManifest.xml
new file mode 100644
index 0000000..0170cdd
--- /dev/null
+++ b/core/tests/coretests/apks/install_verifier_bad/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.coretests.install_verifier_bad">
+
+ <package-verifier android:name="com.android.frameworks.coretests.nonexistent" android:publicKey="Zm9vYmFy" />
+
+ <application android:hasCode="false">
+ </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_verifier_bad/res/values/strings.xml b/core/tests/coretests/apks/install_verifier_bad/res/values/strings.xml
new file mode 100644
index 0000000..3b8b3b1
--- /dev/null
+++ b/core/tests/coretests/apks/install_verifier_bad/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Just need this dummy file to have something to build. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="dummy">dummy</string>
+</resources>
diff --git a/core/tests/coretests/apks/install_verifier_good/Android.mk b/core/tests/coretests/apks/install_verifier_good/Android.mk
new file mode 100644
index 0000000..a48a80e
--- /dev/null
+++ b/core/tests/coretests/apks/install_verifier_good/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := FrameworkCoreTests_install_verifier_good
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/coretests/apks/install_verifier_good/AndroidManifest.xml b/core/tests/coretests/apks/install_verifier_good/AndroidManifest.xml
new file mode 100644
index 0000000..90135a5
--- /dev/null
+++ b/core/tests/coretests/apks/install_verifier_good/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.coretests.install_verifier_bad">
+
+ <package-verifier android:name="com.android.frameworks.coretests" android:publicKey="MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEAnHgFkqwNXTgc3qpl7MimAG42SAxtcgexIBG+UIY6q+K1XQCa33FG1vIgIoDHzU172yYkO4qAbCazSxN1I6SSaCJJBNwBST58Cs8aBch09psDe2AwnZB00kKA4WutKoc0NhlR6vcqSC0JsgSxh14SrJjBqnc9aAC56v3lbVi+2OjaFvmjYAmcN6g0pt/tt7a0SgSeB6Jp/M8sVJbyzzbWTfkKO42PNKO6q0z1M3GrJ3GbO6WHVK0MU/wU4dtF1R4jT7vpPJuk7fnOVCYTUOxTVge/aaL/SqB9tffqIA0JpsG0niFAL4ntEZCJOqtakYDxUugvhaRXU89fwZBxxe7IJwIBAw==" />
+
+ <application android:hasCode="false">
+ </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_verifier_good/res/values/strings.xml b/core/tests/coretests/apks/install_verifier_good/res/values/strings.xml
new file mode 100644
index 0000000..3b8b3b1
--- /dev/null
+++ b/core/tests/coretests/apks/install_verifier_good/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Just need this dummy file to have something to build. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="dummy">dummy</string>
+</resources>
diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
index 6e5f856..113f0f7 100644
--- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
+++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@@ -156,6 +156,7 @@
}
ret.packageName = pkg.packageName;
ret.installLocation = pkg.installLocation;
+ ret.verifiers = pkg.verifiers;
ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation,
archiveFilePath, flags, threshold);
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 05f7cf0..eb135b7 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -72,6 +72,7 @@
import android.content.pm.UserInfo;
import android.content.pm.ManifestDigest;
import android.content.pm.VerifierDeviceIdentity;
+import android.content.pm.VerifierInfo;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -113,6 +114,8 @@
import java.io.InputStream;
import java.io.PrintWriter;
import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -158,6 +161,7 @@
private static final boolean DEBUG_INTENT_MATCHING = false;
private static final boolean DEBUG_PACKAGE_SCANNING = false;
private static final boolean DEBUG_APP_DIR_OBSERVER = false;
+ private static final boolean DEBUG_VERIFY = false;
static final boolean MULTIPLE_APPLICATION_UIDS = true;
private static final int RADIO_UID = Process.PHONE_UID;
@@ -208,6 +212,8 @@
DEFAULT_CONTAINER_PACKAGE,
"com.android.defcontainer.DefaultContainerService");
+ private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
+
private static final String LIB_DIR_NAME = "lib";
static final String mTempContainerPrefix = "smdl2tmp";
@@ -349,7 +355,8 @@
final HashSet<String> mProtectedBroadcasts = new HashSet<String>();
/** List of packages waiting for verification. */
- final SparseArray<InstallArgs> mPendingVerification = new SparseArray<InstallArgs>();
+ final SparseArray<PackageVerificationState> mPendingVerification
+ = new SparseArray<PackageVerificationState>();
final ArrayList<PackageParser.Package> mDeferredDexOpt =
new ArrayList<PackageParser.Package>();
@@ -427,6 +434,8 @@
final SparseArray<PostInstallData> mRunningInstalls = new SparseArray<PostInstallData>();
int mNextInstallToken = 1; // nonzero; will be wrapped back to 1 when ++ overflows
+ private final String mRequiredVerifierPackage;
+
class PackageHandler extends Handler {
private boolean mBound = false;
final ArrayList<HandlerParams> mPendingInstalls =
@@ -740,9 +749,10 @@
} break;
case CHECK_PENDING_VERIFICATION: {
final int verificationId = msg.arg1;
- final InstallArgs args = mPendingVerification.get(verificationId);
+ final PackageVerificationState state = mPendingVerification.get(verificationId);
- if (args != null) {
+ if (state != null) {
+ final InstallArgs args = state.getInstallArgs();
Slog.i(TAG, "Validation timed out for " + args.packageURI.toString());
mPendingVerification.remove(verificationId);
@@ -756,32 +766,39 @@
}
case PACKAGE_VERIFIED: {
final int verificationId = msg.arg1;
- final boolean verified = msg.arg2 == 1 ? true : false;
- final InstallArgs args = mPendingVerification.get(verificationId);
- if (args == null) {
+ final PackageVerificationState state = mPendingVerification.get(verificationId);
+ if (state == null) {
Slog.w(TAG, "Invalid validation token " + verificationId + " received");
break;
}
- mPendingVerification.remove(verificationId);
+ final PackageVerificationResponse response = (PackageVerificationResponse) msg.obj;
- int ret;
- if (verified) {
- ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
- try {
- ret = args.copyApk(mContainerService, true);
- } catch (RemoteException e) {
- Slog.e(TAG, "Could not contact the ContainerService");
+ state.setVerifierResponse(response.callerUid, response.code);
+
+ if (state.isVerificationComplete()) {
+ mPendingVerification.remove(verificationId);
+
+ final InstallArgs args = state.getInstallArgs();
+
+ int ret;
+ if (state.isInstallAllowed()) {
+ ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+ try {
+ ret = args.copyApk(mContainerService, true);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Could not contact the ContainerService");
+ }
+ } else {
+ ret = PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE;
}
- } else {
- ret = PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE;
+
+ processPendingInstall(args, ret);
+
+ mHandler.sendEmptyMessage(MCS_UNBIND);
}
- processPendingInstall(args, ret);
-
- mHandler.sendEmptyMessage(MCS_UNBIND);
-
break;
}
}
@@ -1134,10 +1151,49 @@
// are all flushed. Not really needed, but keeps things nice and
// tidy.
Runtime.getRuntime().gc();
+
+ mRequiredVerifierPackage = getRequiredVerifierLPr();
} // synchronized (mPackages)
} // synchronized (mInstallLock)
}
+ private String getRequiredVerifierLPr() {
+ final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
+ final List<ResolveInfo> receivers = queryIntentReceivers(verification, PACKAGE_MIME_TYPE,
+ PackageManager.GET_DISABLED_COMPONENTS);
+
+ String requiredVerifier = null;
+
+ final int N = receivers.size();
+ for (int i = 0; i < N; i++) {
+ final ResolveInfo info = receivers.get(i);
+
+ if (info.activityInfo == null) {
+ continue;
+ }
+
+ final String packageName = info.activityInfo.packageName;
+
+ final PackageSetting ps = mSettings.mPackages.get(packageName);
+ if (ps == null) {
+ continue;
+ }
+
+ if (!ps.grantedPermissions
+ .contains(android.Manifest.permission.PACKAGE_VERIFICATION_AGENT)) {
+ continue;
+ }
+
+ if (requiredVerifier != null) {
+ throw new RuntimeException("There can be only one required verifier");
+ }
+
+ requiredVerifier = packageName;
+ }
+
+ return requiredVerifier;
+ }
+
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
@@ -4857,17 +4913,110 @@
}
@Override
- public void verifyPendingInstall(int id, int verificationCode)
- throws RemoteException {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.PACKAGE_VERIFICATION_AGENT, null);
-
+ public void verifyPendingInstall(int id, int verificationCode) throws RemoteException {
final Message msg = mHandler.obtainMessage(PACKAGE_VERIFIED);
+ final PackageVerificationResponse response = new PackageVerificationResponse(
+ verificationCode, Binder.getCallingUid());
msg.arg1 = id;
- msg.arg2 = verificationCode;
+ msg.obj = response;
mHandler.sendMessage(msg);
}
+ private ComponentName matchComponentForVerifier(String packageName,
+ List<ResolveInfo> receivers) {
+ ActivityInfo targetReceiver = null;
+
+ final int NR = receivers.size();
+ for (int i = 0; i < NR; i++) {
+ final ResolveInfo info = receivers.get(i);
+ if (info.activityInfo == null) {
+ continue;
+ }
+
+ if (packageName.equals(info.activityInfo.packageName)) {
+ targetReceiver = info.activityInfo;
+ break;
+ }
+ }
+
+ if (targetReceiver == null) {
+ return null;
+ }
+
+ return new ComponentName(targetReceiver.packageName, targetReceiver.name);
+ }
+
+ private List<ComponentName> matchVerifiers(PackageInfoLite pkgInfo,
+ List<ResolveInfo> receivers, final PackageVerificationState verificationState) {
+ if (pkgInfo.verifiers.length == 0) {
+ return null;
+ }
+
+ final int N = pkgInfo.verifiers.length;
+ final List<ComponentName> sufficientVerifiers = new ArrayList<ComponentName>(N + 1);
+ for (int i = 0; i < N; i++) {
+ final VerifierInfo verifierInfo = pkgInfo.verifiers[i];
+
+ final ComponentName comp = matchComponentForVerifier(verifierInfo.packageName,
+ receivers);
+ if (comp == null) {
+ continue;
+ }
+
+ final int verifierUid = getUidForVerifier(verifierInfo);
+ if (verifierUid == -1) {
+ continue;
+ }
+
+ if (DEBUG_VERIFY) {
+ Slog.d(TAG, "Added sufficient verifier " + verifierInfo.packageName
+ + " with the correct signature");
+ }
+ sufficientVerifiers.add(comp);
+ verificationState.addSufficientVerifier(verifierUid);
+ }
+
+ return sufficientVerifiers;
+ }
+
+ private int getUidForVerifier(VerifierInfo verifierInfo) {
+ synchronized (mPackages) {
+ final PackageParser.Package pkg = mPackages.get(verifierInfo.packageName);
+ if (pkg == null) {
+ return -1;
+ } else if (pkg.mSignatures.length != 1) {
+ Slog.i(TAG, "Verifier package " + verifierInfo.packageName
+ + " has more than one signature; ignoring");
+ return -1;
+ }
+
+ /*
+ * If the public key of the package's signature does not match
+ * our expected public key, then this is a different package and
+ * we should skip.
+ */
+
+ final byte[] expectedPublicKey;
+ try {
+ final Signature verifierSig = pkg.mSignatures[0];
+ final PublicKey publicKey = verifierSig.getPublicKey();
+ expectedPublicKey = publicKey.getEncoded();
+ } catch (CertificateException e) {
+ return -1;
+ }
+
+ final byte[] actualPublicKey = verifierInfo.publicKey.getEncoded();
+
+ if (!Arrays.equals(actualPublicKey, expectedPublicKey)) {
+ Slog.i(TAG, "Verifier package " + verifierInfo.packageName
+ + " does not have the expected public key; ignoring");
+ return -1;
+ }
+
+ return pkg.applicationInfo.uid;
+ }
+ }
+
public void finishPackageInstall(int token) {
enforceSystemOrRoot("Only the system is allowed to finish installs");
@@ -5237,9 +5386,11 @@
*/
public void handleStartCopy() throws RemoteException {
int ret = PackageManager.INSTALL_SUCCEEDED;
- boolean fwdLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
- boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0;
- boolean onInt = (flags & PackageManager.INSTALL_INTERNAL) != 0;
+ final boolean fwdLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
+ final boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0;
+ final boolean onInt = (flags & PackageManager.INSTALL_INTERNAL) != 0;
+ PackageInfoLite pkgLite = null;
+
if (onInt && onSd) {
// Check if both bits are set.
Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");
@@ -5261,7 +5412,6 @@
}
// Remote call to find out default install location
- final PackageInfoLite pkgLite;
try {
mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI,
Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -5304,21 +5454,27 @@
}
final InstallArgs args = createInstallArgs(this);
+ mArgs = args;
+
if (ret == PackageManager.INSTALL_SUCCEEDED) {
/*
* Determine if we have any installed package verifiers. If we
* do, then we'll defer to them to verify the packages.
*/
- final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION,
- packageURI);
- verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ final int requiredUid = mRequiredVerifierPackage == null ? -1
+ : getPackageUid(mRequiredVerifierPackage);
+ if (requiredUid != -1 && isVerificationEnabled()) {
+ final Intent verification = new Intent(
+ Intent.ACTION_PACKAGE_NEEDS_VERIFICATION, packageURI);
+ verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- final List<ResolveInfo> receivers = queryIntentReceivers(verification, null,
- PackageManager.GET_DISABLED_COMPONENTS);
- if (isVerificationEnabled() && receivers.size() > 0) {
- if (DEBUG_INSTALL) {
+ final List<ResolveInfo> receivers = queryIntentReceivers(verification, null,
+ PackageManager.GET_DISABLED_COMPONENTS);
+
+ if (DEBUG_VERIFY) {
Slog.d(TAG, "Found " + receivers.size() + " verifiers for intent "
- + verification.toString());
+ + verification.toString() + " with " + pkgLite.verifiers.length
+ + " optional verifiers");
}
final int verificationId = mPendingVerificationToken++;
@@ -5335,35 +5491,70 @@
verificationURI);
}
- mPendingVerification.append(verificationId, args);
+ final PackageVerificationState verificationState = new PackageVerificationState(
+ requiredUid, args);
+
+ mPendingVerification.append(verificationId, verificationState);
+
+ final List<ComponentName> sufficientVerifiers = matchVerifiers(pkgLite,
+ receivers, verificationState);
/*
- * Send the intent to the registered verification agents,
- * but only start the verification timeout after the target
- * BroadcastReceivers have run.
+ * If any sufficient verifiers were listed in the package
+ * manifest, attempt to ask them.
*/
- mContext.sendOrderedBroadcast(verification,
- android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final Message msg = mHandler
- .obtainMessage(CHECK_PENDING_VERIFICATION);
- msg.arg1 = verificationId;
- mHandler.sendMessageDelayed(msg, getVerificationTimeout());
- }
- },
- null, 0, null, null);
+ if (sufficientVerifiers != null) {
+ final int N = sufficientVerifiers.size();
+ if (N == 0) {
+ Slog.i(TAG, "Additional verifiers required, but none installed.");
+ ret = PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE;
+ } else {
+ for (int i = 0; i < N; i++) {
+ final ComponentName verifierComponent = sufficientVerifiers.get(i);
+
+ final Intent sufficientIntent = new Intent(verification);
+ sufficientIntent.setComponent(verifierComponent);
+
+ mContext.sendBroadcast(sufficientIntent);
+ }
+ }
+ }
+
+ final ComponentName requiredVerifierComponent = matchComponentForVerifier(
+ mRequiredVerifierPackage, receivers);
+ if (ret == PackageManager.INSTALL_SUCCEEDED
+ && mRequiredVerifierPackage != null) {
+ /*
+ * Send the intent to the required verification agent,
+ * but only start the verification timeout after the
+ * target BroadcastReceivers have run.
+ */
+ verification.setComponent(requiredVerifierComponent);
+ mContext.sendOrderedBroadcast(verification,
+ android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final Message msg = mHandler
+ .obtainMessage(CHECK_PENDING_VERIFICATION);
+ msg.arg1 = verificationId;
+ mHandler.sendMessageDelayed(msg, getVerificationTimeout());
+ }
+ }, null, 0, null, null);
+
+ /*
+ * We don't want the copy to proceed until verification
+ * succeeds, so null out this field.
+ */
+ mArgs = null;
+ }
} else {
- // Create copy only if we are not in an erroneous state.
- // Remote call to initiate copy using temporary file
- mArgs = args;
+ /*
+ * No package verification is enabled, so immediately start
+ * the remote call to initiate copy using temporary file.
+ */
ret = args.copyApk(mContainerService, true);
}
- } else {
- // There was an error, so let the processPendingInstall() break
- // the bad news... uh, through a call in handleReturnCode()
- mArgs = args;
}
mRet = ret;
@@ -7549,6 +7740,8 @@
public static final int DUMP_PROVIDERS = 1 << 7;
+ public static final int DUMP_VERIFIERS = 1 << 8;
+
public static final int OPTION_SHOW_FILTERS = 1 << 0;
private int mTypes;
@@ -7641,6 +7834,7 @@
pw.println(" p[ackages]: dump installed packages");
pw.println(" s[hared-users]: dump shared user IDs");
pw.println(" m[essages]: print collected runtime messages");
+ pw.println(" v[erifiers]: print package verifier info");
pw.println(" <package.name>: info about given package");
return;
} else if ("-f".equals(opt)) {
@@ -7673,11 +7867,24 @@
dumpState.setDump(DumpState.DUMP_PROVIDERS);
} else if ("m".equals(cmd) || "messages".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_MESSAGES);
+ } else if ("v".equals(cmd) || "verifiers".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_VERIFIERS);
}
}
// reader
synchronized (mPackages) {
+ if (dumpState.isDumping(DumpState.DUMP_VERIFIERS) && packageName == null) {
+ if (dumpState.onTitlePrinted())
+ pw.println(" ");
+ pw.println("Verifiers:");
+ pw.print(" Required: ");
+ pw.print(mRequiredVerifierPackage);
+ pw.print(" (uid=");
+ pw.print(getPackageUid(mRequiredVerifierPackage));
+ pw.println(")");
+ }
+
if (dumpState.isDumping(DumpState.DUMP_LIBS) && packageName == null) {
if (dumpState.onTitlePrinted())
pw.println(" ");
diff --git a/services/java/com/android/server/pm/PackageVerificationResponse.java b/services/java/com/android/server/pm/PackageVerificationResponse.java
new file mode 100644
index 0000000..b2ae0dd
--- /dev/null
+++ b/services/java/com/android/server/pm/PackageVerificationResponse.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+public class PackageVerificationResponse {
+ public final int code;
+
+ public final int callerUid;
+
+ public PackageVerificationResponse(int code, int callerUid) {
+ this.code = code;
+ this.callerUid = callerUid;
+ }
+}
diff --git a/services/java/com/android/server/pm/PackageVerificationState.java b/services/java/com/android/server/pm/PackageVerificationState.java
new file mode 100644
index 0000000..e5b89c1
--- /dev/null
+++ b/services/java/com/android/server/pm/PackageVerificationState.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import com.android.server.pm.PackageManagerService.InstallArgs;
+
+import android.content.pm.PackageManager;
+import android.util.SparseBooleanArray;
+
+/**
+ * Tracks the package verification state for a particular package. Each package
+ * verification has a required verifier and zero or more sufficient verifiers.
+ * Only one of the sufficient verifier list must return affirmative to allow the
+ * package to be considered verified. If there are zero sufficient verifiers,
+ * then package verification is considered complete.
+ */
+class PackageVerificationState {
+ private final InstallArgs mArgs;
+
+ private final SparseBooleanArray mSufficientVerifierUids;
+
+ private final int mRequiredVerifierUid;
+
+ private boolean mSufficientVerificationComplete;
+
+ private boolean mSufficientVerificationPassed;
+
+ private boolean mRequiredVerificationComplete;
+
+ private boolean mRequiredVerificationPassed;
+
+ /**
+ * Create a new package verification state where {@code requiredVerifierUid}
+ * is the user ID for the package that must reply affirmative before things
+ * can continue.
+ *
+ * @param requiredVerifierUid user ID of required package verifier
+ * @param args
+ */
+ public PackageVerificationState(int requiredVerifierUid, InstallArgs args) {
+ mRequiredVerifierUid = requiredVerifierUid;
+ mArgs = args;
+ mSufficientVerifierUids = new SparseBooleanArray();
+ }
+
+ public InstallArgs getInstallArgs() {
+ return mArgs;
+ }
+
+ /**
+ * Add a verifier which is added to our sufficient list.
+ *
+ * @param uid user ID of sufficient verifier
+ */
+ public void addSufficientVerifier(int uid) {
+ mSufficientVerifierUids.put(uid, true);
+ }
+
+ /**
+ * Should be called when a verification is received from an agent so the
+ * state of the package verification can be tracked.
+ *
+ * @param uid user ID of the verifying agent
+ * @return {@code true} if the verifying agent actually exists in our list
+ */
+ public boolean setVerifierResponse(int uid, int code) {
+ if (uid == mRequiredVerifierUid) {
+ mRequiredVerificationComplete = true;
+ switch (code) {
+ case PackageManager.VERIFICATION_ALLOW_WITHOUT_SUFFICIENT:
+ mSufficientVerifierUids.clear();
+ // fall through
+ case PackageManager.VERIFICATION_ALLOW:
+ mRequiredVerificationPassed = true;
+ break;
+ default:
+ mRequiredVerificationPassed = false;
+ }
+ return true;
+ } else {
+ if (mSufficientVerifierUids.get(uid)) {
+ if (code == PackageManager.VERIFICATION_ALLOW) {
+ mSufficientVerificationComplete = true;
+ mSufficientVerificationPassed = true;
+ }
+
+ mSufficientVerifierUids.delete(uid);
+ if (mSufficientVerifierUids.size() == 0) {
+ mSufficientVerificationComplete = true;
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns whether verification is considered complete. This means that the
+ * required verifier and at least one of the sufficient verifiers has
+ * returned a positive verification.
+ *
+ * @return {@code true} when verification is considered complete
+ */
+ public boolean isVerificationComplete() {
+ if (!mRequiredVerificationComplete) {
+ return false;
+ }
+
+ if (mSufficientVerifierUids.size() == 0) {
+ return true;
+ }
+
+ return mSufficientVerificationComplete;
+ }
+
+ /**
+ * Returns whether installation should be allowed. This should only be
+ * called after {@link #isVerificationComplete()} returns {@code true}.
+ *
+ * @return {@code true} if installation should be allowed
+ */
+ public boolean isInstallAllowed() {
+ if (!mRequiredVerificationPassed) {
+ return false;
+ }
+
+ if (mSufficientVerificationComplete) {
+ return mSufficientVerificationPassed;
+ }
+
+ return true;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java
new file mode 100644
index 0000000..ebd3633
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageVerificationStateTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.content.pm.PackageManager;
+import com.android.server.pm.PackageVerificationState;
+
+import android.test.AndroidTestCase;
+
+public class PackageVerificationStateTest extends AndroidTestCase {
+ private static final int REQUIRED_UID = 1948;
+
+ private static final int SUFFICIENT_UID_1 = 1005;
+
+ private static final int SUFFICIENT_UID_2 = 8938;
+
+ public void testPackageVerificationState_OnlyRequiredVerifier_AllowedInstall() {
+ PackageVerificationState state = new PackageVerificationState(REQUIRED_UID, null);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.setVerifierResponse(REQUIRED_UID, PackageManager.VERIFICATION_ALLOW);
+
+ assertTrue("Verification should be considered complete now",
+ state.isVerificationComplete());
+
+ assertTrue("Installation should be marked as allowed",
+ state.isInstallAllowed());
+ }
+
+ public void testPackageVerificationState_OnlyRequiredVerifier_DeniedInstall() {
+ PackageVerificationState state = new PackageVerificationState(REQUIRED_UID, null);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.setVerifierResponse(REQUIRED_UID, PackageManager.VERIFICATION_REJECT);
+
+ assertTrue("Verification should be considered complete now",
+ state.isVerificationComplete());
+
+ assertFalse("Installation should be marked as allowed",
+ state.isInstallAllowed());
+ }
+
+ public void testPackageVerificationState_RequiredAndOneSufficient_RequiredDeniedInstall() {
+ PackageVerificationState state = new PackageVerificationState(REQUIRED_UID, null);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.addSufficientVerifier(SUFFICIENT_UID_1);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.setVerifierResponse(SUFFICIENT_UID_1, PackageManager.VERIFICATION_ALLOW);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.setVerifierResponse(REQUIRED_UID, PackageManager.VERIFICATION_REJECT);
+
+ assertTrue("Verification should be considered complete now",
+ state.isVerificationComplete());
+
+ assertFalse("Installation should be marked as allowed",
+ state.isInstallAllowed());
+ }
+
+ public void testPackageVerificationState_RequiredAndOneSufficient_SufficientDeniedInstall() {
+ PackageVerificationState state = new PackageVerificationState(REQUIRED_UID, null);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.addSufficientVerifier(SUFFICIENT_UID_1);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.setVerifierResponse(SUFFICIENT_UID_1, PackageManager.VERIFICATION_REJECT);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.setVerifierResponse(REQUIRED_UID, PackageManager.VERIFICATION_ALLOW);
+
+ assertTrue("Verification should be considered complete now",
+ state.isVerificationComplete());
+
+ assertFalse("Installation should be marked as allowed",
+ state.isInstallAllowed());
+ }
+
+ public void testPackageVerificationState_RequiredAndTwoSufficient_OneSufficientIsEnough() {
+ PackageVerificationState state = new PackageVerificationState(REQUIRED_UID, null);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.addSufficientVerifier(SUFFICIENT_UID_1);
+ state.addSufficientVerifier(SUFFICIENT_UID_2);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.setVerifierResponse(SUFFICIENT_UID_1, PackageManager.VERIFICATION_ALLOW);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.setVerifierResponse(REQUIRED_UID, PackageManager.VERIFICATION_ALLOW);
+
+ assertTrue("Verification should be considered complete now",
+ state.isVerificationComplete());
+
+ assertTrue("Installation should be marked as allowed",
+ state.isInstallAllowed());
+ }
+
+ public void testPackageVerificationState_RequiredAndTwoSufficient_SecondSufficientIsEnough() {
+ PackageVerificationState state = new PackageVerificationState(REQUIRED_UID, null);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.addSufficientVerifier(SUFFICIENT_UID_1);
+ state.addSufficientVerifier(SUFFICIENT_UID_2);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.setVerifierResponse(REQUIRED_UID, PackageManager.VERIFICATION_ALLOW);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.setVerifierResponse(SUFFICIENT_UID_1, PackageManager.VERIFICATION_REJECT);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.setVerifierResponse(SUFFICIENT_UID_2, PackageManager.VERIFICATION_ALLOW);
+
+ assertTrue("Verification should be considered complete now",
+ state.isVerificationComplete());
+
+ assertTrue("Installation should be marked as allowed",
+ state.isInstallAllowed());
+ }
+
+ public void testPackageVerificationState_RequiredAndTwoSufficient_RequiredOverrides() {
+ PackageVerificationState state = new PackageVerificationState(REQUIRED_UID, null);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.addSufficientVerifier(SUFFICIENT_UID_1);
+ state.addSufficientVerifier(SUFFICIENT_UID_2);
+
+ assertFalse("Verification should not be marked as complete yet",
+ state.isVerificationComplete());
+
+ state.setVerifierResponse(REQUIRED_UID,
+ PackageManager.VERIFICATION_ALLOW_WITHOUT_SUFFICIENT);
+
+ assertTrue("Verification should be marked as complete immediately",
+ state.isVerificationComplete());
+
+ assertTrue("Installation should be marked as allowed",
+ state.isInstallAllowed());
+
+ state.setVerifierResponse(SUFFICIENT_UID_1, PackageManager.VERIFICATION_REJECT);
+
+ assertTrue("Verification should still be marked as completed",
+ state.isVerificationComplete());
+
+ assertTrue("Installation should be marked as allowed still",
+ state.isInstallAllowed());
+
+ state.setVerifierResponse(SUFFICIENT_UID_2, PackageManager.VERIFICATION_ALLOW);
+
+ assertTrue("Verification should still be complete",
+ state.isVerificationComplete());
+
+ assertTrue("Installation should be marked as allowed still",
+ state.isInstallAllowed());
+ }
+}
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 3525abe..58680ea 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -36,9 +36,11 @@
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.Signature;
import android.content.pm.UserInfo;
import android.content.pm.ManifestDigest;
import android.content.pm.VerifierDeviceIdentity;
+import android.content.pm.VerifierInfo;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;