diff options
Diffstat (limited to 'input')
14 files changed, 369 insertions, 60 deletions
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 c6615fff..015dc68f 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 @@ -15,7 +15,6 @@ */ package com.example.android.autofillframework.multidatasetservice; -import android.app.Activity; import android.app.PendingIntent; import android.app.assist.AssistStructure; import android.content.Context; @@ -131,8 +130,8 @@ public class AuthActivity extends AppCompatActivity { int saveTypes = autofillFields.getSaveType(); mReplyIntent = new Intent(); HashMap<String, FilledAutofillFieldCollection> clientFormDataMap = - SharedPrefsAutofillRepository.getInstance(this).getFilledAutofillFieldCollection - (autofillFields.getFocusedHints(), autofillFields.getAllHints()); + SharedPrefsAutofillRepository.getInstance().getFilledAutofillFieldCollection + (this, autofillFields.getFocusedHints(), autofillFields.getAllHints()); if (forResponse) { setResponseIntent(AutofillHelper.newResponse (this, false, autofillFields, clientFormDataMap)); 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 75cc849f..1e76f6ee 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 @@ -29,7 +29,6 @@ import android.widget.RemoteViews; import com.example.android.autofillframework.R; import com.example.android.autofillframework.multidatasetservice.model.FilledAutofillFieldCollection; -import java.util.ArrayList; import java.util.HashMap; import java.util.Set; 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 19cf2ae0..047ee672 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,9 +29,11 @@ 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; +import com.example.android.autofillframework.multidatasetservice.datasource.SharedPrefsPackageVerificationRepository; import com.example.android.autofillframework.multidatasetservice.model.FilledAutofillFieldCollection; import com.example.android.autofillframework.multidatasetservice.settings.MyPreferences; @@ -49,6 +51,13 @@ public class MyAutofillService extends AutofillService { FillCallback callback) { AssistStructure structure = request.getFillContexts() .get(request.getFillContexts().size() - 1).getStructure(); + String packageName = structure.getActivityComponent().getPackageName(); + if (!SharedPrefsPackageVerificationRepository.getInstance() + .putPackageSignatures(getApplicationContext(), packageName)) { + Toast.makeText(getApplicationContext(), R.string.invalid_package_signature, + Toast.LENGTH_SHORT).show(); + return; + } final Bundle data = request.getClientState(); Log.d(TAG, "onFillRequest(): data=" + bundleToString(data)); @@ -79,8 +88,8 @@ public class MyAutofillService extends AutofillService { } else { boolean datasetAuth = MyPreferences.getInstance(this).isDatasetAuth(); HashMap<String, FilledAutofillFieldCollection> clientFormDataMap = - SharedPrefsAutofillRepository.getInstance(this).getFilledAutofillFieldCollection - (autofillFields.getFocusedHints(), autofillFields.getAllHints()); + SharedPrefsAutofillRepository.getInstance().getFilledAutofillFieldCollection + (this, autofillFields.getFocusedHints(), autofillFields.getAllHints()); FillResponse response = AutofillHelper.newResponse (this, datasetAuth, autofillFields, clientFormDataMap); callback.onSuccess(response); @@ -91,12 +100,20 @@ public class MyAutofillService extends AutofillService { public void onSaveRequest(SaveRequest request, SaveCallback callback) { List<FillContext> context = request.getFillContexts(); final AssistStructure structure = context.get(context.size() - 1).getStructure(); + String packageName = structure.getActivityComponent().getPackageName(); + if (!SharedPrefsPackageVerificationRepository.getInstance() + .putPackageSignatures(getApplicationContext(), packageName)) { + Toast.makeText(getApplicationContext(), R.string.invalid_package_signature, + Toast.LENGTH_SHORT).show(); + return; + } final Bundle data = request.getClientState(); Log.d(TAG, "onSaveRequest(): data=" + bundleToString(data)); StructureParser parser = new StructureParser(structure); parser.parseForSave(); FilledAutofillFieldCollection filledAutofillFieldCollection = parser.getClientFormData(); - SharedPrefsAutofillRepository.getInstance(this).saveFilledAutofillFieldCollection(filledAutofillFieldCollection); + SharedPrefsAutofillRepository.getInstance() + .saveFilledAutofillFieldCollection(this, filledAutofillFieldCollection); } @Override diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/AutofillRepository.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/AutofillDataSource.java index 7ee95552..99d1c261 100644 --- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/AutofillRepository.java +++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/AutofillDataSource.java @@ -15,27 +15,30 @@ */ package com.example.android.autofillframework.multidatasetservice.datasource; +import android.content.Context; + import com.example.android.autofillframework.multidatasetservice.model.FilledAutofillFieldCollection; import java.util.HashMap; import java.util.List; -public interface AutofillRepository { +public interface AutofillDataSource { /** - * Gets saved FilledAutofillFieldCollection that contains some objects that can autofill fields with these - * {@code autofillHints}. + * Gets saved FilledAutofillFieldCollection that contains some objects that can autofill fields + * with these {@code autofillHints}. */ - HashMap<String, FilledAutofillFieldCollection> getFilledAutofillFieldCollection(List<String> focusedAutofillHints, - List<String> allAutofillHints); + HashMap<String, FilledAutofillFieldCollection> getFilledAutofillFieldCollection(Context context, + List<String> focusedAutofillHints, List<String> allAutofillHints); /** * Saves LoginCredential under this datasetName. */ - void saveFilledAutofillFieldCollection(FilledAutofillFieldCollection filledAutofillFieldCollection); + void saveFilledAutofillFieldCollection(Context context, + FilledAutofillFieldCollection filledAutofillFieldCollection); /** * Clears all data. */ - void clear(); + void clear(Context context); } diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/PackageVerificationDataSource.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/PackageVerificationDataSource.java new file mode 100644 index 00000000..129001d0 --- /dev/null +++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/PackageVerificationDataSource.java @@ -0,0 +1,38 @@ +/* + * 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; + +public interface PackageVerificationDataSource { + + /** + * Verifies that the signatures in the passed {@code Context} match what is currently in + * storage. If there are no current signatures in storage for this packageName, it will store + * the signatures from the passed {@code Context}. + * + * @return {@code true} if signatures for this packageName are not currently in storage + * or if the signatures in the passed {@code Context} match what is currently in storage; + * {@code false} if the signatures in the passed {@code Context} do not match what is + * currently in storage or if an {@code Exception} was thrown while generating the signatures. + */ + boolean putPackageSignatures(Context context, String packageName); + + /** + * Clears all signature data currently in storage. + */ + 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 9d29fb3a..7b55ef26 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 @@ -16,7 +16,6 @@ package com.example.android.autofillframework.multidatasetservice.datasource; import android.content.Context; -import android.content.SharedPreferences; import android.util.ArraySet; import com.example.android.autofillframework.multidatasetservice.model.FilledAutofillFieldCollection; @@ -32,36 +31,33 @@ import java.util.Set; * Disclaimer: you should not store sensitive fields like user data unencrypted. This is done * here only for simplicity and learning purposes. */ -public class SharedPrefsAutofillRepository implements AutofillRepository { - private static final String SHARED_PREF_KEY = "com.example.android.autofillframework.service"; +public class SharedPrefsAutofillRepository implements AutofillDataSource { + private static final String SHARED_PREF_KEY = "com.example.android.autofillframework" + + ".multidatasetservice.datasource.AutofillDataSource"; private static final String CLIENT_FORM_DATA_KEY = "loginCredentialDatasets"; private static final String DATASET_NUMBER_KEY = "datasetNumber"; - private static SharedPrefsAutofillRepository sInstance; - private final SharedPreferences mPrefs; - - private SharedPrefsAutofillRepository(Context context) { - mPrefs = context.getApplicationContext() - .getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE); + private SharedPrefsAutofillRepository() { } - public static SharedPrefsAutofillRepository getInstance(Context context) { + public static SharedPrefsAutofillRepository getInstance() { if (sInstance == null) { - sInstance = new SharedPrefsAutofillRepository(context); + sInstance = new SharedPrefsAutofillRepository(); } return sInstance; } @Override - public HashMap<String, FilledAutofillFieldCollection> getFilledAutofillFieldCollection(List<String> focusedAutofillHints, - List<String> allAutofillHints) { + public HashMap<String, FilledAutofillFieldCollection> getFilledAutofillFieldCollection( + Context context, List<String> focusedAutofillHints, List<String> allAutofillHints) { boolean hasDataForFocusedAutofillHints = false; HashMap<String, FilledAutofillFieldCollection> clientFormDataMap = new HashMap<>(); - Set<String> clientFormDataStringSet = getAllAutofillDataStringSet(); + Set<String> clientFormDataStringSet = getAllAutofillDataStringSet(context); for (String clientFormDataString : clientFormDataStringSet) { Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); - FilledAutofillFieldCollection filledAutofillFieldCollection = gson.fromJson(clientFormDataString, FilledAutofillFieldCollection.class); + FilledAutofillFieldCollection filledAutofillFieldCollection = + gson.fromJson(clientFormDataString, FilledAutofillFieldCollection.class); if (filledAutofillFieldCollection != null) { if (filledAutofillFieldCollection.helpsWithHints(focusedAutofillHints)) { // Saved data has data relevant to at least 1 of the hints associated with the @@ -71,7 +67,8 @@ public class SharedPrefsAutofillRepository implements AutofillRepository { if (filledAutofillFieldCollection.helpsWithHints(allAutofillHints)) { // Saved data has data relevant to at least 1 of these hints associated with any // of the Views in the hierarchy. - clientFormDataMap.put(filledAutofillFieldCollection.getDatasetName(), filledAutofillFieldCollection); + clientFormDataMap.put(filledAutofillFieldCollection.getDatasetName(), + filledAutofillFieldCollection); } } } @@ -83,42 +80,61 @@ public class SharedPrefsAutofillRepository implements AutofillRepository { } @Override - public void saveFilledAutofillFieldCollection(FilledAutofillFieldCollection filledAutofillFieldCollection) { - String datasetName = "dataset-" + getDatasetNumber(); + public void saveFilledAutofillFieldCollection(Context context, + FilledAutofillFieldCollection filledAutofillFieldCollection) { + String datasetName = "dataset-" + getDatasetNumber(context); filledAutofillFieldCollection.setDatasetName(datasetName); - Set<String> allAutofillData = getAllAutofillDataStringSet(); + Set<String> allAutofillData = getAllAutofillDataStringSet(context); Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); allAutofillData.add(gson.toJson(filledAutofillFieldCollection)); - saveAllAutofillDataStringSet(allAutofillData); - incrementDatasetNumber(); + saveAllAutofillDataStringSet(context, allAutofillData); + incrementDatasetNumber(context); } @Override - public void clear() { - mPrefs.edit().remove(CLIENT_FORM_DATA_KEY).apply(); + public void clear(Context context) { + context.getApplicationContext() + .getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE) + .edit() + .remove(CLIENT_FORM_DATA_KEY) + .remove(DATASET_NUMBER_KEY) + .apply(); } - private Set<String> getAllAutofillDataStringSet() { - return mPrefs.getStringSet(CLIENT_FORM_DATA_KEY, new ArraySet<String>()); + private Set<String> getAllAutofillDataStringSet(Context context) { + return context.getApplicationContext() + .getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE) + .getStringSet(CLIENT_FORM_DATA_KEY, new ArraySet<String>()); } - private void saveAllAutofillDataStringSet(Set<String> allAutofillDataStringSet) { - mPrefs.edit().putStringSet(CLIENT_FORM_DATA_KEY, allAutofillDataStringSet).apply(); + private void saveAllAutofillDataStringSet(Context context, + Set<String> allAutofillDataStringSet) { + context.getApplicationContext() + .getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE) + .edit() + .putStringSet(CLIENT_FORM_DATA_KEY, allAutofillDataStringSet) + .apply(); } /** * For simplicity, datasets will be named in the form "dataset-X" where X means * this was the Xth dataset saved. */ - private int getDatasetNumber() { - return mPrefs.getInt(DATASET_NUMBER_KEY, 0); + private int getDatasetNumber(Context context) { + return context.getApplicationContext() + .getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE) + .getInt(DATASET_NUMBER_KEY, 0); } /** * Every time a dataset is saved, this should be called to increment the dataset number. * (only important for this service's dataset naming scheme). */ - private void incrementDatasetNumber() { - mPrefs.edit().putInt(DATASET_NUMBER_KEY, getDatasetNumber() + 1).apply(); + private void incrementDatasetNumber(Context context) { + context.getApplicationContext() + .getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE) + .edit() + .putInt(DATASET_NUMBER_KEY, getDatasetNumber(context) + 1) + .apply(); } }
\ No newline at end of file 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 new file mode 100644 index 00000000..b7bb5828 --- /dev/null +++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/datasource/SharedPrefsPackageVerificationRepository.java @@ -0,0 +1,126 @@ +/* + * 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.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 static com.example.android.autofillframework.CommonUtil.TAG; + +public class SharedPrefsPackageVerificationRepository implements PackageVerificationDataSource { + + private static final String SHARED_PREF_KEY = "com.example.android.autofillframework" + + ".multidatasetservice.datasource.PackageVerificationDataSource"; + private static PackageVerificationDataSource sInstance; + + private SharedPrefsPackageVerificationRepository() { + } + + public static PackageVerificationDataSource getInstance() { + if (sInstance == null) { + sInstance = new SharedPrefsPackageVerificationRepository(); + } + return sInstance; + } + + @Override + public void clear(Context context) { + context.getApplicationContext().getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE) + .edit() + .clear() + .apply(); + } + + @Override + public boolean putPackageSignatures(Context context, String packageName) { + String hash; + try { + hash = getCertificateHash(context, packageName); + Log.d(TAG, "Hash for " + packageName + ": " + hash); + } catch (Exception e) { + Log.w(TAG, "Error getting hash for " + packageName + ": " + e); + return false; + } + + if (!containsSignatureForPackage(context, packageName)) { + // Storage does not yet contain signature for this package name. + context.getApplicationContext() + .getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE) + .edit() + .putString(packageName, hash) + .apply(); + return true; + } + return containsMatchingSignatureForPackage(context, packageName, hash); + } + + private boolean containsSignatureForPackage(Context context, String packageName) { + SharedPreferences prefs = context.getApplicationContext().getSharedPreferences( + SHARED_PREF_KEY, Context.MODE_PRIVATE); + return prefs.contains(packageName); + } + + private boolean containsMatchingSignatureForPackage(Context context, String packageName, + String hash) { + SharedPreferences prefs = context.getApplicationContext().getSharedPreferences( + 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/java/com/example/android/autofillframework/multidatasetservice/model/FilledAutofillFieldCollection.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/FilledAutofillFieldCollection.java index fccb3b6c..d50af538 100644 --- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/FilledAutofillFieldCollection.java +++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/model/FilledAutofillFieldCollection.java @@ -78,6 +78,7 @@ public final class FilledAutofillFieldCollection { * Populates a {@link Dataset.Builder} with appropriate values for each {@link AutofillId} * in a {@code AutofillFieldMetadataCollection}. * + * <p> * In other words, it constructs an autofill * {@link Dataset.Builder} by applying saved values (from this {@code FilledAutofillFieldCollection}) * to Views specified in a {@code AutofillFieldMetadataCollection}, which represents the current diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/settings/MyPreferences.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/settings/MyPreferences.java index bb6e8314..34d63ef3 100644 --- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/settings/MyPreferences.java +++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/settings/MyPreferences.java @@ -22,12 +22,9 @@ import android.service.autofill.FillResponse; import android.support.annotation.NonNull; public class MyPreferences { - private static final String TAG = "MyPreferences"; - private static final String RESPONSE_AUTH_KEY = "response_auth"; private static final String DATASET_AUTH_KEY = "dataset_auth"; private static final String MASTER_PASSWORD_KEY = "master_password"; - private static MyPreferences sInstance; private final SharedPreferences mPrefs; diff --git a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/settings/SettingsActivity.java b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/settings/SettingsActivity.java index f013f785..5224bea3 100644 --- a/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/settings/SettingsActivity.java +++ b/input/autofill/AutofillFramework/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/settings/SettingsActivity.java @@ -30,6 +30,7 @@ import android.widget.TextView; import com.example.android.autofillframework.R; import com.example.android.autofillframework.multidatasetservice.datasource.SharedPrefsAutofillRepository; +import com.example.android.autofillframework.multidatasetservice.datasource.SharedPrefsPackageVerificationRepository; public class SettingsActivity extends AppCompatActivity { @@ -90,10 +91,10 @@ public class SettingsActivity extends AppCompatActivity { .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - SharedPrefsAutofillRepository.getInstance - (SettingsActivity.this).clear(); - MyPreferences.getInstance(SettingsActivity.this) - .clearCredentials(); + SharedPrefsAutofillRepository.getInstance().clear(SettingsActivity.this); + SharedPrefsPackageVerificationRepository.getInstance() + .clear(SettingsActivity.this); + MyPreferences.getInstance(SettingsActivity.this).clearCredentials(); dialog.dismiss(); } }) @@ -149,7 +150,7 @@ public class SettingsActivity extends AppCompatActivity { private void setupSettingsSwitch(int containerId, int labelId, int switchId, boolean checked, CompoundButton.OnCheckedChangeListener checkedChangeListener) { - ViewGroup container = (ViewGroup) findViewById(containerId); + ViewGroup container = findViewById(containerId); String switchLabel = ((TextView) container.findViewById(labelId)).getText().toString(); final Switch switchView = container.findViewById(switchId); switchView.setContentDescription(switchLabel); @@ -165,10 +166,11 @@ public class SettingsActivity extends AppCompatActivity { private void setupSettingsButton(int containerId, int labelId, int imageViewId, final View.OnClickListener onClickListener) { - ViewGroup container = (ViewGroup) findViewById(containerId); - String buttonLabel = ((TextView) container.findViewById(labelId)).getText().toString(); - final ImageView imageView = container.findViewById(imageViewId); - imageView.setContentDescription(buttonLabel); + ViewGroup container = findViewById(containerId); + TextView buttonLabel = container.findViewById(labelId); + String buttonLabelText = buttonLabel.getText().toString(); + ImageView imageView = container.findViewById(imageViewId); + imageView.setContentDescription(buttonLabelText); container.setOnClickListener(onClickListener); } } 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 89ec9d4b..16a87813 100644 --- a/input/autofill/AutofillFramework/Application/src/main/res/values/strings.xml +++ b/input/autofill/AutofillFramework/Application/src/main/res/values/strings.xml @@ -66,6 +66,7 @@ <string name="submit_label">Submit</string> <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="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. diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/MyAutofillService.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/MyAutofillService.kt index f74c5873..704d17a1 100644 --- a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/MyAutofillService.kt +++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/MyAutofillService.kt @@ -23,19 +23,24 @@ import android.service.autofill.FillResponse import android.service.autofill.SaveCallback import android.service.autofill.SaveRequest import android.util.Log -import android.view.autofill.AutofillId +import android.widget.Toast import com.example.android.autofillframework.CommonUtil.TAG import com.example.android.autofillframework.CommonUtil.bundleToString import com.example.android.autofillframework.R import com.example.android.autofillframework.multidatasetservice.datasource.SharedPrefsAutofillRepository import com.example.android.autofillframework.multidatasetservice.settings.MyPreferences -import java.util.Arrays class MyAutofillService : AutofillService() { override fun onFillRequest(request: FillRequest, cancellationSignal: CancellationSignal, callback: FillCallback) { - val structure = request.getFillContexts().get(request.getFillContexts().size - 1).structure + val structure = request.fillContexts[request.fillContexts.size - 1].structure + val packageName = structure.activityComponent.packageName + if (!PackageVerifier.isValidPackage(applicationContext, packageName)) { + Toast.makeText(applicationContext, R.string.invalid_package_signature, + Toast.LENGTH_SHORT).show() + return + } val data = request.clientState Log.d(TAG, "onFillRequest(): data=" + bundleToString(data)) cancellationSignal.setOnCancelListener { Log.w(TAG, "Cancel autofill not implemented in this sample.") } @@ -68,6 +73,12 @@ class MyAutofillService : AutofillService() { override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) { val context = request.fillContexts val structure = context[context.size - 1].structure + val packageName = structure.activityComponent.packageName + if (!PackageVerifier.isValidPackage(applicationContext, packageName)) { + Toast.makeText(applicationContext, R.string.invalid_package_signature, + Toast.LENGTH_SHORT).show() + return + } val data = request.clientState Log.d(TAG, "onSaveRequest(): data=" + bundleToString(data)) val parser = StructureParser(structure) diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/PackageVerifier.kt b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/PackageVerifier.kt new file mode 100644 index 00000000..d059a233 --- /dev/null +++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/java/com/example/android/autofillframework/multidatasetservice/PackageVerifier.kt @@ -0,0 +1,98 @@ +package com.example.android.autofillframework.multidatasetservice + +/* + * 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. + */ + +import android.content.Context +import android.content.pm.PackageManager +import android.util.Log +import com.example.android.autofillframework.CommonUtil.TAG +import java.io.ByteArrayInputStream +import java.security.MessageDigest +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate + +object PackageVerifier { + + + /** + * Verifies if a package is valid by matching its certificate with the previously stored + * certificate. + */ + fun isValidPackage(context: Context, packageName: String): Boolean { + val hash: String + try { + hash = getCertificateHash(context, packageName) + Log.d(TAG, "Hash for $packageName: $hash") + } catch (e: Exception) { + Log.w(TAG, "Error getting hash for $packageName: $e") + return false + } + + return verifyHash(context, packageName, hash) + } + + private fun getCertificateHash(context: Context, packageName: String): String { + val pm = context.packageManager + val packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES) + val signatures = packageInfo.signatures + val cert = signatures[0].toByteArray() + ByteArrayInputStream(cert).use { input -> + val factory = CertificateFactory.getInstance("X509") + val x509 = factory.generateCertificate(input) as X509Certificate + val md = MessageDigest.getInstance("SHA256") + val publicKey = md.digest(x509.encoded) + return toHexFormat(publicKey) + } + } + + private fun toHexFormat(bytes: ByteArray): String { + val builder = StringBuilder(bytes.size * 2) + for (i in bytes.indices) { + var hex = Integer.toHexString(bytes[i].toInt()) + val 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.size - 1) { + builder.append(':') + } + } + return builder.toString() + } + + private fun verifyHash(context: Context, packageName: String, hash: String): Boolean { + val prefs = context.applicationContext.getSharedPreferences( + "package-hashes", Context.MODE_PRIVATE) + if (!prefs.contains(packageName)) { + Log.d(TAG, "Creating intial hash for " + packageName) + prefs.edit().putString(packageName, hash).apply() + return true + } + + val existingHash = prefs.getString(packageName, null) + if (hash != existingHash) { + Log.w(TAG, "hash mismatch for " + packageName + ": expected " + existingHash + + ", got " + hash) + return false + } + return true + } +}
\ No newline at end of file diff --git a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values/strings.xml b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values/strings.xml index 1eca8c1e..5a370190 100644 --- a/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values/strings.xml +++ b/input/autofill/AutofillFramework/kotlinApp/Application/src/main/res/values/strings.xml @@ -60,6 +60,7 @@ <string name="submit_label">Submit</string> <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="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. |