aboutsummaryrefslogtreecommitdiff
path: root/input/autofill
diff options
context:
space:
mode:
authorFelipe Leme <felipeal@google.com>2017-09-21 10:13:07 -0700
committerFelipe Leme <felipeal@google.com>2017-09-25 13:14:04 -0700
commit500b70f82bafaf5d39bb3eb98ee680b90f380798 (patch)
treeee949b452c94c9c3490faf156f8ada99d10f2305 /input/autofill
parent4547cb7002a1c1086752409ba1f2708f7d00ad0e (diff)
downloadandroid-500b70f82bafaf5d39bb3eb98ee680b90f380798.tar.gz
Initial Digital Asset Links support (DAL).
This change uses DAL to verify that an app being autofilled is linked to the web domain set in a node, and fail if the association could not be verified. It also includes some minor improvements, such as: - Clears cache on WebView activity. - Takes an optional url extra in the WebView activity intent. - Uses FillCallback.onFailure() to report some errors. Bug: 66414472 Bug: 66417779 Bug: 66900717 Test: manual verification Change-Id: I2fa96ae61201d7d11aaab168957da2a5685a5764
Diffstat (limited to 'input/autofill')
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/AndroidManifest.xml2
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/CommonUtil.java1
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/WebViewSignInActivity.java19
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AuthActivity.java2
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AutofillHelper.java4
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/MyAutofillService.java34
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/SecurityHelper.java189
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/StructureParser.java41
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/DigitalAssetLinksDataSource.java35
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/SharedPrefsAutofillRepository.java5
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/SharedPrefsDigitalAssetLinksRepository.java63
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/SharedPrefsPackageVerificationRepository.java45
-rw-r--r--input/autofill/AutofillFramework/Application/src/main/res/values/strings.xml1
13 files changed, 376 insertions, 65 deletions
diff --git a/input/autofill/AutofillFramework/Application/src/main/AndroidManifest.xml b/input/autofill/AutofillFramework/Application/src/main/AndroidManifest.xml
index a5abfe86..0af57162 100644
--- a/input/autofill/AutofillFramework/Application/src/main/AndroidManifest.xml
+++ b/input/autofill/AutofillFramework/Application/src/main/AndroidManifest.xml
@@ -18,6 +18,8 @@
android:versionCode="1"
android:versionName="1.0">
+ <uses-permission android:name="android.permission.INTERNET"/>
+
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/CommonUtil.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/CommonUtil.java
index 84b5a976..8c429d01 100644
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/CommonUtil.java
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/CommonUtil.java
@@ -31,6 +31,7 @@ public final class CommonUtil {
public static final String TAG = "AutofillSample";
public static final boolean DEBUG = true;
+ public static final boolean VERBOSE = false;
public static final String EXTRA_DATASET_NAME = "dataset_name";
public static final String EXTRA_FOR_RESPONSE = "for_response";
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/WebViewSignInActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/WebViewSignInActivity.java
index 9ef84280..616529a2 100644
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/WebViewSignInActivity.java
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/app/WebViewSignInActivity.java
@@ -19,9 +19,14 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
+import static com.example.android.autofillframework.CommonUtil.DEBUG;
+import static com.example.android.autofillframework.CommonUtil.TAG;
+
import com.example.android.autofillframework.R;
public class WebViewSignInActivity extends AppCompatActivity {
@@ -38,7 +43,19 @@ public class WebViewSignInActivity extends AppCompatActivity {
setContentView(R.layout.login_webview_activity);
WebView webView = findViewById(R.id.webview);
+ WebSettings webSettings = webView.getSettings();
webView.setWebViewClient(new WebViewClient());
- webView.loadUrl("file:///android_res/raw/sample_form.html");
+ webSettings.setJavaScriptEnabled(true);
+
+ String url = getIntent().getStringExtra("url");
+ if (url == null) {
+ url = "file:///android_res/raw/sample_form.html";
+ }
+ if (DEBUG) Log.d(TAG, "Clearing WebView data");
+ webView.clearHistory();
+ webView.clearFormData();
+ webView.clearCache(true);
+ Log.i(TAG, "Loading URL " + url);
+ webView.loadUrl(url);
}
} \ No newline at end of file
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AuthActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AuthActivity.java
index 015dc68f..631cc0a9 100644
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AuthActivity.java
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AuthActivity.java
@@ -124,7 +124,7 @@ public class AuthActivity extends AppCompatActivity {
Intent intent = getIntent();
boolean forResponse = intent.getBooleanExtra(EXTRA_FOR_RESPONSE, true);
AssistStructure structure = intent.getParcelableExtra(EXTRA_ASSIST_STRUCTURE);
- StructureParser parser = new StructureParser(structure);
+ StructureParser parser = new StructureParser(getApplicationContext(), structure);
parser.parseForFill();
AutofillFieldMetadataCollection autofillFields = parser.getAutofillFields();
int saveTypes = autofillFields.getSaveType();
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AutofillHelper.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AutofillHelper.java
index 0b47cfd1..4c0f173e 100644
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AutofillHelper.java
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/AutofillHelper.java
@@ -38,6 +38,10 @@ import static com.example.android.autofillframework.CommonUtil.TAG;
*/
public final class AutofillHelper {
+ private AutofillHelper() {
+ throw new UnsupportedOperationException("provide static methods only");
+ }
+
/**
* Wraps autofill data in a LoginCredential Dataset object which can then be sent back to the
* client View.
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/MyAutofillService.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/MyAutofillService.java
index f30e91b5..ac030221 100644
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/MyAutofillService.java
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/MyAutofillService.java
@@ -29,7 +29,6 @@ import android.service.autofill.SaveRequest;
import android.util.Log;
import android.view.autofill.AutofillId;
import android.widget.RemoteViews;
-import android.widget.Toast;
import com.example.android.autofillframework.R;
import com.example.android.autofillframework.multidatasetservice.datasource.SharedPrefsAutofillRepository;
@@ -41,8 +40,8 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
-import static com.example.android.autofillframework.CommonUtil.DEBUG;
import static com.example.android.autofillframework.CommonUtil.TAG;
+import static com.example.android.autofillframework.CommonUtil.VERBOSE;
import static com.example.android.autofillframework.CommonUtil.bundleToString;
import static com.example.android.autofillframework.CommonUtil.dumpStructure;
@@ -56,13 +55,13 @@ public class MyAutofillService extends AutofillService {
String packageName = structure.getActivityComponent().getPackageName();
if (!SharedPrefsPackageVerificationRepository.getInstance()
.putPackageSignatures(getApplicationContext(), packageName)) {
- Toast.makeText(getApplicationContext(), R.string.invalid_package_signature,
- Toast.LENGTH_SHORT).show();
+ callback.onFailure(
+ getApplicationContext().getString(R.string.invalid_package_signature));
return;
}
final Bundle data = request.getClientState();
- if (DEBUG) {
- Log.d(TAG, "onFillRequest(): data=" + bundleToString(data));
+ if (VERBOSE) {
+ Log.v(TAG, "onFillRequest(): data=" + bundleToString(data));
dumpStructure(structure);
}
@@ -73,8 +72,17 @@ public class MyAutofillService extends AutofillService {
}
});
// Parse AutoFill data in Activity
- StructureParser parser = new StructureParser(structure);
- parser.parseForFill();
+ StructureParser parser = new StructureParser(getApplicationContext(), structure);
+ // TODO: try / catch on other places (onSave, auth activity, etc...)
+ try {
+ parser.parseForFill();
+ } catch (SecurityException e) {
+ // TODO: handle cases where DAL didn't pass by showing a custom UI asking the user
+ // to confirm the mapping. Might require subclassing SecurityException.
+ Log.w(TAG, "Security exception handling " + request, e);
+ callback.onFailure(e.getMessage());
+ return;
+ }
AutofillFieldMetadataCollection autofillFields = parser.getAutofillFields();
FillResponse.Builder responseBuilder = new FillResponse.Builder();
// Check user's settings for authenticating Responses and Datasets.
@@ -108,16 +116,16 @@ public class MyAutofillService extends AutofillService {
String packageName = structure.getActivityComponent().getPackageName();
if (!SharedPrefsPackageVerificationRepository.getInstance()
.putPackageSignatures(getApplicationContext(), packageName)) {
- Toast.makeText(getApplicationContext(), R.string.invalid_package_signature,
- Toast.LENGTH_SHORT).show();
+ callback.onFailure(
+ getApplicationContext().getString(R.string.invalid_package_signature));
return;
}
final Bundle data = request.getClientState();
- if (DEBUG) {
- Log.d(TAG, "onSaveRequest(): data=" + bundleToString(data));
+ if (VERBOSE) {
+ Log.v(TAG, "onSaveRequest(): data=" + bundleToString(data));
dumpStructure(structure);
}
- StructureParser parser = new StructureParser(structure);
+ StructureParser parser = new StructureParser(getApplicationContext(), structure);
parser.parseForSave();
FilledAutofillFieldCollection filledAutofillFieldCollection = parser.getClientFormData();
SharedPrefsAutofillRepository.getInstance()
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/SecurityHelper.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/SecurityHelper.java
new file mode 100644
index 00000000..3d13b6d3
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/SecurityHelper.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2017 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.example.android.autofillframework.multidatasetservice;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.os.AsyncTask;
+import android.util.Log;
+
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.MessageDigest;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+import static com.example.android.autofillframework.CommonUtil.DEBUG;
+import static com.example.android.autofillframework.CommonUtil.TAG;
+import static com.example.android.autofillframework.CommonUtil.VERBOSE;
+
+/**
+ * Helper class for security checks.
+ */
+public final class SecurityHelper {
+
+ private static final String REST_TEMPLATE =
+ "https://digitalassetlinks.googleapis.com/v1/assetlinks:check?"
+ + "source.web.site=%s&relation=delegate_permission/%s"
+ + "&target.android_app.package_name=%s"
+ + "&target.android_app.certificate.sha256_fingerprint=%s";
+
+ private static final String PERMISSION_GET_LOGIN_CREDS = "common.get_login_creds";
+ private static final String PERMISSION_HANDLE_ALL_URLS = "common.handle_all_urls";
+
+ private SecurityHelper() {
+ throw new UnsupportedOperationException("provides static methods only");
+ }
+
+ private static boolean isValidSync(String webDomain, String permission, String packageName,
+ String fingerprint) {
+ if (DEBUG) Log.d(TAG, "validating domain " + webDomain + " for pkg " + packageName
+ + " and fingerprint " + fingerprint + " for permission" + permission);
+ if (!webDomain.startsWith("http:") && !webDomain.startsWith("https:") ) {
+ // Unfortunately AssistStructure.ViewNode does not tell what the domain is, so let's
+ // assume it's https
+ webDomain = "https://" + webDomain;
+ }
+
+ String restUrl =
+ String.format(REST_TEMPLATE, webDomain, permission, packageName, fingerprint);
+ if (DEBUG) Log.d(TAG, "DAL REST request: " + restUrl);
+
+ HttpURLConnection urlConnection = null;
+ StringBuilder output = new StringBuilder();
+ try {
+ URL url = new URL(restUrl);
+ urlConnection = (HttpURLConnection) url.openConnection();
+ try (BufferedReader reader = new BufferedReader(
+ new InputStreamReader(urlConnection.getInputStream()))) {
+ String line = null;
+ while ((line = reader.readLine()) != null) {
+ output.append(line);
+ }
+ }
+ String response = output.toString();
+ if (VERBOSE) Log.v(TAG, "DAL REST Response: " + response);
+
+ JSONObject jsonObject = new JSONObject(response);
+ boolean valid = jsonObject.optBoolean("linked", false);
+ if (DEBUG) Log.d(TAG, "Valid: " + valid);
+
+ return valid;
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to validate", e);
+ } finally {
+ if (urlConnection != null) {
+ urlConnection.disconnect();
+ }
+ }
+
+ }
+
+ private static boolean isValidSync(String webDomain, String packageName, String fingerprint) {
+ boolean isValid =
+ isValidSync(webDomain, PERMISSION_GET_LOGIN_CREDS, packageName, fingerprint);
+ if (!isValid) {
+ // Ideally we should only check for the get_login_creds, but not all domains set
+ // it yet, so validating for handle_all_urls gives a higher coverage.
+ if (DEBUG) {
+ Log.d(TAG, PERMISSION_GET_LOGIN_CREDS + " validation failed; trying "
+ + PERMISSION_HANDLE_ALL_URLS);
+ }
+ isValid = isValidSync(webDomain, PERMISSION_HANDLE_ALL_URLS, packageName, fingerprint);
+ }
+ return isValid;
+ }
+
+
+ public static boolean isValid(String webDomain, String packageName, String fingerprint) {
+ if (DEBUG) Log.d(TAG, "validating domain " + webDomain + " for pkg " + packageName
+ + " and fingerprint " + fingerprint );
+ final String fullDomain;
+ if (!webDomain.startsWith("http:") && !webDomain.startsWith("https:") ) {
+ // Unfortunately AssistStructure.ViewNode does not tell what the domain is, so let's
+ // assume it's https
+ fullDomain = "https://" + webDomain;
+ } else {
+ fullDomain = webDomain;
+ }
+
+ // TODO: use the DAL Java API or a better REST alternative like Volley
+ // and/or document it should not block until it returns (for example, the server could
+ // start parsing the structure while it waits for the result.
+ AsyncTask<String, Integer, Boolean> task = new AsyncTask<String, Integer, Boolean>() {
+ @Override
+ protected Boolean doInBackground(String... strings) {
+ return isValidSync(fullDomain, packageName, fingerprint);
+ }
+ };
+ try {
+ return task.execute((String[]) null).get();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ Log.w(TAG, "Thread interrupted");
+ } catch (Exception e) {
+ Log.w(TAG, "Async task failed", e);
+ }
+ return false;
+ }
+
+ /**
+ * Gets the fingerprint of the signed certificate of a package.
+ */
+ public static String getFingerprint(Context context, String packageName) throws Exception {
+ PackageManager pm = context.getPackageManager();
+ PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+ Signature[] signatures = packageInfo.signatures;
+ if (signatures.length != 1) {
+ throw new SecurityException(packageName + " has " + signatures.length + " signatures");
+ }
+ byte[] cert = signatures[0].toByteArray();
+ try (InputStream input = new ByteArrayInputStream(cert)) {
+ CertificateFactory factory = CertificateFactory.getInstance("X509");
+ X509Certificate x509 = (X509Certificate) factory.generateCertificate(input);
+ MessageDigest md = MessageDigest.getInstance("SHA256");
+ byte[] publicKey = md.digest(x509.getEncoded());
+ return toHexFormat(publicKey);
+ }
+ }
+
+ private static String toHexFormat(byte[] bytes) {
+ StringBuilder builder = new StringBuilder(bytes.length * 2);
+ for (int i = 0; i < bytes.length; i++) {
+ String hex = Integer.toHexString(bytes[i]);
+ int length = hex.length();
+ if (length == 1) {
+ hex = "0" + hex;
+ }
+ if (length > 2) {
+ hex = hex.substring(length - 2, length);
+ }
+ builder.append(hex.toUpperCase());
+ if (i < (bytes.length - 1)) {
+ builder.append(':');
+ }
+ }
+ return builder.toString();
+ }
+}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/StructureParser.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/StructureParser.java
index 812ba40f..a912c0fa 100644
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/StructureParser.java
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/StructureParser.java
@@ -18,13 +18,17 @@ package com.example.android.autofillframework.multidatasetservice;
import android.app.assist.AssistStructure;
import android.app.assist.AssistStructure.ViewNode;
import android.app.assist.AssistStructure.WindowNode;
+import android.content.Context;
import android.util.Log;
import android.view.autofill.AutofillValue;
+import com.example.android.autofillframework.R;
+import com.example.android.autofillframework.multidatasetservice.datasource.SharedPrefsDigitalAssetLinksRepository;
import com.example.android.autofillframework.multidatasetservice.model.FilledAutofillField;
import com.example.android.autofillframework.multidatasetservice.model.FilledAutofillFieldCollection;
import static com.example.android.autofillframework.CommonUtil.TAG;
+import static com.example.android.autofillframework.CommonUtil.DEBUG;
/**
* Parser for an AssistStructure object. This is invoked when the Autofill Service receives an
@@ -34,10 +38,12 @@ import static com.example.android.autofillframework.CommonUtil.TAG;
final class StructureParser {
private final AutofillFieldMetadataCollection mAutofillFields =
new AutofillFieldMetadataCollection();
+ private final Context mContext;
private final AssistStructure mStructure;
private FilledAutofillFieldCollection mFilledAutofillFieldCollection;
- StructureParser(AssistStructure structure) {
+ StructureParser(Context context, AssistStructure structure) {
+ mContext = context;
mStructure = structure;
}
@@ -53,17 +59,42 @@ final class StructureParser {
* Traverse AssistStructure and add ViewNode metadata to a flat list.
*/
private void parse(boolean forFill) {
- Log.d(TAG, "Parsing structure for " + mStructure.getActivityComponent());
+ if (DEBUG) Log.d(TAG, "Parsing structure for " + mStructure.getActivityComponent());
int nodes = mStructure.getWindowNodeCount();
mFilledAutofillFieldCollection = new FilledAutofillFieldCollection();
+ StringBuilder webDomain = new StringBuilder();
for (int i = 0; i < nodes; i++) {
WindowNode node = mStructure.getWindowNodeAt(i);
ViewNode view = node.getRootViewNode();
- parseLocked(forFill, view);
+ parseLocked(forFill, view, webDomain);
+ }
+ if (webDomain.length() > 0 ) {
+ String packageName = mStructure.getActivityComponent().getPackageName();
+ boolean valid = SharedPrefsDigitalAssetLinksRepository.getInstance().isValid(mContext,
+ webDomain.toString(), packageName);
+ if (!valid) {
+ throw new SecurityException(mContext.getString(
+ R.string.invalid_link_association, webDomain, packageName));
+ }
+ if (DEBUG) Log.d(TAG, "Domain " + webDomain + " is valid for " + packageName);
+ } else {
+ if (DEBUG) Log.d(TAG, "no web domain");
}
}
- private void parseLocked(boolean forFill, ViewNode viewNode) {
+ private void parseLocked(boolean forFill, ViewNode viewNode, StringBuilder validWebDomain) {
+ String webDomain = viewNode.getWebDomain();
+ if (webDomain != null) {
+ if (DEBUG) Log.d(TAG, "child web domain: " + webDomain);
+ if (validWebDomain.length() > 0) {
+ if (!webDomain.equals(validWebDomain.toString())) {
+ throw new SecurityException("Found multiple web domains: valid= "
+ + validWebDomain + ", child=" + webDomain);
+ }
+ } else {
+ validWebDomain.append(webDomain);
+ }
+ }
if (viewNode.getAutofillHints() != null) {
String[] filteredHints = AutofillHints.filterForSupportedHints(
@@ -92,7 +123,7 @@ final class StructureParser {
int childrenSize = viewNode.getChildCount();
if (childrenSize > 0) {
for (int i = 0; i < childrenSize; i++) {
- parseLocked(forFill, viewNode.getChildAt(i));
+ parseLocked(forFill, viewNode.getChildAt(i), validWebDomain);
}
}
}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/DigitalAssetLinksDataSource.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/DigitalAssetLinksDataSource.java
new file mode 100644
index 00000000..04624cbb
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/DigitalAssetLinksDataSource.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 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.example.android.autofillframework.multidatasetservice.datasource;
+
+import android.content.Context;
+
+/**
+ * Helper format
+ * <a href="https://developers.google.com/digital-asset-links/">Digital Asset Links</a> needs.
+ */
+public interface DigitalAssetLinksDataSource {
+
+ /**
+ * Checks if the association between a web domain and a package is valid.
+ */
+ boolean isValid(Context context, String webDomain, String packageName);
+
+ /**
+ * Clears all cached data.
+ */
+ void clear(Context context);
+}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/SharedPrefsAutofillRepository.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/SharedPrefsAutofillRepository.java
index 7b55ef26..91a1923e 100644
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/SharedPrefsAutofillRepository.java
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/SharedPrefsAutofillRepository.java
@@ -28,8 +28,9 @@ import java.util.Set;
/**
* Singleton autofill data repository that stores autofill fields to SharedPreferences.
- * Disclaimer: you should not store sensitive fields like user data unencrypted. This is done
- * here only for simplicity and learning purposes.
+ *
+ * <p><b>Disclaimer</b>: you should not store sensitive fields like user data unencrypted.
+ * This is done here only for simplicity and learning purposes.
*/
public class SharedPrefsAutofillRepository implements AutofillDataSource {
private static final String SHARED_PREF_KEY = "com.example.android.autofillframework"
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/SharedPrefsDigitalAssetLinksRepository.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/SharedPrefsDigitalAssetLinksRepository.java
new file mode 100644
index 00000000..fdfd6578
--- /dev/null
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/SharedPrefsDigitalAssetLinksRepository.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 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.example.android.autofillframework.multidatasetservice.datasource;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.example.android.autofillframework.multidatasetservice.SecurityHelper;
+
+import static com.example.android.autofillframework.CommonUtil.TAG;
+
+/**
+ * Singleton repository that caches the result of Digital Asset Links checks.
+ */
+public class SharedPrefsDigitalAssetLinksRepository implements DigitalAssetLinksDataSource {
+
+ private static SharedPrefsDigitalAssetLinksRepository sInstance;
+
+ private SharedPrefsDigitalAssetLinksRepository() {
+ }
+
+ public static SharedPrefsDigitalAssetLinksRepository getInstance() {
+ if (sInstance == null) {
+ sInstance = new SharedPrefsDigitalAssetLinksRepository();
+ }
+ return sInstance;
+ }
+
+ @Override
+ public boolean isValid(Context context, String webDomain, String packageName) {
+ // TODO: implement caching. It could cache the whole domain -> (packagename, fingerprint),
+ // but then either invalidate when the package change or when the DAL association times out
+ // (the maxAge is part of the API response), or document that a real-life service
+ // should do that.
+
+ String fingerprint = null;
+ try {
+ fingerprint = SecurityHelper.getFingerprint(context, packageName);
+ } catch (Exception e) {
+ Log.w(TAG, "error getting fingerprint for " + packageName, e);
+ return false;
+ }
+ return SecurityHelper.isValid(webDomain,packageName,fingerprint);
+ }
+
+ @Override
+ public void clear(Context context) {
+ // TODO: implement once if caches results or remove from the interface
+ }
+}
diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/SharedPrefsPackageVerificationRepository.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/SharedPrefsPackageVerificationRepository.java
index b7bb5828..aa467786 100644
--- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/SharedPrefsPackageVerificationRepository.java
+++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/SharedPrefsPackageVerificationRepository.java
@@ -17,16 +17,9 @@ package com.example.android.autofillframework.multidatasetservice.datasource;
import android.content.Context;
import android.content.SharedPreferences;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.Signature;
import android.util.Log;
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.security.MessageDigest;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
+import com.example.android.autofillframework.multidatasetservice.SecurityHelper;
import static com.example.android.autofillframework.CommonUtil.TAG;
@@ -58,7 +51,7 @@ public class SharedPrefsPackageVerificationRepository implements PackageVerifica
public boolean putPackageSignatures(Context context, String packageName) {
String hash;
try {
- hash = getCertificateHash(context, packageName);
+ hash = SecurityHelper.getFingerprint(context, packageName);
Log.d(TAG, "Hash for " + packageName + ": " + hash);
} catch (Exception e) {
Log.w(TAG, "Error getting hash for " + packageName + ": " + e);
@@ -89,38 +82,4 @@ public class SharedPrefsPackageVerificationRepository implements PackageVerifica
SHARED_PREF_KEY, Context.MODE_PRIVATE);
return hash.equals(prefs.getString(packageName, null));
}
-
- private String getCertificateHash(Context context, String packageName)
- throws Exception {
- PackageManager pm = context.getPackageManager();
- PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
- Signature[] signatures = packageInfo.signatures;
- byte[] cert = signatures[0].toByteArray();
- try (InputStream input = new ByteArrayInputStream(cert)) {
- CertificateFactory factory = CertificateFactory.getInstance("X509");
- X509Certificate x509 = (X509Certificate) factory.generateCertificate(input);
- MessageDigest md = MessageDigest.getInstance("SHA256");
- byte[] publicKey = md.digest(x509.getEncoded());
- return toHexFormat(publicKey);
- }
- }
-
- private String toHexFormat(byte[] bytes) {
- StringBuilder builder = new StringBuilder(bytes.length * 2);
- for (int i = 0; i < bytes.length; i++) {
- String hex = Integer.toHexString(bytes[i]);
- int length = hex.length();
- if (length == 1) {
- hex = "0" + hex;
- }
- if (length > 2) {
- hex = hex.substring(length - 2, length);
- }
- builder.append(hex.toUpperCase());
- if (i < (bytes.length - 1)) {
- builder.append(':');
- }
- }
- return builder.toString();
- }
}
diff --git a/input/autofill/AutofillFramework/Application/src/main/res/values/strings.xml b/input/autofill/AutofillFramework/Application/src/main/res/values/strings.xml
index 229cf9e8..01a743b5 100644
--- a/input/autofill/AutofillFramework/Application/src/main/res/values/strings.xml
+++ b/input/autofill/AutofillFramework/Application/src/main/res/values/strings.xml
@@ -79,6 +79,7 @@
<string name="cc_exp_month_description">Credit Card Expiration Month</string>
<string name="cc_exp_year_description">Credit Card Expiration Year</string>
<string name="invalid_package_signature">Invalid package signature</string>
+ <string name="invalid_link_association">Could not associate web domain %1$s with app %2$s</string>
<string name="edittext_login_info">This is a sample login page that uses standard EditTexts
from the UI toolkit. EditTexts are already optimized for autofill so extra autofill-specific
code is almost never needed.