IntentFilterVerificationReceiver.java revision 6a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.statementservice;
18
19import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.pm.PackageManager;
23import android.content.pm.PackageManager.NameNotFoundException;
24import android.os.Bundle;
25import android.os.Handler;
26import android.os.ResultReceiver;
27import android.util.Log;
28import android.util.Patterns;
29
30import com.android.statementservice.retriever.Utils;
31
32import java.net.MalformedURLException;
33import java.net.URL;
34import java.util.ArrayList;
35import java.util.Collections;
36import java.util.List;
37import java.util.regex.Pattern;
38
39/**
40 * Receives {@link Intent#ACTION_INTENT_FILTER_NEEDS_VERIFICATION} broadcast and calls
41 * {@link DirectStatementService} to verify the request. Calls
42 * {@link PackageManager#verifyIntentFilter} to notify {@link PackageManager} the result of the
43 * verification.
44 *
45 * This implementation of the API will send a HTTP request for each host specified in the query.
46 * To avoid overwhelming the network at app install time, {@code MAX_HOSTS_PER_REQUEST} limits
47 * the maximum number of hosts in a query. If a query contains more than
48 * {@code MAX_HOSTS_PER_REQUEST} hosts, it will fail immediately without making any HTTP request
49 * and call {@link PackageManager#verifyIntentFilter} with
50 * {@link PackageManager#INTENT_FILTER_VERIFICATION_FAILURE}.
51 */
52public final class IntentFilterVerificationReceiver extends BroadcastReceiver {
53    private static final String TAG = IntentFilterVerificationReceiver.class.getSimpleName();
54
55    private static final Integer MAX_HOSTS_PER_REQUEST = 10;
56
57    private static final String HANDLE_ALL_URLS_RELATION
58            = "delegate_permission/common.handle_all_urls";
59
60    private static final String ANDROID_ASSET_FORMAT = "{\"namespace\": \"android_app\", "
61            + "\"package_name\": \"%s\", \"sha256_cert_fingerprints\": [\"%s\"]}";
62    private static final String WEB_ASSET_FORMAT = "{\"namespace\": \"web\", \"site\": \"%s\"}";
63    private static final Pattern ANDROID_PACKAGE_NAME_PATTERN =
64            Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*)*$");
65    private static final String TOO_MANY_HOSTS_FORMAT =
66            "Request contains %d hosts which is more than the allowed %d.";
67
68    private static void sendErrorToPackageManager(PackageManager packageManager,
69            int verificationId) {
70        packageManager.verifyIntentFilter(verificationId,
71                PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
72                Collections.<String>emptyList());
73    }
74
75    @Override
76    public void onReceive(Context context, Intent intent) {
77        final String action = intent.getAction();
78        if (Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION.equals(action)) {
79            Bundle inputExtras = intent.getExtras();
80            if (inputExtras != null) {
81                Intent serviceIntent = new Intent(context, DirectStatementService.class);
82                serviceIntent.setAction(DirectStatementService.CHECK_ALL_ACTION);
83
84                int verificationId = inputExtras.getInt(
85                        PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_ID);
86                String scheme = inputExtras.getString(
87                        PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME);
88                String hosts = inputExtras.getString(
89                        PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_HOSTS);
90                String packageName = inputExtras.getString(
91                        PackageManager.EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME);
92
93                Log.i(TAG, "Verify IntentFilter for " + hosts);
94
95                Bundle extras = new Bundle();
96                extras.putString(DirectStatementService.EXTRA_RELATION, HANDLE_ALL_URLS_RELATION);
97
98                String[] hostList = hosts.split(" ");
99                if (hostList.length > MAX_HOSTS_PER_REQUEST) {
100                    Log.w(TAG, String.format(TOO_MANY_HOSTS_FORMAT,
101                            hostList.length, MAX_HOSTS_PER_REQUEST));
102                    sendErrorToPackageManager(context.getPackageManager(), verificationId);
103                    return;
104                }
105
106                try {
107                    ArrayList<String> sourceAssets = new ArrayList<String>();
108                    for (String host : hostList) {
109                        sourceAssets.add(createWebAssetString(scheme, host));
110                    }
111                    extras.putStringArrayList(DirectStatementService.EXTRA_SOURCE_ASSET_DESCRIPTORS,
112                            sourceAssets);
113                } catch (MalformedURLException e) {
114                    Log.w(TAG, "Error when processing input host: " + e.getMessage());
115                    sendErrorToPackageManager(context.getPackageManager(), verificationId);
116                    return;
117                }
118                try {
119                    extras.putString(DirectStatementService.EXTRA_TARGET_ASSET_DESCRIPTOR,
120                            createAndroidAssetString(context, packageName));
121                } catch (NameNotFoundException e) {
122                    Log.w(TAG, "Error when processing input Android package: " + e.getMessage());
123                    sendErrorToPackageManager(context.getPackageManager(), verificationId);
124                    return;
125                }
126                extras.putParcelable(DirectStatementService.EXTRA_RESULT_RECEIVER,
127                        new IsAssociatedResultReceiver(
128                                new Handler(), context.getPackageManager(), verificationId));
129
130                serviceIntent.putExtras(extras);
131                context.startService(serviceIntent);
132            }
133        } else {
134            Log.w(TAG, "Intent action not supported: " + action);
135        }
136    }
137
138    private String createAndroidAssetString(Context context, String packageName)
139            throws NameNotFoundException {
140        if (!ANDROID_PACKAGE_NAME_PATTERN.matcher(packageName).matches()) {
141            throw new NameNotFoundException("Input package name is not valid.");
142        }
143
144        List<String> certFingerprints =
145                Utils.getCertFingerprintsFromPackageManager(packageName, context);
146
147        return String.format(ANDROID_ASSET_FORMAT, packageName,
148                Utils.joinStrings("\", \"", certFingerprints));
149    }
150
151    private String createWebAssetString(String scheme, String host) throws MalformedURLException {
152        if (!Patterns.DOMAIN_NAME.matcher(host).matches()) {
153            throw new MalformedURLException("Input host is not valid.");
154        }
155        if (!scheme.equals("http") && !scheme.equals("https")) {
156            throw new MalformedURLException("Input scheme is not valid.");
157        }
158
159        return String.format(WEB_ASSET_FORMAT, new URL(scheme, host, "").toString());
160    }
161
162    /**
163     * Receives the result of {@code StatementService.CHECK_ACTION} from
164     * {@link DirectStatementService} and passes it back to {@link PackageManager}.
165     */
166    private static class IsAssociatedResultReceiver extends ResultReceiver {
167
168        private final int mVerificationId;
169        private final PackageManager mPackageManager;
170
171        public IsAssociatedResultReceiver(Handler handler, PackageManager packageManager,
172                int verificationId) {
173            super(handler);
174            mVerificationId = verificationId;
175            mPackageManager = packageManager;
176        }
177
178        @Override
179        protected void onReceiveResult(int resultCode, Bundle resultData) {
180            if (resultCode == DirectStatementService.RESULT_SUCCESS) {
181                if (resultData.getBoolean(DirectStatementService.IS_ASSOCIATED)) {
182                    mPackageManager.verifyIntentFilter(mVerificationId,
183                            PackageManager.INTENT_FILTER_VERIFICATION_SUCCESS,
184                            Collections.<String>emptyList());
185                } else {
186                    mPackageManager.verifyIntentFilter(mVerificationId,
187                            PackageManager.INTENT_FILTER_VERIFICATION_FAILURE,
188                            resultData.getStringArrayList(DirectStatementService.FAILED_SOURCES));
189                }
190            } else {
191                sendErrorToPackageManager(mPackageManager, mVerificationId);
192            }
193        }
194    }
195}
196