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/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";