Implement IntentFilter verification service.

This commit adds a verifier that verifies a host delegates permission for
an app to handle Url for the host using the Statement protocol.

- Implements the Statement protocol
-- The protocol defines a file format that represents statements.
-- The protocol defines where each asset type should put their statement
declaration. For web asset, the statement file should be hosted at
<scheme>://<host>:<port>/.well-known/associations.json.

- Implements IntentFilterVerificationReceiver, an interface between
StatementService and PackageManager. PackageManager will send a
broadcast with action Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION.
The service will process the request and returns the results by calling
PackageManager.verifyIntentFilter().

To verify an IntentFilter like this defined in Android app com.test.app
<intent-filter>
  <data android:scheme="https" />
  <data android:host="www.test.com" />
  <data android:pathPattern=".*"/>
</intent-filter>

The service will try to retrieve the statement file from
https://www.test.com:443/.well-known/associations.json and try to find
a JSON object equivalent to
{'relation': ['delegate_permission/common.handle_all_urls'],
 'target': {'namespace': 'android_app',
            'package_name': 'com.test.app',
            'sha256_cert_fingerprints': [APP_CERT_FP]}}
The entry should have the correct relation, package name, and
certificate sha256 fingerprint.

Because this implementation will send a HTTP request for each host
specified in the intent-filter in AndroidManifest.xml, to avoid overwhelming
the network at app install time, we limit the maximum number of hosts we will
verify for a single app to 10. Any app with more than 10 hosts in the
autoVerify=true intent-filter won't be auto verified.

