blob: 712347ab4fb7ea8c15c12e79d3ef5b8601e9048f [file] [log] [blame]
Joseph Wen6a34bb22015-02-25 14:00:39 -05001/*
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}