/* This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. Keepass2Android is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Keepass2Android is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Keepass2Android. If not, see . */ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using Android.App; using Android.App.Admin; using Android.Content; using Android.Content.PM; using Android.Graphics; using Android.OS; using Android.Preferences; using Android.Views; using Android.Widget; using Google.Android.Material.Dialog; using Java.Util; using keepass2android; using KeePassLib.Cryptography; using Newtonsoft.Json; using OtpKeyProv; namespace keepass2android { [Activity(Label = "@string/app_name", Theme = "@style/Kp2aTheme_ActionBar", WindowSoftInputMode = SoftInput.StateHidden, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden)] public class GeneratePasswordActivity : #if DEBUG LifecycleAwareActivity #else LockCloseActivity #endif { private readonly int[] _buttonLengthButtonIds = new[] {Resource.Id.btn_length6, Resource.Id.btn_length8, Resource.Id.btn_length12, Resource.Id.btn_length16, Resource.Id.btn_length24, Resource.Id.btn_length32}; private readonly int[] _checkboxIds = new[] {Resource.Id.cb_uppercase, Resource.Id.cb_lowercase, Resource.Id.cb_digits, Resource.Id.cb_minus, Resource.Id.cb_underline, Resource.Id.cb_space, Resource.Id.cb_specials, Resource.Id.cb_specials_extended, Resource.Id.cb_brackets, Resource.Id.cb_at_least_one_from_each_group, Resource.Id.cb_exclude_lookalike }; PasswordFont _passwordFont = new PasswordFont(); private ActivityDesign _design; public GeneratePasswordActivity() { _design = new ActivityDesign(this); } public static void Launch(Activity act) { Intent i = new Intent(act, typeof(GeneratePasswordActivity)); act.StartActivityForResult(i, 0); } public static void LaunchWithoutLockCheck(Activity act) { Intent i = new Intent(act, typeof(GeneratePasswordActivity)); #if DEBUG #else i.PutExtra(NoLockCheck, true); #endif act.StartActivityForResult(i, 0); } private PasswordProfiles _profiles; private bool _updateDisabled = false; class PasswordProfiles { public List> Profiles { get; set; } public PasswordGenerator.CombinedKeyOptions LastUsedSettings { get; set; } public int? TryFindLastUsedProfileIndex() { for (int i=0;i(key, options); return; } } Profiles.Add(new KeyValuePair(key, options)); } public void Remove(in int profileIndex) { Profiles.RemoveAt(profileIndex); } } protected override void OnCreate(Bundle savedInstanceState) { _design.ApplyTheme(); base.OnCreate(savedInstanceState); SetContentView(Resource.Layout.generate_password); SetResult(KeePass.ExitNormal); var prefs = GetPreferences(FileCreationMode.Private); string jsonOptions = prefs.GetString("password_generator_profiles", null); if (jsonOptions != null) { try { _profiles = JsonConvert.DeserializeObject(jsonOptions); } catch (Exception e) { Kp2aLog.LogUnexpectedError(e); } } else { PasswordGenerator.CombinedKeyOptions options = new PasswordGenerator.CombinedKeyOptions() { PasswordGenerationOptions = new PasswordGenerator.PasswordGenerationOptions() { Length = prefs.GetInt("length", 12), UpperCase = prefs.GetBoolean("cb_uppercase", true), LowerCase = prefs.GetBoolean("cb_lowercase", true), Digits = prefs.GetBoolean("cb_digits", true), Minus = prefs.GetBoolean("cb_minus", false), Underline = prefs.GetBoolean("cb_underline", false), Space = prefs.GetBoolean("cb_space", false), Specials = prefs.GetBoolean("cb_specials", false), SpecialsExtended = false, Brackets = prefs.GetBoolean("cb_brackets", false) } }; _profiles = new PasswordProfiles() { LastUsedSettings = options, Profiles = GetDefaultProfiles() }; } _profiles ??= new PasswordProfiles(); _profiles.LastUsedSettings ??= new PasswordGenerator.CombinedKeyOptions() { PasswordGenerationOptions = new PasswordGenerator.PasswordGenerationOptions() {Length = 7, UpperCase = true, LowerCase = true, Digits = true} }; _profiles.Profiles ??= new List>(); _updateDisabled = true; PopulateFieldsFromOptions(_profiles.LastUsedSettings); _updateDisabled = false; var profileSpinner = UpdateProfileSpinner(); profileSpinner.ItemSelected += (sender, args) => { if (profileSpinner.SelectedItemPosition > 0) { _profiles.LastUsedSettings = _profiles.Profiles[profileSpinner.SelectedItemPosition - 1].Value; _updateDisabled = true; PopulateFieldsFromOptions(_profiles.LastUsedSettings); _updateDisabled = false; UpdatePassword(); } }; foreach (int id in _buttonLengthButtonIds) { Button button = (Button) FindViewById(id); button.Click += (sender, e) => { Button b = (Button) sender; EditText editText = (EditText) FindViewById(Resource.Id.length); editText.Text = b.Text; }; } FindViewById(Resource.Id.length).TextChanged += (sender, args) => UpdatePassword(); FindViewById(Resource.Id.wordcount).TextChanged += (sender, args) => UpdatePassword(); FindViewById(Resource.Id.wordseparator).TextChanged += (sender, args) => UpdatePassword(); foreach (int id in _checkboxIds) { FindViewById(id).CheckedChange += (sender, args) => UpdatePassword(); } var mode_spinner = FindViewById(Resource.Id.spinner_password_generator_mode); mode_spinner.ItemSelected += (sender, args) => { FindViewById(Resource.Id.passphraseOptions).Visibility = mode_spinner.SelectedItemPosition == 0 ? ViewStates.Gone : ViewStates.Visible; FindViewById(Resource.Id.passwordOptions).Visibility = mode_spinner.SelectedItemPosition == 1 ? ViewStates.Gone : ViewStates.Visible; UpdatePassword(); }; FindViewById(Resource.Id.spinner_password_generator_case_mode).ItemSelected += (sender, args) => { UpdatePassword(); }; Button genPassButton = (Button) FindViewById(Resource.Id.generate_password_button); genPassButton.Click += (sender, e) => { UpdatePassword(); }; View acceptButton = FindViewById(Resource.Id.accept_button); acceptButton.Click += (sender, e) => { EditText password = (EditText) FindViewById(Resource.Id.password_edit); Intent intent = new Intent(); intent.PutExtra("keepass2android.password.generated_password", password.Text); SetResult(KeePass.ResultOkPasswordGenerator, intent); Finish(); }; View cancelButton = FindViewById(Resource.Id.cancel_button); cancelButton.Click += (sender, e) => { SetResult(Result.Canceled); Finish(); }; FindViewById(Resource.Id.btn_password_generator_profile_save) .Click += (sender, args) => { var editText = new EditText(this); new MaterialAlertDialogBuilder(this) .SetMessage(Resource.String.save_password_generation_profile_text) .SetView(editText) .SetPositiveButton(Android.Resource.String.Ok, (o, eventArgs) => { _profiles.Add(editText.Text, GetOptions()); UpdateProfileSpinner(); }) .Show(); }; FindViewById(Resource.Id.btn_password_generator_profile_delete) .Click += (sender, args) => { if (profileSpinner.SelectedItemPosition > 0) { _profiles.Remove(profileSpinner.SelectedItemPosition-1); UpdateProfileSpinner(); } }; EditText txtPasswordToSet = (EditText) FindViewById(Resource.Id.password_edit); _passwordFont.ApplyTo(txtPasswordToSet); UpdatePassword(); SupportActionBar.SetDisplayHomeAsUpEnabled(true); SupportActionBar.SetHomeButtonEnabled(true); } private Spinner UpdateProfileSpinner() { string[] profileNames = new List {GetString(Resource.String.custom_settings)} .Concat(_profiles.Profiles.Select(p => p.Key)) .ToArray(); ArrayAdapter profileArrayAdapter = new ArrayAdapter(this, Android.Resource.Layout.SimpleSpinnerDropDownItem, profileNames); var profileSpinner = FindViewById(Resource.Id.spinner_password_generator_profile); profileSpinner.Adapter = profileArrayAdapter; UpdateProfileSpinnerSelection(); return profileSpinner; } private static List> GetDefaultProfiles() { return new List>() { new KeyValuePair( "Simple12", new PasswordGenerator.CombinedKeyOptions() { PasswordGenerationOptions = new PasswordGenerator.PasswordGenerationOptions() { Length = 12, AtLeastOneFromEachGroup = true, ExcludeLookAlike = true, Digits = true, LowerCase = true, UpperCase = true } } ), new KeyValuePair( "Special12", new PasswordGenerator.CombinedKeyOptions() { PasswordGenerationOptions = new PasswordGenerator.PasswordGenerationOptions() { Length = 12, AtLeastOneFromEachGroup = true, ExcludeLookAlike = true, Digits = true, LowerCase = true, UpperCase = true,Specials = true,Brackets = true } } ), new KeyValuePair( "Password64", new PasswordGenerator.CombinedKeyOptions() { PasswordGenerationOptions = new PasswordGenerator.PasswordGenerationOptions() { Length = 64, AtLeastOneFromEachGroup = true, Digits = true, LowerCase = true, UpperCase = true, ExcludeLookAlike = false,Specials = true,Brackets = true, Minus = true, Space = true, SpecialsExtended = true,Underline = true } } ), new KeyValuePair( "Passphrase7", new PasswordGenerator.CombinedKeyOptions() { PassphraseGenerationOptions = new PasswordGenerator.PassphraseGenerationOptions() { WordCount = 7, CaseMode = PasswordGenerator.PassphraseGenerationOptions.PassphraseCaseMode.Lowercase, Separator = " " } } ), new KeyValuePair( "Passphrase5Plus", new PasswordGenerator.CombinedKeyOptions() { PassphraseGenerationOptions = new PasswordGenerator.PassphraseGenerationOptions() { WordCount = 5, CaseMode = PasswordGenerator.PassphraseGenerationOptions.PassphraseCaseMode.PascalCase, Separator = " " }, PasswordGenerationOptions = new PasswordGenerator.PasswordGenerationOptions() { Length = 2, AtLeastOneFromEachGroup = true, ExcludeLookAlike = true, Digits = true, Specials = true } } ) }; } private void PopulateFieldsFromOptions(PasswordGenerator.CombinedKeyOptions combinedOptions) { PasswordGenerator.PasswordGenerationOptions passwordOptions = combinedOptions.PasswordGenerationOptions; if (passwordOptions != null) { ((CheckBox)FindViewById(Resource.Id.cb_uppercase)).Checked = passwordOptions.UpperCase; ((CheckBox)FindViewById(Resource.Id.cb_lowercase)).Checked = passwordOptions.LowerCase; ((CheckBox)FindViewById(Resource.Id.cb_digits)).Checked = passwordOptions.Digits; ((CheckBox)FindViewById(Resource.Id.cb_minus)).Checked = passwordOptions.Minus; ((CheckBox)FindViewById(Resource.Id.cb_underline)).Checked = passwordOptions.Underline; ((CheckBox)FindViewById(Resource.Id.cb_space)).Checked = passwordOptions.Space; ((CheckBox)FindViewById(Resource.Id.cb_specials)).Checked = passwordOptions.Specials; ((CheckBox)FindViewById(Resource.Id.cb_specials_extended)).Checked = passwordOptions.SpecialsExtended; ((CheckBox)FindViewById(Resource.Id.cb_brackets)).Checked = passwordOptions.Brackets; ((CheckBox)FindViewById(Resource.Id.cb_exclude_lookalike)).Checked = passwordOptions.ExcludeLookAlike; ((CheckBox)FindViewById(Resource.Id.cb_at_least_one_from_each_group)).Checked = passwordOptions.AtLeastOneFromEachGroup; ((EditText)FindViewById(Resource.Id.length)).Text = passwordOptions.Length.ToString(CultureInfo.InvariantCulture); FindViewById(Resource.Id.passwordOptions).Visibility = ViewStates.Visible; } else { FindViewById(Resource.Id.passwordOptions).Visibility = ViewStates.Gone; } var passphraseOptions = combinedOptions.PassphraseGenerationOptions; if (passphraseOptions != null) { FindViewById(Resource.Id.wordcount).Text = passphraseOptions.WordCount.ToString(CultureInfo.InvariantCulture); FindViewById(Resource.Id.wordseparator).Text = passphraseOptions.Separator; FindViewById(Resource.Id.spinner_password_generator_case_mode) .SetSelection((int)passphraseOptions.CaseMode); FindViewById(Resource.Id.passphraseOptions).Visibility = ViewStates.Visible; } else { FindViewById(Resource.Id.passphraseOptions).Visibility = ViewStates.Gone; } int mode; if (combinedOptions.PasswordGenerationOptions != null && combinedOptions.PassphraseGenerationOptions != null) mode = 2; else if (combinedOptions.PasswordGenerationOptions == null && combinedOptions.PassphraseGenerationOptions != null) mode = 1; else mode = 0; FindViewById(Resource.Id.spinner_password_generator_mode) .SetSelection(mode); } private void UpdatePassword() { if (_updateDisabled) return; String password = ""; uint passwordBits = 0; Task.Run(() => { password = GeneratePassword(); passwordBits = QualityEstimation.EstimatePasswordBits(password.ToCharArray()); RunOnUiThread(() => { EditText txtPassword = (EditText)FindViewById(Resource.Id.password_edit); txtPassword.Text = password; var progressBar = FindViewById(Resource.Id.pb_password_strength); progressBar.Progress = (int)passwordBits; progressBar.Max = 128; Color color = new Color(196, 63, 49); if (passwordBits > 40) { color = new Color(219, 152, 55); } if (passwordBits > 64) { color = new Color(96, 138, 38); } if (passwordBits > 100) { color = new Color(31, 128, 31); } progressBar.ProgressDrawable.SetColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SrcIn)); FindViewById(Resource.Id.tv_password_strength).Text = " " + passwordBits + " bits"; UpdateProfileSpinnerSelection(); }); }); } private void UpdateProfileSpinnerSelection() { int? lastUsedIndex = _profiles.TryFindLastUsedProfileIndex(); FindViewById(Resource.Id.spinner_password_generator_profile) .SetSelection(lastUsedIndex != null ? lastUsedIndex.Value + 1 : 0); } public String GeneratePassword() { String password = ""; try { PasswordGenerator generator = new PasswordGenerator(this); var options = GetOptions(); try { password = generator.GeneratePassword(options); } catch (Exception e) { throw new ArgumentException(GetString(Resource.String.error_pass_gen_type)); } _profiles.LastUsedSettings = options; SaveProfiles(); } catch (Exception e) { Toast.MakeText(this, e.Message, ToastLength.Long).Show(); } return password; } private void SaveProfiles() { var prefs = GetPreferences(FileCreationMode.Private); prefs.Edit() .PutString("password_generator_profiles", JsonConvert.SerializeObject(_profiles)) .Commit(); } private PasswordGenerator.CombinedKeyOptions GetOptions() { PasswordGenerator.CombinedKeyOptions options = new PasswordGenerator.CombinedKeyOptions(); if (FindViewById(Resource.Id.passphraseOptions).Visibility == ViewStates.Visible) { int wordCount; if (!int.TryParse(((EditText)FindViewById(Resource.Id.wordcount)).Text, out wordCount)) { throw new Exception(GetString(Resource.String.error_wrong_length)); } options.PassphraseGenerationOptions = new PasswordGenerator.PassphraseGenerationOptions() { WordCount = wordCount, Separator = FindViewById(Resource.Id.wordseparator).Text, CaseMode = (PasswordGenerator.PassphraseGenerationOptions.PassphraseCaseMode)FindViewById(Resource.Id.spinner_password_generator_case_mode).SelectedItemPosition }; } if (FindViewById(Resource.Id.passwordOptions).Visibility == ViewStates.Visible) { int length; if (!int.TryParse(((EditText) FindViewById(Resource.Id.length)).Text, out length)) { throw new Exception(GetString(Resource.String.error_wrong_length)); } options.PasswordGenerationOptions = new PasswordGenerator.PasswordGenerationOptions() { Length = length, UpperCase = ((CheckBox) FindViewById(Resource.Id.cb_uppercase)).Checked, LowerCase = ((CheckBox) FindViewById(Resource.Id.cb_lowercase)).Checked, Digits = ((CheckBox) FindViewById(Resource.Id.cb_digits)).Checked, Minus = ((CheckBox) FindViewById(Resource.Id.cb_minus)).Checked, Underline = ((CheckBox) FindViewById(Resource.Id.cb_underline)).Checked, Space = ((CheckBox) FindViewById(Resource.Id.cb_space)).Checked, Specials = ((CheckBox) FindViewById(Resource.Id.cb_specials)).Checked, SpecialsExtended = ((CheckBox) FindViewById(Resource.Id.cb_specials_extended)).Checked, Brackets = ((CheckBox) FindViewById(Resource.Id.cb_brackets)).Checked, ExcludeLookAlike = ((CheckBox) FindViewById(Resource.Id.cb_exclude_lookalike)).Checked, AtLeastOneFromEachGroup = ((CheckBox) FindViewById(Resource.Id.cb_at_least_one_from_each_group)) .Checked }; } return options; } public override bool OnOptionsItemSelected(IMenuItem item) { switch (item.ItemId) { case Android.Resource.Id.Home: OnBackPressed(); return true; } return false; } } }