private static List<VpnProfile> loadVpnProfiles(KeyStore keyStore, int... excludeTypes) { final ArrayList<VpnProfile> result = Lists.newArrayList(); final String[] keys = keyStore.saw(Credentials.VPN); if (keys != null) { for (String key : keys) { final VpnProfile profile = VpnProfile.decode(key, keyStore.get(Credentials.VPN + key)); if (profile != null && !ArrayUtils.contains(excludeTypes, profile.type)) { result.add(profile); } } } return result; }
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mDPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE); mKeyStore = KeyStore.getInstance(); mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this.getActivity()); // Defaults to needing to confirm credentials final boolean confirmCredentials = getActivity().getIntent().getBooleanExtra(CONFIRM_CREDENTIALS, true); if (getActivity() instanceof ChooseLockGeneric.InternalActivity) { mPasswordConfirmed = !confirmCredentials; } if (savedInstanceState != null) { mPasswordConfirmed = savedInstanceState.getBoolean(PASSWORD_CONFIRMED); mWaitingForConfirmation = savedInstanceState.getBoolean(WAITING_FOR_CONFIRMATION); mFinishPending = savedInstanceState.getBoolean(FINISH_PENDING); } if (mPasswordConfirmed) { updatePreferencesOrFinish(); } else if (!mWaitingForConfirmation) { ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(this.getActivity(), this); if (!helper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST, null, null)) { mPasswordConfirmed = true; // no password set, so no need to confirm updatePreferencesOrFinish(); } else { mWaitingForConfirmation = true; } } }
@Override public boolean onContextItemSelected(MenuItem item) { if (mDialog != null) { Log.v(TAG, "onContextItemSelected() is called when mDialog != null"); return false; } VpnPreference preference = mPreferences.get(mSelectedKey); if (preference == null) { Log.v(TAG, "onContextItemSelected() is called but no preference is found"); return false; } switch (item.getItemId()) { case R.string.vpn_menu_edit: mDialog = new VpnDialog(getActivity(), this, preference.getProfile(), true); mDialog.setOnDismissListener(this); mDialog.show(); return true; case R.string.vpn_menu_delete: disconnect(mSelectedKey); getPreferenceScreen().removePreference(preference); mPreferences.remove(mSelectedKey); mKeyStore.delete(Credentials.VPN + mSelectedKey); return true; } return false; }
@Override public void onClick(DialogInterface dialog, int button) { if (button == DialogInterface.BUTTON_POSITIVE) { // Always save the profile. VpnProfile profile = mDialog.getProfile(); mKeyStore.put(Credentials.VPN + profile.key, profile.encode()); // Update the preference. VpnPreference preference = mPreferences.get(profile.key); if (preference != null) { disconnect(profile.key); preference.update(profile); } else { preference = new VpnPreference(getActivity(), profile); preference.setOnPreferenceClickListener(this); mPreferences.put(profile.key, preference); getPreferenceScreen().addPreference(preference); } // If we are not editing, connect! if (!mDialog.isEditing()) { try { connect(profile); } catch (Exception e) { Log.e(TAG, "connect", e); } } } }
private int upgradeQualityForKeyStore(int quality) { if (!mKeyStore.isEmpty()) { if (quality < CredentialStorage.MIN_PASSWORD_QUALITY) { quality = CredentialStorage.MIN_PASSWORD_QUALITY; } } return quality; }
/** * Save a lock pattern. * * @param pattern The new pattern to save. * @param isFallback Specifies if this is a fallback to biometric weak */ public void saveLockPattern(List<LockPatternView.Cell> pattern, boolean isFallback) { // Compute the hash final byte[] hash = LockPatternUtils.patternToHash(pattern); try { // Write the hash to file RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "rw"); // Truncate the file if pattern is null, to clear the lock if (pattern == null) { raf.setLength(0); } else { raf.write(hash, 0, hash.length); } raf.close(); DevicePolicyManager dpm = getDevicePolicyManager(); KeyStore keyStore = KeyStore.getInstance(); if (pattern != null) { keyStore.password(patternToString(pattern)); setBoolean(PATTERN_EVER_CHOSEN_KEY, true); if (!isFallback) { deleteGallery(); setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); dpm.setActivePasswordState( DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, pattern.size(), 0, 0, 0, 0, 0, 0); } else { setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK); setLong(PASSWORD_TYPE_ALTERNATE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); finishBiometricWeak(); dpm.setActivePasswordState( DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK, 0, 0, 0, 0, 0, 0, 0); } } else { if (keyStore.isEmpty()) { keyStore.reset(); } dpm.setActivePasswordState( DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0); } } catch (FileNotFoundException fnfe) { // Cant do much, unless we want to fail over to using the settings // provider Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename); } catch (IOException ioe) { // Cant do much Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename); } }
private void resetWhilePreservingInitState() { IBinder operationToken = mOperationToken; if (operationToken != null) { mKeyStore.abort(operationToken); } mOperationToken = null; mOperationHandle = 0; mChunkedStreamer = null; }
private void resetAll() { mKey = null; IBinder operationToken = mOperationToken; if (operationToken != null) { mKeyStore.abort(operationToken); } mOperationToken = null; mOperationHandle = 0; mChunkedStreamer = null; }
@Override public void finalize() throws Throwable { try { IBinder operationToken = mOperationToken; if (operationToken != null) { mKeyStore.abort(operationToken); } } finally { super.finalize(); } }
@Override public void onResume() { super.onResume(); // Check KeyStore here, so others do not need to deal with it. if (!mKeyStore.isUnlocked()) { if (!mUnlocking) { // Let us unlock KeyStore. See you later! Credentials.getInstance().unlock(getActivity()); } else { // We already tried, but it is still not working! finishFragment(); } mUnlocking = !mUnlocking; return; } // Now KeyStore is always unlocked. Reset the flag. mUnlocking = false; // Currently we are the only user of profiles in KeyStore. // Assuming KeyStore and KeyGuard do the right thing, we can // safely cache profiles in the memory. if (mPreferences.size() == 0) { PreferenceGroup group = getPreferenceScreen(); final Context context = getActivity(); final List<VpnProfile> profiles = loadVpnProfiles(mKeyStore); for (VpnProfile profile : profiles) { final VpnPreference pref = new VpnPreference(context, profile); pref.setOnPreferenceClickListener(this); mPreferences.put(profile.key, pref); group.addPreference(pref); } } // Show the dialog if there is one. if (mDialog != null) { mDialog.setOnDismissListener(this); mDialog.show(); } // Start monitoring. if (mUpdater == null) { mUpdater = new Handler(this); } mUpdater.sendEmptyMessage(0); // Register for context menu. Hmmm, getListView() is hidden? registerForContextMenu(getListView()); }
private void ensureKeystoreOperationInitialized() throws InvalidKeyException { if (mChunkedStreamer != null) { return; } if (mKey == null) { throw new IllegalStateException("Not initialized"); } KeymasterArguments keymasterArgs = new KeymasterArguments(); keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC); keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest); keymasterArgs.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mMacSizeBits); OperationResult opResult = mKeyStore.begin( mKey.getAlias(), KeymasterDefs.KM_PURPOSE_SIGN, true, keymasterArgs, null, // no additional entropy needed for HMAC because it's deterministic mKey.getUid()); if (opResult == null) { throw new KeyStoreConnectException(); } // Store operation token and handle regardless of the error code returned by KeyStore to // ensure that the operation gets aborted immediately if the code below throws an exception. mOperationToken = opResult.token; mOperationHandle = opResult.operationHandle; // If necessary, throw an exception due to KeyStore operation having failed. InvalidKeyException e = KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit( mKeyStore, mKey, opResult.resultCode); if (e != null) { throw e; } if (mOperationToken == null) { throw new ProviderException("Keystore returned null operation token"); } if (mOperationHandle == 0) { throw new ProviderException("Keystore returned invalid operation handle"); } mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer( new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(mKeyStore, mOperationToken)); }
@Override public Dialog onCreateDialog(Bundle savedInstanceState) { final Context context = getActivity(); final KeyStore keyStore = KeyStore.getInstance(); initProfiles(keyStore, context.getResources()); final AlertDialog.Builder builder = new AlertDialog.Builder(context); final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); builder.setTitle(R.string.vpn_menu_lockdown); final View view = dialogInflater.inflate(R.layout.vpn_lockdown_editor, null, false); final ListView listView = (ListView) view.findViewById(android.R.id.list); listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); listView.setAdapter(new TitleAdapter(context, mTitles)); listView.setItemChecked(mCurrentIndex, true); builder.setView(view); builder.setPositiveButton( android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { final int newIndex = listView.getCheckedItemPosition(); if (mCurrentIndex == newIndex) return; if (newIndex == 0) { keyStore.delete(Credentials.LOCKDOWN_VPN); } else { final VpnProfile profile = mProfiles.get(newIndex - 1); if (!profile.isValidLockdownProfile()) { Toast.makeText(context, R.string.vpn_lockdown_config_error, Toast.LENGTH_LONG) .show(); return; } keyStore.put(Credentials.LOCKDOWN_VPN, profile.key.getBytes()); } // kick profiles since we changed them ConnectivityManager.from(getActivity()).updateLockdownVpn(); } }); return builder.create(); }
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mDPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE); mKeyStore = KeyStore.getInstance(); mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this.getActivity()); // Defaults to needing to confirm credentials final boolean confirmCredentials = getActivity().getIntent().getBooleanExtra(CONFIRM_CREDENTIALS, true); mPasswordConfirmed = !confirmCredentials; if (savedInstanceState != null) { mPasswordConfirmed = savedInstanceState.getBoolean(PASSWORD_CONFIRMED); } }
private void loadCertificates(Spinner spinner, String prefix) { final Context context = mConfigUi.getContext(); String[] certs = KeyStore.getInstance().saw(prefix, android.os.Process.WIFI_UID); if (certs == null || certs.length == 0) { certs = new String[] {unspecifiedCert}; } else { final String[] array = new String[certs.length + 1]; array[0] = unspecifiedCert; System.arraycopy(certs, 0, array, 1, certs.length); certs = array; } final ArrayAdapter<String> adapter = new ArrayAdapter<String>(context, android.R.layout.simple_spinner_item, certs); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinner.setAdapter(adapter); }
public void onPatternDetected(List<LockPatternView.Cell> pattern) { if (mLockPatternUtils.checkPattern(pattern)) { mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct); mKeyguardStatusViewManager.setInstructionText(""); mKeyguardStatusViewManager.updateStatusLines(true); mCallback.keyguardDone(true); mCallback.reportSuccessfulUnlockAttempt(); KeyStore.getInstance().password(LockPatternUtils.patternToString(pattern)); } else { boolean reportFailedAttempt = false; if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) { mCallback.pokeWakelock(UNLOCK_PATTERN_WAKE_INTERVAL_MS); } mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { mTotalFailedPatternAttempts++; mFailedPatternAttemptsSinceLastTimeout++; reportFailedAttempt = true; } if (mFailedPatternAttemptsSinceLastTimeout >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) { long deadline = mLockPatternUtils.setLockoutAttemptDeadline(); handleAttemptLockout(deadline); } else { // TODO mUnlockIcon.setVisibility(View.VISIBLE); mKeyguardStatusViewManager.setInstructionText( getContext().getString(R.string.lockscreen_pattern_wrong)); mKeyguardStatusViewManager.updateStatusLines(true); mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS); } // Because the following can result in cleanUp() being called on this screen, // member variables reset in cleanUp() shouldn't be accessed after this call. if (reportFailedAttempt) { mCallback.reportFailedUnlockAttempt(); } } }
@Override public void onResume() { super.onResume(); // Make sure we reload the preference hierarchy since some of these settings // depend on others... createPreferenceHierarchy(); final LockPatternUtils lockPatternUtils = mChooseLockSettingsHelper.utils(); if (mVisiblePattern != null) { mVisiblePattern.setChecked(lockPatternUtils.isVisiblePatternEnabled()); } if (mTactileFeedback != null) { mTactileFeedback.setChecked(lockPatternUtils.isTactileFeedbackEnabled()); } if (mPowerButtonInstantlyLocks != null) { mPowerButtonInstantlyLocks.setChecked(lockPatternUtils.getPowerButtonInstantlyLocks()); } mShowPassword.setChecked( Settings.System.getInt(getContentResolver(), Settings.System.TEXT_SHOW_PASSWORD, 1) != 0); KeyStore.State state = KeyStore.getInstance().state(); mResetCredentials.setEnabled(state != KeyStore.State.UNINITIALIZED); // Location Settings updateLocationToggles(); if (mSettingsObserver == null) { mSettingsObserver = new Observer() { public void update(Observable o, Object arg) { updateLocationToggles(); } }; mContentQueryMap.addObserver(mSettingsObserver); } }
private void verifyPasswordAndUnlock() { String entry = mPasswordEntry.getText().toString(); if (mLockPatternUtils.checkPassword(entry)) { mCallback.keyguardDone(true); mCallback.reportSuccessfulUnlockAttempt(); mStatusViewManager.setInstructionText(null); KeyStore.getInstance().password(entry); } else if (entry.length() > MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) { // to avoid accidental lockout, only count attempts that are long enough to be a // real password. This may require some tweaking. mCallback.reportFailedUnlockAttempt(); if (0 == (mUpdateMonitor.getFailedAttempts() % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) { long deadline = mLockPatternUtils.setLockoutAttemptDeadline(); handleAttemptLockout(deadline); } mStatusViewManager.setInstructionText(mContext.getString(R.string.lockscreen_password_wrong)); } else if (entry.length() > 0) { mStatusViewManager.setInstructionText(mContext.getString(R.string.lockscreen_password_wrong)); } mPasswordEntry.setText(""); }
public class VpnSettings extends SettingsPreferenceFragment implements Handler.Callback, Preference.OnPreferenceClickListener, DialogInterface.OnClickListener, DialogInterface.OnDismissListener { private static final String TAG = "VpnSettings"; private static final String TAG_LOCKDOWN = "lockdown"; // TODO: migrate to using DialogFragment when editing private final IConnectivityManager mService = IConnectivityManager.Stub.asInterface( ServiceManager.getService(Context.CONNECTIVITY_SERVICE)); private final KeyStore mKeyStore = KeyStore.getInstance(); private boolean mUnlocking = false; private HashMap<String, VpnPreference> mPreferences = new HashMap<String, VpnPreference>(); private VpnDialog mDialog; private Handler mUpdater; private LegacyVpnInfo mInfo; // The key of the profile for the current ContextMenu. private String mSelectedKey; @Override public void onCreate(Bundle savedState) { super.onCreate(savedState); setHasOptionsMenu(true); addPreferencesFromResource(R.xml.vpn_settings2); if (savedState != null) { VpnProfile profile = VpnProfile.decode(savedState.getString("VpnKey"), savedState.getByteArray("VpnProfile")); if (profile != null) { mDialog = new VpnDialog(getActivity(), this, profile, savedState.getBoolean("VpnEditing")); } } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.vpn, menu); } @Override public void onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); // Hide lockdown VPN on devices that require IMS authentication if (SystemProperties.getBoolean("persist.radio.imsregrequired", false)) { menu.findItem(R.id.vpn_lockdown).setVisible(false); } } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.vpn_create: { // Generate a new key. Here we just use the current time. long millis = System.currentTimeMillis(); while (mPreferences.containsKey(Long.toHexString(millis))) { ++millis; } mDialog = new VpnDialog(getActivity(), this, new VpnProfile(Long.toHexString(millis)), true); mDialog.setOnDismissListener(this); mDialog.show(); return true; } case R.id.vpn_lockdown: { LockdownConfigFragment.show(this); return true; } } return super.onOptionsItemSelected(item); } @Override public void onSaveInstanceState(Bundle savedState) { // We do not save view hierarchy, as they are just profiles. if (mDialog != null) { VpnProfile profile = mDialog.getProfile(); savedState.putString("VpnKey", profile.key); savedState.putByteArray("VpnProfile", profile.encode()); savedState.putBoolean("VpnEditing", mDialog.isEditing()); } // else? } @Override public void onResume() { super.onResume(); // Check KeyStore here, so others do not need to deal with it. if (!mKeyStore.isUnlocked()) { if (!mUnlocking) { // Let us unlock KeyStore. See you later! Credentials.getInstance().unlock(getActivity()); } else { // We already tried, but it is still not working! finishFragment(); } mUnlocking = !mUnlocking; return; } // Now KeyStore is always unlocked. Reset the flag. mUnlocking = false; // Currently we are the only user of profiles in KeyStore. // Assuming KeyStore and KeyGuard do the right thing, we can // safely cache profiles in the memory. if (mPreferences.size() == 0) { PreferenceGroup group = getPreferenceScreen(); final Context context = getActivity(); final List<VpnProfile> profiles = loadVpnProfiles(mKeyStore); for (VpnProfile profile : profiles) { final VpnPreference pref = new VpnPreference(context, profile); pref.setOnPreferenceClickListener(this); mPreferences.put(profile.key, pref); group.addPreference(pref); } } // Show the dialog if there is one. if (mDialog != null) { mDialog.setOnDismissListener(this); mDialog.show(); } // Start monitoring. if (mUpdater == null) { mUpdater = new Handler(this); } mUpdater.sendEmptyMessage(0); // Register for context menu. Hmmm, getListView() is hidden? registerForContextMenu(getListView()); } @Override public void onPause() { super.onPause(); // Hide the dialog if there is one. if (mDialog != null) { mDialog.setOnDismissListener(null); mDialog.dismiss(); } // Unregister for context menu. if (getView() != null) { unregisterForContextMenu(getListView()); } } @Override public void onDismiss(DialogInterface dialog) { // Here is the exit of a dialog. mDialog = null; } @Override public void onClick(DialogInterface dialog, int button) { if (button == DialogInterface.BUTTON_POSITIVE) { // Always save the profile. VpnProfile profile = mDialog.getProfile(); mKeyStore.put(Credentials.VPN + profile.key, profile.encode()); // Update the preference. VpnPreference preference = mPreferences.get(profile.key); if (preference != null) { disconnect(profile.key); preference.update(profile); } else { preference = new VpnPreference(getActivity(), profile); preference.setOnPreferenceClickListener(this); mPreferences.put(profile.key, preference); getPreferenceScreen().addPreference(preference); } // If we are not editing, connect! if (!mDialog.isEditing()) { try { connect(profile); } catch (Exception e) { Log.e(TAG, "connect", e); } } } } @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) { if (mDialog != null) { Log.v(TAG, "onCreateContextMenu() is called when mDialog != null"); return; } if (info instanceof AdapterContextMenuInfo) { Preference preference = (Preference) getListView().getItemAtPosition(((AdapterContextMenuInfo) info).position); if (preference instanceof VpnPreference) { VpnProfile profile = ((VpnPreference) preference).getProfile(); mSelectedKey = profile.key; menu.setHeaderTitle(profile.name); menu.add(Menu.NONE, R.string.vpn_menu_edit, 0, R.string.vpn_menu_edit); menu.add(Menu.NONE, R.string.vpn_menu_delete, 0, R.string.vpn_menu_delete); } } } @Override public boolean onContextItemSelected(MenuItem item) { if (mDialog != null) { Log.v(TAG, "onContextItemSelected() is called when mDialog != null"); return false; } VpnPreference preference = mPreferences.get(mSelectedKey); if (preference == null) { Log.v(TAG, "onContextItemSelected() is called but no preference is found"); return false; } switch (item.getItemId()) { case R.string.vpn_menu_edit: mDialog = new VpnDialog(getActivity(), this, preference.getProfile(), true); mDialog.setOnDismissListener(this); mDialog.show(); return true; case R.string.vpn_menu_delete: disconnect(mSelectedKey); getPreferenceScreen().removePreference(preference); mPreferences.remove(mSelectedKey); mKeyStore.delete(Credentials.VPN + mSelectedKey); return true; } return false; } @Override public boolean onPreferenceClick(Preference preference) { if (mDialog != null) { Log.v(TAG, "onPreferenceClick() is called when mDialog != null"); return true; } if (preference instanceof VpnPreference) { VpnProfile profile = ((VpnPreference) preference).getProfile(); if (mInfo != null && profile.key.equals(mInfo.key) && mInfo.state == LegacyVpnInfo.STATE_CONNECTED) { try { mInfo.intent.send(); return true; } catch (Exception e) { // ignore } } mDialog = new VpnDialog(getActivity(), this, profile, false); } else { // Generate a new key. Here we just use the current time. long millis = System.currentTimeMillis(); while (mPreferences.containsKey(Long.toHexString(millis))) { ++millis; } mDialog = new VpnDialog(getActivity(), this, new VpnProfile(Long.toHexString(millis)), true); } mDialog.setOnDismissListener(this); mDialog.show(); return true; } @Override public boolean handleMessage(Message message) { mUpdater.removeMessages(0); if (isResumed()) { try { LegacyVpnInfo info = mService.getLegacyVpnInfo(); if (mInfo != null) { VpnPreference preference = mPreferences.get(mInfo.key); if (preference != null) { preference.update(-1); } mInfo = null; } if (info != null) { VpnPreference preference = mPreferences.get(info.key); if (preference != null) { preference.update(info.state); mInfo = info; } } } catch (Exception e) { // ignore } mUpdater.sendEmptyMessageDelayed(0, 1000); } return true; } private void connect(VpnProfile profile) throws Exception { try { mService.startLegacyVpn(profile); } catch (IllegalStateException e) { Toast.makeText(getActivity(), R.string.vpn_no_network, Toast.LENGTH_LONG).show(); } } private void disconnect(String key) { if (mInfo != null && key.equals(mInfo.key)) { try { mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN); } catch (Exception e) { // ignore } } } @Override protected int getHelpResource() { return R.string.help_url_vpn; } private static class VpnPreference extends Preference { private VpnProfile mProfile; private int mState = -1; VpnPreference(Context context, VpnProfile profile) { super(context); setPersistent(false); setOrder(0); mProfile = profile; update(); } VpnProfile getProfile() { return mProfile; } void update(VpnProfile profile) { mProfile = profile; update(); } void update(int state) { mState = state; update(); } void update() { if (mState < 0) { String[] types = getContext().getResources().getStringArray(R.array.vpn_types_long); setSummary(types[mProfile.type]); } else { String[] states = getContext().getResources().getStringArray(R.array.vpn_states); setSummary(states[mState]); } setTitle(mProfile.name); notifyHierarchyChanged(); } @Override public int compareTo(Preference preference) { int result = -1; if (preference instanceof VpnPreference) { VpnPreference another = (VpnPreference) preference; if ((result = another.mState - mState) == 0 && (result = mProfile.name.compareTo(another.mProfile.name)) == 0 && (result = mProfile.type - another.mProfile.type) == 0) { result = mProfile.key.compareTo(another.mProfile.key); } } return result; } } /** Dialog to configure always-on VPN. */ public static class LockdownConfigFragment extends DialogFragment { private List<VpnProfile> mProfiles; private List<CharSequence> mTitles; private int mCurrentIndex; private static class TitleAdapter extends ArrayAdapter<CharSequence> { public TitleAdapter(Context context, List<CharSequence> objects) { super( context, com.android.internal.R.layout.select_dialog_singlechoice_holo, android.R.id.text1, objects); } } public static void show(VpnSettings parent) { if (!parent.isAdded()) return; final LockdownConfigFragment dialog = new LockdownConfigFragment(); dialog.show(parent.getFragmentManager(), TAG_LOCKDOWN); } private static String getStringOrNull(KeyStore keyStore, String key) { final byte[] value = keyStore.get(Credentials.LOCKDOWN_VPN); return value == null ? null : new String(value); } private void initProfiles(KeyStore keyStore, Resources res) { final String lockdownKey = getStringOrNull(keyStore, Credentials.LOCKDOWN_VPN); mProfiles = loadVpnProfiles(keyStore, VpnProfile.TYPE_PPTP); mTitles = Lists.newArrayList(); mTitles.add(res.getText(R.string.vpn_lockdown_none)); mCurrentIndex = 0; for (VpnProfile profile : mProfiles) { if (TextUtils.equals(profile.key, lockdownKey)) { mCurrentIndex = mTitles.size(); } mTitles.add(profile.name); } } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final Context context = getActivity(); final KeyStore keyStore = KeyStore.getInstance(); initProfiles(keyStore, context.getResources()); final AlertDialog.Builder builder = new AlertDialog.Builder(context); final LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext()); builder.setTitle(R.string.vpn_menu_lockdown); final View view = dialogInflater.inflate(R.layout.vpn_lockdown_editor, null, false); final ListView listView = (ListView) view.findViewById(android.R.id.list); listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); listView.setAdapter(new TitleAdapter(context, mTitles)); listView.setItemChecked(mCurrentIndex, true); builder.setView(view); builder.setPositiveButton( android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { final int newIndex = listView.getCheckedItemPosition(); if (mCurrentIndex == newIndex) return; if (newIndex == 0) { keyStore.delete(Credentials.LOCKDOWN_VPN); } else { final VpnProfile profile = mProfiles.get(newIndex - 1); if (!profile.isValidLockdownProfile()) { Toast.makeText(context, R.string.vpn_lockdown_config_error, Toast.LENGTH_LONG) .show(); return; } keyStore.put(Credentials.LOCKDOWN_VPN, profile.key.getBytes()); } // kick profiles since we changed them ConnectivityManager.from(getActivity()).updateLockdownVpn(); } }); return builder.create(); } } private static List<VpnProfile> loadVpnProfiles(KeyStore keyStore, int... excludeTypes) { final ArrayList<VpnProfile> result = Lists.newArrayList(); final String[] keys = keyStore.saw(Credentials.VPN); if (keys != null) { for (String key : keys) { final VpnProfile profile = VpnProfile.decode(key, keyStore.get(Credentials.VPN + key)); if (profile != null && !ArrayUtils.contains(excludeTypes, profile.type)) { result.add(profile); } } } return result; } }
/** * Save a lock password. Does not ensure that the password is as good as the requested mode, but * will adjust the mode to be as good as the pattern. * * @param password The password to save * @param quality {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)} * @param isFallback Specifies if this is a fallback to biometric weak */ public void saveLockPassword(String password, int quality, boolean isFallback) { // Compute the hash final byte[] hash = passwordToHash(password); try { // Write the hash to file RandomAccessFile raf = new RandomAccessFile(sLockPasswordFilename, "rw"); // Truncate the file if pattern is null, to clear the lock if (password == null) { raf.setLength(0); } else { raf.write(hash, 0, hash.length); } raf.close(); DevicePolicyManager dpm = getDevicePolicyManager(); KeyStore keyStore = KeyStore.getInstance(); if (password != null) { // Update the encryption password. updateEncryptionPassword(password); // Update the keystore password keyStore.password(password); int computedQuality = computePasswordQuality(password); if (!isFallback) { deleteGallery(); setLong(PASSWORD_TYPE_KEY, Math.max(quality, computedQuality)); if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { int letters = 0; int uppercase = 0; int lowercase = 0; int numbers = 0; int symbols = 0; int nonletter = 0; for (int i = 0; i < password.length(); i++) { char c = password.charAt(i); if (c >= 'A' && c <= 'Z') { letters++; uppercase++; } else if (c >= 'a' && c <= 'z') { letters++; lowercase++; } else if (c >= '0' && c <= '9') { numbers++; nonletter++; } else { symbols++; nonletter++; } } dpm.setActivePasswordState( Math.max(quality, computedQuality), password.length(), letters, uppercase, lowercase, numbers, symbols, nonletter); } else { // The password is not anything. dpm.setActivePasswordState( DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0); } } else { // Case where it's a fallback for biometric weak setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK); setLong(PASSWORD_TYPE_ALTERNATE_KEY, Math.max(quality, computedQuality)); finishBiometricWeak(); dpm.setActivePasswordState( DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK, 0, 0, 0, 0, 0, 0, 0); } // Add the password to the password history. We assume all // password // hashes have the same length for simplicity of implementation. String passwordHistory = getString(PASSWORD_HISTORY_KEY); if (passwordHistory == null) { passwordHistory = new String(); } int passwordHistoryLength = getRequestedPasswordHistoryLength(); if (passwordHistoryLength == 0) { passwordHistory = ""; } else { passwordHistory = new String(hash) + "," + passwordHistory; // Cut it to contain passwordHistoryLength hashes // and passwordHistoryLength -1 commas. passwordHistory = passwordHistory.substring( 0, Math.min( hash.length * passwordHistoryLength + passwordHistoryLength - 1, passwordHistory.length())); } setString(PASSWORD_HISTORY_KEY, passwordHistory); } else { // Conditionally reset the keystore if empty. If // non-empty, we are just switching key guard type if (keyStore.isEmpty()) { keyStore.reset(); } dpm.setActivePasswordState( DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0); } } catch (FileNotFoundException fnfe) { // Cant do much, unless we want to fail over to using the settings provider Log.e(TAG, "Unable to save lock pattern to " + sLockPasswordFilename); } catch (IOException ioe) { // Cant do much Log.e(TAG, "Unable to save lock pattern to " + sLockPasswordFilename); } }
private static String getStringOrNull(KeyStore keyStore, String key) { final byte[] value = keyStore.get(Credentials.LOCKDOWN_VPN); return value == null ? null : new String(value); }
/** * {@link MacSpi} which provides HMAC implementations backed by Android KeyStore. * * @hide */ public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOperation { public static class HmacSHA1 extends AndroidKeyStoreHmacSpi { public HmacSHA1() { super(KeymasterDefs.KM_DIGEST_SHA1); } } public static class HmacSHA224 extends AndroidKeyStoreHmacSpi { public HmacSHA224() { super(KeymasterDefs.KM_DIGEST_SHA_2_224); } } public static class HmacSHA256 extends AndroidKeyStoreHmacSpi { public HmacSHA256() { super(KeymasterDefs.KM_DIGEST_SHA_2_256); } } public static class HmacSHA384 extends AndroidKeyStoreHmacSpi { public HmacSHA384() { super(KeymasterDefs.KM_DIGEST_SHA_2_384); } } public static class HmacSHA512 extends AndroidKeyStoreHmacSpi { public HmacSHA512() { super(KeymasterDefs.KM_DIGEST_SHA_2_512); } } private final KeyStore mKeyStore = KeyStore.getInstance(); private final int mKeymasterDigest; private final int mMacSizeBits; // Fields below are populated by engineInit and should be preserved after engineDoFinal. private AndroidKeyStoreSecretKey mKey; // Fields below are reset when engineDoFinal succeeds. private KeyStoreCryptoOperationChunkedStreamer mChunkedStreamer; private IBinder mOperationToken; private long mOperationHandle; protected AndroidKeyStoreHmacSpi(int keymasterDigest) { mKeymasterDigest = keymasterDigest; mMacSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest); } @Override protected int engineGetMacLength() { return (mMacSizeBits + 7) / 8; } @Override protected void engineInit(Key key, AlgorithmParameterSpec params) throws InvalidKeyException, InvalidAlgorithmParameterException { resetAll(); boolean success = false; try { init(key, params); ensureKeystoreOperationInitialized(); success = true; } finally { if (!success) { resetAll(); } } } private void init(Key key, AlgorithmParameterSpec params) throws InvalidKeyException, InvalidAlgorithmParameterException { if (key == null) { throw new InvalidKeyException("key == null"); } else if (!(key instanceof AndroidKeyStoreSecretKey)) { throw new InvalidKeyException("Only Android KeyStore secret keys supported. Key: " + key); } mKey = (AndroidKeyStoreSecretKey) key; if (params != null) { throw new InvalidAlgorithmParameterException("Unsupported algorithm parameters: " + params); } } private void resetAll() { mKey = null; IBinder operationToken = mOperationToken; if (operationToken != null) { mKeyStore.abort(operationToken); } mOperationToken = null; mOperationHandle = 0; mChunkedStreamer = null; } private void resetWhilePreservingInitState() { IBinder operationToken = mOperationToken; if (operationToken != null) { mKeyStore.abort(operationToken); } mOperationToken = null; mOperationHandle = 0; mChunkedStreamer = null; } @Override protected void engineReset() { resetWhilePreservingInitState(); } private void ensureKeystoreOperationInitialized() throws InvalidKeyException { if (mChunkedStreamer != null) { return; } if (mKey == null) { throw new IllegalStateException("Not initialized"); } KeymasterArguments keymasterArgs = new KeymasterArguments(); keymasterArgs.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC); keymasterArgs.addEnum(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest); keymasterArgs.addUnsignedInt(KeymasterDefs.KM_TAG_MAC_LENGTH, mMacSizeBits); OperationResult opResult = mKeyStore.begin( mKey.getAlias(), KeymasterDefs.KM_PURPOSE_SIGN, true, keymasterArgs, null, // no additional entropy needed for HMAC because it's deterministic mKey.getUid()); if (opResult == null) { throw new KeyStoreConnectException(); } // Store operation token and handle regardless of the error code returned by KeyStore to // ensure that the operation gets aborted immediately if the code below throws an exception. mOperationToken = opResult.token; mOperationHandle = opResult.operationHandle; // If necessary, throw an exception due to KeyStore operation having failed. InvalidKeyException e = KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit( mKeyStore, mKey, opResult.resultCode); if (e != null) { throw e; } if (mOperationToken == null) { throw new ProviderException("Keystore returned null operation token"); } if (mOperationHandle == 0) { throw new ProviderException("Keystore returned invalid operation handle"); } mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer( new KeyStoreCryptoOperationChunkedStreamer.MainDataStream(mKeyStore, mOperationToken)); } @Override protected void engineUpdate(byte input) { engineUpdate(new byte[] {input}, 0, 1); } @Override protected void engineUpdate(byte[] input, int offset, int len) { try { ensureKeystoreOperationInitialized(); } catch (InvalidKeyException e) { throw new ProviderException("Failed to reinitialize MAC", e); } byte[] output; try { output = mChunkedStreamer.update(input, offset, len); } catch (KeyStoreException e) { throw new ProviderException("Keystore operation failed", e); } if ((output != null) && (output.length != 0)) { throw new ProviderException("Update operation unexpectedly produced output"); } } @Override protected byte[] engineDoFinal() { try { ensureKeystoreOperationInitialized(); } catch (InvalidKeyException e) { throw new ProviderException("Failed to reinitialize MAC", e); } byte[] result; try { result = mChunkedStreamer.doFinal( null, 0, 0, null, // no signature provided -- this invocation will generate one null // no additional entropy needed -- HMAC is deterministic ); } catch (KeyStoreException e) { throw new ProviderException("Keystore operation failed", e); } resetWhilePreservingInitState(); return result; } @Override public void finalize() throws Throwable { try { IBinder operationToken = mOperationToken; if (operationToken != null) { mKeyStore.abort(operationToken); } } finally { super.finalize(); } } @Override public long getOperationHandle() { return mOperationHandle; } }