Change-Id: I787c9d176e4110aa441eb5fe4fa9651a071c6610
diff --git a/packages/StatementService/src/com/android/statementservice/IntentFilterVerificationReceiver.java b/packages/StatementService/src/com/android/statementservice/IntentFilterVerificationReceiver.java
new file mode 100644
index 0000000..712347a
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/IntentFilterVerificationReceiver.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2015 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.statementservice;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.ResultReceiver;
+import android.util.Log;
+import android.util.Patterns;
+
+import com.android.statementservice.retriever.Utils;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Receives {@link Intent#ACTION_INTENT_FILTER_NEEDS_VERIFICATION} broadcast and calls
+ * {@link DirectStatementService} to verify the request. Calls
+ * {@link PackageManager#verifyIntentFilter} to notify {@link PackageManager} the result of the
+ * verification.
+ *
+ * This implementation of the API will send a HTTP request for each host specified in the query.
+ * To avoid overwhelming the network at app install time, {@code MAX_HOSTS_PER_REQUEST} limits
+ * the maximum number of hosts in a query. If a query contains more than
+ * {@code MAX_HOSTS_PER_REQUEST} hosts, it will fail immediately without making any HTTP request
+ * and call {@link PackageManager#verifyIntentFilter} with
+ * {@link PackageManager#INTENT_FILTER_VERIFICATION_FAILURE}.
+ */
+public final class IntentFilterVerificationReceiver extends BroadcastReceiver {
+    private static final String TAG = IntentFilterVerificationReceiver.class.getSimpleName();
+
+    private static final Integer MAX_HOSTS_PER_REQUEST = 10;
+
+    private static final String HANDLE_ALL_URLS_RELATION
+            = "delegate_permission/common.handle_all_urls";
+
+    private static final String ANDROID_ASSET_FORMAT = "{\"namespace\": \"android_app\", "
+            + "\"package_name\": \"%s\", \"sha256_cert_fingerprints\": [\"%s\"]}";
+    private static final String WEB_ASSET_FORMAT = "{\"namespace\": \"web\", \"site\": \"%s\"}";
+    private static final Pattern ANDROID_PACKAGE_NAME_PATTERN =
+            Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*)*$");
+    private static final String TOO_MANY_HOSTS_FORMAT =
+            "Request contains %d hosts which is more than the allowed %d.";
+
+    private static void sendErrorToPackageManager(PackageManager packageManager,
+            int verificationId) {
+        packageManager.verifyIntentFilter(verificationId,
+                PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
+                Collections.<String>emptyList());
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final String action = intent.getAction();
+        if (Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION.equals(action)) {
+            Bundle inputExtras = intent.getExtras();
+            if (inputExtras != null) {
+                Intent serviceIntent = new Intent(context, DirectStatementService.class);
+                serviceIntent.setAction(DirectStatementService.CHECK_ALL_ACTION);
+
+                int verificationId = inputExtras.getInt(
+                        PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID);
+                String scheme = inputExtras.getString(
+                        PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME);
+                String hosts = inputExtras.getString(
+                        PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS);
+                String packageName = inputExtras.getString(
+                        PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME);
+
+                Log.i(TAG, "Verify IntentFilter for " + hosts);
+
+                Bundle extras = new Bundle();
+                extras.putString(DirectStatementService.EXTRA_RELATION, HANDLE_ALL_URLS_RELATION);
+
+                String[] hostList = hosts.split(" ");
+                if (hostList.length > MAX_HOSTS_PER_REQUEST) {
+                    Log.w(TAG, String.format(TOO_MANY_HOSTS_FORMAT,
+                            hostList.length, MAX_HOSTS_PER_REQUEST));
+                    sendErrorToPackageManager(context.getPackageManager(), verificationId);
+                    return;
+                }
+
+                try {
+                    ArrayList<String> sourceAssets = new ArrayList<String>();
+                    for (String host : hostList) {
+                        sourceAssets.add(createWebAssetString(scheme, host));
+                    }
+                    extras.putStringArrayList(DirectStatementService.EXTRA_SOURCE_ASSET_DESCRIPTORS,
+                            sourceAssets);
+                } catch (MalformedURLException e) {
+                    Log.w(TAG, "Error when processing input host: " + e.getMessage());
+                    sendErrorToPackageManager(context.getPackageManager(), verificationId);
+                    return;
+                }
+                try {
+                    extras.putString(DirectStatementService.EXTRA_TARGET_ASSET_DESCRIPTOR,
+                            createAndroidAssetString(context, packageName));
+                } catch (NameNotFoundException e) {
+                    Log.w(TAG, "Error when processing input Android package: " + e.getMessage());
+                    sendErrorToPackageManager(context.getPackageManager(), verificationId);
+                    return;
+                }
+                extras.putParcelable(DirectStatementService.EXTRA_RESULT_RECEIVER,
+                        new IsAssociatedResultReceiver(
+                                new Handler(), context.getPackageManager(), verificationId));
+
+                serviceIntent.putExtras(extras);
+                context.startService(serviceIntent);
+            }
+        } else {
+            Log.w(TAG, "Intent action not supported: " + action);
+        }
+    }
+
+    private String createAndroidAssetString(Context context, String packageName)
+            throws NameNotFoundException {
+        if (!ANDROID_PACKAGE_NAME_PATTERN.matcher(packageName).matches()) {
+            throw new NameNotFoundException("Input package name is not valid.");
+        }
+
+        List<String> certFingerprints =
+                Utils.getCertFingerprintsFromPackageManager(packageName, context);
+
+        return String.format(ANDROID_ASSET_FORMAT, packageName,
+                Utils.joinStrings("\", \"", certFingerprints));
+    }
+
+    private String createWebAssetString(String scheme, String host) throws MalformedURLException {
+        if (!Patterns.DOMAIN_NAME.matcher(host).matches()) {
+            throw new MalformedURLException("Input host is not valid.");
+        }
+        if (!scheme.equals("http") && !scheme.equals("https")) {
+            throw new MalformedURLException("Input scheme is not valid.");
+        }
+
+        return String.format(WEB_ASSET_FORMAT, new URL(scheme, host, "").toString());
+    }
+
+    /**
+     * Receives the result of {@code StatementService.CHECK_ACTION} from
+     * {@link DirectStatementService} and passes it back to {@link PackageManager}.
+     */
+    private static class IsAssociatedResultReceiver extends ResultReceiver {
+
+        private final int mVerificationId;
+        private final PackageManager mPackageManager;
+
+        public IsAssociatedResultReceiver(Handler handler, PackageManager packageManager,
+                int verificationId) {
+            super(handler);
+            mVerificationId = verificationId;
+            mPackageManager = packageManager;
+        }
+
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            if (resultCode == DirectStatementService.RESULT_SUCCESS) {
+                if (resultData.getBoolean(DirectStatementService.IS_ASSOCIATED)) {
+                    mPackageManager.verifyIntentFilter(mVerificationId,
+                            PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS,
+                            Collections.<String>emptyList());
+                } else {
+                    mPackageManager.verifyIntentFilter(mVerificationId,
+                            PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
+                            resultData.getStringArrayList(DirectStatementService.FAILED_SOURCES));
+                }
+            } else {
+                sendErrorToPackageManager(mPackageManager, mVerificationId);
+            }
+        }
+    }
+}