Credential storage triggers unlock when keystore is locked.
If the phone is in the unlocked state and keystore is locked, storing credentials asks for a password that does not exist to the user. Replace this workflow with a key guard confirmation, asking the user to unlock the screen in the same way they would normally unlock their phone. Bug: 68298609 Test: adb push sample_credentials.p12 /sdcard/ Test: adb shell su 1000 service call android.security.keystore 9 i32 0 Test: adb shell am start -a android.credentials.INSTALL --user 10 Test: adb shell su 1000 service call android.security.keystore 9 i32 10 Change-Id: I8a3068a5d7de508fb417016acdf41b1712a2e7cc
This commit is contained in:
@@ -1,53 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Copyright (C) 2011 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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:padding="15dip">
|
|
||||||
|
|
||||||
<TextView android:id="@+id/hint"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="10sp"/>
|
|
||||||
|
|
||||||
<TextView android:id="@+id/error"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="10sp"
|
|
||||||
android:textColor="@color/red"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:visibility="gone"/>
|
|
||||||
|
|
||||||
<TextView android:id="@+id/old_password_prompt"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/credentials_old_password"
|
|
||||||
android:visibility="gone"/>
|
|
||||||
|
|
||||||
<EditText android:id="@+id/old_password"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:password="true"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:visibility="gone"/>
|
|
||||||
</LinearLayout>
|
|
||||||
</ScrollView>
|
|
@@ -5613,27 +5613,12 @@
|
|||||||
<string name="credential_for_vpn_and_apps">Installed for VPN and apps</string>
|
<string name="credential_for_vpn_and_apps">Installed for VPN and apps</string>
|
||||||
<!-- Sub-heading for a user credential installed to be used as part of a Wi-Fi configuration. [CHAR LIMIT=NONE]. -->
|
<!-- Sub-heading for a user credential installed to be used as part of a Wi-Fi configuration. [CHAR LIMIT=NONE]. -->
|
||||||
<string name="credential_for_wifi">Installed for Wi-Fi</string>
|
<string name="credential_for_wifi">Installed for Wi-Fi</string>
|
||||||
|
|
||||||
<!-- Title of dialog to enable credential storage [CHAR LIMIT=30] -->
|
|
||||||
<string name="credentials_unlock"></string>
|
|
||||||
<!-- Description of dialog to enable credential storage [CHAR LIMIT=NONE] -->
|
|
||||||
<string name="credentials_unlock_hint">Type the password for credential storage.</string>
|
|
||||||
<!-- Description of the input box for the old password [CHAR LIMIT=30] -->
|
|
||||||
<string name="credentials_old_password">Current password:</string>
|
|
||||||
<!-- Description of dialog to reset credential storage [CHAR LIMIT=NONE] -->
|
<!-- Description of dialog to reset credential storage [CHAR LIMIT=NONE] -->
|
||||||
<string name="credentials_reset_hint">Remove all the contents?</string>
|
<string name="credentials_reset_hint">Remove all the contents?</string>
|
||||||
<!-- Error message [CHAR LIMIT=NONE] -->
|
|
||||||
<string name="credentials_wrong_password">Incorrect password.</string>
|
|
||||||
<!-- Error message [CHAR LIMIT=NONE] -->
|
|
||||||
<string name="credentials_reset_warning">Incorrect password. You have one more chance before credential storage is erased.</string>
|
|
||||||
<!-- Error message [CHAR LIMIT=NONE] -->
|
|
||||||
<string name="credentials_reset_warning_plural">Incorrect password. You have <xliff:g id="number" example="5">%1$d</xliff:g> more chances before credential storage is erased.</string>
|
|
||||||
<!-- Toast message [CHAR LIMIT=30] -->
|
<!-- Toast message [CHAR LIMIT=30] -->
|
||||||
<string name="credentials_erased">Credential storage is erased.</string>
|
<string name="credentials_erased">Credential storage is erased.</string>
|
||||||
<!-- Toast message [CHAR LIMIT=30] when credential storage containing private keys and certificates could not be erased (opposite of string credentials_erased) -->
|
<!-- Toast message [CHAR LIMIT=30] when credential storage containing private keys and certificates could not be erased (opposite of string credentials_erased) -->
|
||||||
<string name="credentials_not_erased">Credential storage couldn\u2019t be erased.</string>
|
<string name="credentials_not_erased">Credential storage couldn\u2019t be erased.</string>
|
||||||
<!-- Toast message [CHAR LIMIT=30] -->
|
|
||||||
<string name="credentials_enabled">Credential storage is enabled.</string>
|
|
||||||
<!-- This string is in a dialog, and the dialog shows up on a device that's managed by a user's company. It lets the user know that they need to have a secure lock screen (PIN, password, or pattern) before they can use credential storage [CHAR LIMIT=NONE] -->
|
<!-- This string is in a dialog, and the dialog shows up on a device that's managed by a user's company. It lets the user know that they need to have a secure lock screen (PIN, password, or pattern) before they can use credential storage [CHAR LIMIT=NONE] -->
|
||||||
<string name="credentials_configure_lock_screen_hint">Before you can use credential storage, your device need to have a secure lock screen</string>
|
<string name="credentials_configure_lock_screen_hint">Before you can use credential storage, your device need to have a secure lock screen</string>
|
||||||
<!-- This string is for the content of the button that leads user to lock screen settings [CHAR LIMIT=20] -->
|
<!-- This string is for the content of the button that leads user to lock screen settings [CHAR LIMIT=20] -->
|
||||||
|
@@ -35,25 +35,17 @@ import android.security.Credentials;
|
|||||||
import android.security.KeyChain;
|
import android.security.KeyChain;
|
||||||
import android.security.KeyChain.KeyChainConnection;
|
import android.security.KeyChain.KeyChainConnection;
|
||||||
import android.security.KeyStore;
|
import android.security.KeyStore;
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.android.internal.widget.LockPatternUtils;
|
import com.android.internal.widget.LockPatternUtils;
|
||||||
import com.android.org.bouncycastle.asn1.ASN1InputStream;
|
import com.android.org.bouncycastle.asn1.ASN1InputStream;
|
||||||
import com.android.org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
import com.android.org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||||
import com.android.settings.password.ChooseLockSettingsHelper;
|
import com.android.settings.password.ChooseLockSettingsHelper;
|
||||||
import com.android.settings.security.ConfigureKeyGuardDialog;
|
import com.android.settings.security.ConfigureKeyGuardDialog;
|
||||||
import com.android.settings.vpn2.VpnUtils;
|
import com.android.settings.vpn2.VpnUtils;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import sun.security.util.ObjectIdentifier;
|
import sun.security.util.ObjectIdentifier;
|
||||||
import sun.security.x509.AlgorithmId;
|
import sun.security.x509.AlgorithmId;
|
||||||
|
|
||||||
@@ -78,8 +70,8 @@ import sun.security.x509.AlgorithmId;
|
|||||||
*
|
*
|
||||||
* KeyStore: LOCKED
|
* KeyStore: LOCKED
|
||||||
* KeyGuard: OFF/ON
|
* KeyGuard: OFF/ON
|
||||||
* Action: old unlock dialog
|
* Action: confirm key guard
|
||||||
* Notes: assume old password, need to use it to unlock.
|
* Notes: request normal unlock to unlock the keystore.
|
||||||
* if unlock, ensure key guard before install.
|
* if unlock, ensure key guard before install.
|
||||||
* if reset, treat as UNINITALIZED/OFF
|
* if reset, treat as UNINITALIZED/OFF
|
||||||
*
|
*
|
||||||
@@ -115,14 +107,6 @@ public final class CredentialStorage extends Activity {
|
|||||||
*/
|
*/
|
||||||
private Bundle mInstallBundle;
|
private Bundle mInstallBundle;
|
||||||
|
|
||||||
/**
|
|
||||||
* After unsuccessful KeyStore.unlock, the number of unlock
|
|
||||||
* attempts remaining before the KeyStore will reset itself.
|
|
||||||
*
|
|
||||||
* Reset to -1 on successful unlock or reset.
|
|
||||||
*/
|
|
||||||
private int mRetriesRemaining = -1;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
@@ -166,11 +150,12 @@ public final class CredentialStorage extends Activity {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case LOCKED: {
|
case LOCKED: {
|
||||||
new UnlockDialog();
|
// Force key guard confirmation
|
||||||
|
confirmKeyGuard(CONFIRM_KEY_GUARD_REQUEST);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case UNLOCKED: {
|
case UNLOCKED: {
|
||||||
if (!checkKeyGuardQuality()) {
|
if (isActivePasswordQualityInsufficient()) {
|
||||||
final ConfigureKeyGuardDialog dialog = new ConfigureKeyGuardDialog();
|
final ConfigureKeyGuardDialog dialog = new ConfigureKeyGuardDialog();
|
||||||
dialog.show(getFragmentManager(), ConfigureKeyGuardDialog.TAG);
|
dialog.show(getFragmentManager(), ConfigureKeyGuardDialog.TAG);
|
||||||
return;
|
return;
|
||||||
@@ -189,7 +174,7 @@ public final class CredentialStorage extends Activity {
|
|||||||
* case after unlocking with an old-style password).
|
* case after unlocking with an old-style password).
|
||||||
*/
|
*/
|
||||||
private void ensureKeyGuard() {
|
private void ensureKeyGuard() {
|
||||||
if (!checkKeyGuardQuality()) {
|
if (isActivePasswordQualityInsufficient()) {
|
||||||
// key guard not setup, doing so will initialize keystore
|
// key guard not setup, doing so will initialize keystore
|
||||||
final ConfigureKeyGuardDialog dialog = new ConfigureKeyGuardDialog();
|
final ConfigureKeyGuardDialog dialog = new ConfigureKeyGuardDialog();
|
||||||
dialog.show(getFragmentManager(), ConfigureKeyGuardDialog.TAG);
|
dialog.show(getFragmentManager(), ConfigureKeyGuardDialog.TAG);
|
||||||
@@ -205,9 +190,9 @@ public final class CredentialStorage extends Activity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the currently set key guard matches our minimum quality requirements.
|
* Returns true if the currently set key guard violates our minimum quality requirements.
|
||||||
*/
|
*/
|
||||||
private boolean checkKeyGuardQuality() {
|
private boolean isActivePasswordQualityInsufficient() {
|
||||||
final int credentialOwner =
|
final int credentialOwner =
|
||||||
UserManager.get(this).getCredentialOwnerProfile(UserHandle.myUserId());
|
UserManager.get(this).getCredentialOwnerProfile(UserHandle.myUserId());
|
||||||
final int quality = new LockPatternUtils(this).getActivePasswordQuality(credentialOwner);
|
final int quality = new LockPatternUtils(this).getActivePasswordQuality(credentialOwner);
|
||||||
@@ -457,11 +442,8 @@ public final class CredentialStorage extends Activity {
|
|||||||
|
|
||||||
final UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
|
final UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
|
||||||
final UserInfo parentInfo = userManager.getProfileParent(launchedFromUserId);
|
final UserInfo parentInfo = userManager.getProfileParent(launchedFromUserId);
|
||||||
if (parentInfo == null || parentInfo.id != UserHandle.myUserId()) {
|
// Caller is running in a profile of this user
|
||||||
// Caller is not running in a profile of this user
|
return ((parentInfo != null) && (parentInfo.id == UserHandle.myUserId()));
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -469,19 +451,15 @@ public final class CredentialStorage extends Activity {
|
|||||||
*/
|
*/
|
||||||
private boolean confirmKeyGuard(int requestCode) {
|
private boolean confirmKeyGuard(int requestCode) {
|
||||||
final Resources res = getResources();
|
final Resources res = getResources();
|
||||||
boolean launched = new ChooseLockSettingsHelper(this)
|
return new ChooseLockSettingsHelper(this)
|
||||||
.launchConfirmationActivity(requestCode,
|
.launchConfirmationActivity(requestCode,
|
||||||
res.getText(R.string.credentials_title), true);
|
res.getText(R.string.credentials_title), true);
|
||||||
return launched;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
// Receive key guard password initiated by confirmKeyGuard.
|
||||||
/**
|
|
||||||
* Receive key guard password initiated by confirmKeyGuard.
|
|
||||||
*/
|
|
||||||
if (requestCode == CONFIRM_KEY_GUARD_REQUEST) {
|
if (requestCode == CONFIRM_KEY_GUARD_REQUEST) {
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
final String password = data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
|
final String password = data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
|
||||||
@@ -503,102 +481,4 @@ public final class CredentialStorage extends Activity {
|
|||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Prompt for unlock with old-style password.
|
|
||||||
*
|
|
||||||
* On successful unlock, ensure migration to key guard before continuing.
|
|
||||||
* On unsuccessful unlock, retry by calling handleUnlockOrInstall.
|
|
||||||
*/
|
|
||||||
private class UnlockDialog implements TextWatcher,
|
|
||||||
DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
|
|
||||||
private boolean mUnlockConfirmed;
|
|
||||||
|
|
||||||
private final Button mButton;
|
|
||||||
private final TextView mOldPassword;
|
|
||||||
private final TextView mError;
|
|
||||||
|
|
||||||
private UnlockDialog() {
|
|
||||||
final View view = View.inflate(
|
|
||||||
CredentialStorage.this, R.layout.credentials_dialog, null);
|
|
||||||
|
|
||||||
final CharSequence text;
|
|
||||||
if (mRetriesRemaining == -1) {
|
|
||||||
text = getResources().getText(R.string.credentials_unlock_hint);
|
|
||||||
} else if (mRetriesRemaining > 3) {
|
|
||||||
text = getResources().getText(R.string.credentials_wrong_password);
|
|
||||||
} else if (mRetriesRemaining == 1) {
|
|
||||||
text = getResources().getText(R.string.credentials_reset_warning);
|
|
||||||
} else {
|
|
||||||
text = getString(R.string.credentials_reset_warning_plural, mRetriesRemaining);
|
|
||||||
}
|
|
||||||
|
|
||||||
((TextView) view.findViewById(R.id.hint)).setText(text);
|
|
||||||
mOldPassword = (TextView) view.findViewById(R.id.old_password);
|
|
||||||
mOldPassword.setVisibility(View.VISIBLE);
|
|
||||||
mOldPassword.addTextChangedListener(this);
|
|
||||||
mError = (TextView) view.findViewById(R.id.error);
|
|
||||||
|
|
||||||
final AlertDialog dialog = new AlertDialog.Builder(CredentialStorage.this)
|
|
||||||
.setView(view)
|
|
||||||
.setTitle(R.string.credentials_unlock)
|
|
||||||
.setPositiveButton(android.R.string.ok, this)
|
|
||||||
.setNegativeButton(android.R.string.cancel, this)
|
|
||||||
.create();
|
|
||||||
dialog.setOnDismissListener(this);
|
|
||||||
dialog.show();
|
|
||||||
mButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
|
|
||||||
mButton.setEnabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable editable) {
|
|
||||||
mButton.setEnabled(mOldPassword == null || mOldPassword.getText().length() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int button) {
|
|
||||||
mUnlockConfirmed = (button == DialogInterface.BUTTON_POSITIVE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDismiss(DialogInterface dialog) {
|
|
||||||
if (mUnlockConfirmed) {
|
|
||||||
mUnlockConfirmed = false;
|
|
||||||
mError.setVisibility(View.VISIBLE);
|
|
||||||
mKeyStore.unlock(mOldPassword.getText().toString());
|
|
||||||
final int error = mKeyStore.getLastError();
|
|
||||||
if (error == KeyStore.NO_ERROR) {
|
|
||||||
mRetriesRemaining = -1;
|
|
||||||
Toast.makeText(CredentialStorage.this,
|
|
||||||
R.string.credentials_enabled,
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
// aha, now we are unlocked, switch to key guard.
|
|
||||||
// we'll end up back in onResume to install
|
|
||||||
ensureKeyGuard();
|
|
||||||
} else if (error == KeyStore.UNINITIALIZED) {
|
|
||||||
mRetriesRemaining = -1;
|
|
||||||
Toast.makeText(CredentialStorage.this,
|
|
||||||
R.string.credentials_erased,
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
// we are reset, we can now set new password with key guard
|
|
||||||
handleUnlockOrInstall();
|
|
||||||
} else if (error >= KeyStore.WRONG_PASSWORD) {
|
|
||||||
// we need to try again
|
|
||||||
mRetriesRemaining = error - KeyStore.WRONG_PASSWORD + 1;
|
|
||||||
handleUnlockOrInstall();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user