implement editor for TOTP settings in EntryEditActivity, closes https://github.com/PhilippC/keepass2android/issues/770
This commit is contained in:
		| @@ -33,12 +33,17 @@ using KeePassLib.Security; | |||||||
| using Android.Content.PM; | using Android.Content.PM; | ||||||
| using System.IO; | using System.IO; | ||||||
| using System.Globalization; | using System.Globalization; | ||||||
|  | using System.Net; | ||||||
|  | using System.Text; | ||||||
|  | using Android.Content.Res; | ||||||
| using Android.Database; | using Android.Database; | ||||||
| using Android.Graphics; | using Android.Graphics; | ||||||
| using Android.Graphics.Drawables; | using Android.Graphics.Drawables; | ||||||
| using Android.Util; | using Android.Util; | ||||||
| using keepass2android.Io; | using keepass2android.Io; | ||||||
| using KeePassLib.Serialization; | using KeePassLib.Serialization; | ||||||
|  | using KeeTrayTOTP.Libraries; | ||||||
|  | using PluginTOTP; | ||||||
| using Debug = System.Diagnostics.Debug; | using Debug = System.Diagnostics.Debug; | ||||||
| using File = System.IO.File; | using File = System.IO.File; | ||||||
| using Object = Java.Lang.Object; | using Object = Java.Lang.Object; | ||||||
| @@ -287,7 +292,31 @@ namespace keepass2android | |||||||
| 				EditAdvancedString(ees.FindViewById(Resource.Id.edit_extra)); | 				EditAdvancedString(ees.FindViewById(Resource.Id.edit_extra)); | ||||||
| 			}; | 			}; | ||||||
| 			SetAddExtraStringEnabled(); | 			SetAddExtraStringEnabled(); | ||||||
| 		    FindViewById(Resource.Id.entry_extras_container).Visibility = | 			 | ||||||
|  |  | ||||||
|  |             Button configureTotpButton = (Button)FindViewById(Resource.Id.configure_totp); | ||||||
|  |  | ||||||
|  |             configureTotpButton.Visibility = CanConfigureOtpSettings() ? ViewStates.Gone : ViewStates.Visible; | ||||||
|  | 			configureTotpButton.Click += (sender, e) => | ||||||
|  |             { | ||||||
|  |                 bool added = false; | ||||||
|  |                 View ees = FindExtraEditSection("otp"); | ||||||
|  | 				if (ees == null) | ||||||
|  |                 { | ||||||
|  |                     LinearLayout container = (LinearLayout) FindViewById(Resource.Id.advanced_container); | ||||||
|  |  | ||||||
|  |                     KeyValuePair<string, ProtectedString> pair = | ||||||
|  |                         new KeyValuePair<string, ProtectedString>("otp", new ProtectedString(true, "")); | ||||||
|  |                     ees = CreateExtraStringView(pair); | ||||||
|  |                     container.AddView(ees); | ||||||
|  |                     added = true; | ||||||
|  |                 } | ||||||
|  |                  | ||||||
|  |  | ||||||
|  | 				EditTotpString(ees.FindViewById(Resource.Id.edit_extra)); | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  | 			FindViewById(Resource.Id.entry_extras_container).Visibility = | ||||||
| 		        State.EditMode.ShowAddExtras || State.Entry.Strings.Any(s => !PwDefs.IsStandardField(s.Key)) ? ViewStates.Visible : ViewStates.Gone; | 		        State.EditMode.ShowAddExtras || State.Entry.Strings.Any(s => !PwDefs.IsStandardField(s.Key)) ? ViewStates.Visible : ViewStates.Gone; | ||||||
| 		    FindViewById(Resource.Id.entry_binaries_container).Visibility = | 		    FindViewById(Resource.Id.entry_binaries_container).Visibility = | ||||||
| 		        State.EditMode.ShowAddAttachments || State.Entry.Binaries.Any() ? ViewStates.Visible : ViewStates.Gone; | 		        State.EditMode.ShowAddAttachments || State.Entry.Binaries.Any() ? ViewStates.Visible : ViewStates.Gone; | ||||||
| @@ -402,9 +431,17 @@ namespace keepass2android | |||||||
| 		private void SetAddExtraStringEnabled() | 		private void SetAddExtraStringEnabled() | ||||||
| 		{ | 		{ | ||||||
| 			((Button)FindViewById(Resource.Id.add_advanced)).Visibility = (!App.Kp2a.CurrentDb.DatabaseFormat.CanHaveCustomFields || !State.EditMode.ShowAddExtras) ? ViewStates.Gone : ViewStates.Visible; | 			((Button)FindViewById(Resource.Id.add_advanced)).Visibility = (!App.Kp2a.CurrentDb.DatabaseFormat.CanHaveCustomFields || !State.EditMode.ShowAddExtras) ? ViewStates.Gone : ViewStates.Visible; | ||||||
|  |             ((Button)FindViewById(Resource.Id.configure_totp)).Visibility = CanConfigureOtpSettings() ? ViewStates.Gone : ViewStates.Visible; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		private void MakePasswordVisibleOrHidden() |         private bool CanConfigureOtpSettings() | ||||||
|  |         { | ||||||
|  |             return (!App.Kp2a.CurrentDb.DatabaseFormat.CanHaveCustomFields || !State.EditMode.ShowAddExtras)  | ||||||
|  |                 && (new Kp2aTotp().TryGetAdapter(new PwEntryOutput(State.Entry, App.Kp2a.CurrentDb)) == null || (State.Entry.Strings.GetKeys().Contains("otp"))) //only allow to edit KeeWeb/KeepassXC style otps | ||||||
|  |                 ; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void MakePasswordVisibleOrHidden() | ||||||
| 		{ | 		{ | ||||||
| 		    EditText password = (EditText) FindViewById(Resource.Id.entry_password); | 		    EditText password = (EditText) FindViewById(Resource.Id.entry_password); | ||||||
| 			TextView confpassword = (TextView) FindViewById(Resource.Id.entry_confpassword); | 			TextView confpassword = (TextView) FindViewById(Resource.Id.entry_confpassword); | ||||||
| @@ -942,7 +979,8 @@ namespace keepass2android | |||||||
|                     binariesGroup.Visibility = ViewStates.Visible; |                     binariesGroup.Visibility = ViewStates.Visible; | ||||||
|                     FindViewById(Resource.Id.entry_binaries_container).Visibility = ViewStates.Visible; |                     FindViewById(Resource.Id.entry_binaries_container).Visibility = ViewStates.Visible; | ||||||
|                     ((Button)FindViewById(Resource.Id.add_advanced)).Visibility = ViewStates.Visible; |                     ((Button)FindViewById(Resource.Id.add_advanced)).Visibility = ViewStates.Visible; | ||||||
|                     FindViewById(Resource.Id.entry_extras_container).Visibility = ViewStates.Visible; |                     ((Button)FindViewById(Resource.Id.configure_totp)).Visibility = ViewStates.Visible; | ||||||
|  | 					FindViewById(Resource.Id.entry_extras_container).Visibility = ViewStates.Visible; | ||||||
|  |  | ||||||
|                     return true; |                     return true; | ||||||
|                 case Android.Resource.Id.Home: |                 case Android.Resource.Id.Home: | ||||||
| @@ -1020,6 +1058,7 @@ namespace keepass2android | |||||||
| 			if (type == "bool") | 			if (type == "bool") | ||||||
| 			{ | 			{ | ||||||
| 				RelativeLayout ees = (RelativeLayout)LayoutInflater.Inflate(Resource.Layout.entry_edit_section_bool, null); | 				RelativeLayout ees = (RelativeLayout)LayoutInflater.Inflate(Resource.Layout.entry_edit_section_bool, null); | ||||||
|  |                 ees.Tag = pair.Key; | ||||||
| 				var keyView = ((TextView)ees.FindViewById(Resource.Id.extrakey)); | 				var keyView = ((TextView)ees.FindViewById(Resource.Id.extrakey)); | ||||||
| 				var checkbox = ((CheckBox)ees.FindViewById(Resource.Id.checkbox)); | 				var checkbox = ((CheckBox)ees.FindViewById(Resource.Id.checkbox)); | ||||||
| 			    var valueView = ((TextView)ees.FindViewById(Resource.Id.value)); | 			    var valueView = ((TextView)ees.FindViewById(Resource.Id.value)); | ||||||
| @@ -1037,6 +1076,7 @@ namespace keepass2android | |||||||
| 			else if (type == "file") | 			else if (type == "file") | ||||||
| 			{ | 			{ | ||||||
| 				RelativeLayout ees = (RelativeLayout)LayoutInflater.Inflate(Resource.Layout.entry_edit_section_file, null); | 				RelativeLayout ees = (RelativeLayout)LayoutInflater.Inflate(Resource.Layout.entry_edit_section_file, null); | ||||||
|  |                 ees.Tag = pair.Key; | ||||||
| 				var keyView = ((TextView)ees.FindViewById(Resource.Id.extrakey)); | 				var keyView = ((TextView)ees.FindViewById(Resource.Id.extrakey)); | ||||||
| 				var titleView = ((TextView)ees.FindViewById(Resource.Id.title)); | 				var titleView = ((TextView)ees.FindViewById(Resource.Id.title)); | ||||||
| 				keyView.Text = pair.Key; | 				keyView.Text = pair.Key; | ||||||
| @@ -1054,6 +1094,7 @@ namespace keepass2android | |||||||
| 			else | 			else | ||||||
| 			{ | 			{ | ||||||
| 				RelativeLayout ees = (RelativeLayout)LayoutInflater.Inflate(Resource.Layout.entry_edit_section, null); | 				RelativeLayout ees = (RelativeLayout)LayoutInflater.Inflate(Resource.Layout.entry_edit_section, null); | ||||||
|  |                 ees.Tag = pair.Key; | ||||||
| 				var keyView = ((TextView)ees.FindViewById(Resource.Id.extrakey)); | 				var keyView = ((TextView)ees.FindViewById(Resource.Id.extrakey)); | ||||||
| 				var titleView = ((TextView)ees.FindViewById(Resource.Id.title)); | 				var titleView = ((TextView)ees.FindViewById(Resource.Id.title)); | ||||||
| 				keyView.Text = pair.Key; | 				keyView.Text = pair.Key; | ||||||
| @@ -1090,8 +1131,184 @@ namespace keepass2android | |||||||
| 		    } | 		    } | ||||||
| 		     | 		     | ||||||
| 	    } | 	    } | ||||||
|  | 		 | ||||||
|  |         private void EditTotpString(View sender) | ||||||
|  |         { | ||||||
|  |             AlertDialog.Builder builder = new AlertDialog.Builder(this); | ||||||
|  |             View dlgView = LayoutInflater.Inflate(Resource.Layout. | ||||||
|  |                 configure_totp_dialog, null); | ||||||
|  |  | ||||||
| 	    private void EditAdvancedString(View sender) |  | ||||||
|  |  | ||||||
|  |             builder.SetView(dlgView); | ||||||
|  |             builder.SetNegativeButton(Android.Resource.String.Cancel, (o, args) => { }); | ||||||
|  |             builder.SetPositiveButton(Android.Resource.String.Ok, (o, args) => | ||||||
|  |             { | ||||||
|  |                  | ||||||
|  |                 var targetField = ((TextView)((View)sender.Parent).FindViewById(Resource.Id.value)); | ||||||
|  |                 if (targetField != null) | ||||||
|  |                 { | ||||||
|  |                     string entryTitle = Util.GetEditText(this, Resource.Id.entry_title); | ||||||
|  |                     string username = Util.GetEditText(this, Resource.Id.entry_user_name); | ||||||
|  |                     string secret = dlgView.FindViewById<TextView>(Resource.Id.totp_secret_key).Text; | ||||||
|  |                     string totpLength = dlgView.FindViewById<EditText>(Resource.Id.totp_length).Text; | ||||||
|  |                     string timeStep = dlgView.FindViewById<EditText>(Resource.Id.totp_time_step).Text; | ||||||
|  |                     var checkedTotpId = (int)dlgView.FindViewById<RadioGroup>(Resource.Id.totp_encoding).CheckedRadioButtonId; | ||||||
|  |                     TotpEncoding encoding = (checkedTotpId == Resource.Id.totp_encoding_steam) | ||||||
|  |                         ? TotpEncoding.Steam : (checkedTotpId == Resource.Id.totp_encoding_rfc6238 ? TotpEncoding.Default : TotpEncoding.Custom); | ||||||
|  |                     var algorithm = (int)dlgView.FindViewById<Spinner>(Resource.Id.totp_algorithm).SelectedItemPosition; | ||||||
|  |  | ||||||
|  |                     targetField.Text = BuildOtpString(entryTitle, username, secret, totpLength, timeStep, encoding, algorithm); | ||||||
|  | 				} | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  | 					Toast.MakeText(this, "did not find target field", ToastLength.Long).Show(); | ||||||
|  |                 } | ||||||
|  |                  | ||||||
|  | 				 | ||||||
|  | 				//not calling State.Entry.Strings.Set(...). We only do this when the user saves the changes. | ||||||
|  | 				State.EntryModified = true; | ||||||
|  |  | ||||||
|  | 			}); | ||||||
|  |             Dialog dialog = builder.Create(); | ||||||
|  |  | ||||||
|  |             dlgView.FindViewById<RadioButton>(Resource.Id.totp_encoding_custom).CheckedChange += (o, args) => | ||||||
|  |             { | ||||||
|  |                 dlgView.FindViewById(Resource.Id.totp_custom_settings_group).Visibility = args.IsChecked ? ViewStates.Visible : ViewStates.Gone; | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 			//copy values from entry into dialog | ||||||
|  | 			View ees = (View)sender.Parent; | ||||||
|  |             TotpData totpData = new Kp2aTotp().TryGetTotpData(new PwEntryOutput(State.Entry, App.Kp2a.CurrentDb)); | ||||||
|  |             if (totpData != null) | ||||||
|  |             { | ||||||
|  |                 dlgView.FindViewById<TextView>(Resource.Id.totp_secret_key).Text = totpData.TotpSeed; | ||||||
|  |                 if (totpData.Encoder == TotpData.EncoderSteam) | ||||||
|  |                 { | ||||||
|  |                     dlgView.FindViewById<RadioButton>(Resource.Id.totp_encoding_steam).Checked = true; | ||||||
|  |                 }  | ||||||
|  |                 else if ((totpData.Encoder == TotpData.EncoderRfc6238) && (totpData.IsDefaultRfc6238)) | ||||||
|  | 				{ | ||||||
|  |                     dlgView.FindViewById<RadioButton>(Resource.Id.totp_encoding_rfc6238).Checked = true; | ||||||
|  | 				} | ||||||
|  | 				else | ||||||
|  |                 { | ||||||
|  |                     dlgView.FindViewById<RadioButton>(Resource.Id.totp_encoding_custom).Checked = true; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  |                 dlgView.FindViewById<EditText>(Resource.Id.totp_length).Text = totpData.Length; | ||||||
|  |                 dlgView.FindViewById<EditText>(Resource.Id.totp_time_step).Text = totpData.Duration; | ||||||
|  |                 dlgView.FindViewById <Spinner>(Resource.Id.totp_algorithm).SetSelection(totpData.HashAlgorithm == TotpData.HashSha1 ? 0 : ( | ||||||
|  |                         totpData.HashAlgorithm == TotpData.HashSha256 ? 1: | ||||||
|  |                             (totpData.HashAlgorithm == TotpData.HashSha256 ? 2 : 0))); | ||||||
|  |  | ||||||
|  |                 dlgView.FindViewById(Resource.Id.totp_custom_settings_group).Visibility = dlgView.FindViewById<RadioButton>(Resource.Id.totp_encoding_custom).Checked ? ViewStates.Visible : ViewStates.Gone; | ||||||
|  | 			} | ||||||
|  |              | ||||||
|  |             _passwordFont.ApplyTo(dlgView.FindViewById<EditText>(Resource.Id.totp_secret_key)); | ||||||
|  |             Util.SetNoPersonalizedLearning(dlgView); | ||||||
|  |              | ||||||
|  |  | ||||||
|  |  | ||||||
|  |             dialog.Show(); | ||||||
|  |  | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         string SanitizeInput(string encodedData) | ||||||
|  |         { | ||||||
|  |             if (encodedData.Length <= 0) | ||||||
|  |             { | ||||||
|  |                 return encodedData; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  | 			StringBuilder newEncodedDataBuilder = new StringBuilder(encodedData); | ||||||
|  |             int i = 0; | ||||||
|  |             foreach (var ch in encodedData) | ||||||
|  |             { | ||||||
|  |                 switch (ch) | ||||||
|  |                 { | ||||||
|  |                     case '0': | ||||||
|  |                         newEncodedDataBuilder[i++] = 'O'; | ||||||
|  |                         break; | ||||||
|  |                     case '1': | ||||||
|  |                         newEncodedDataBuilder[i++] = 'L'; | ||||||
|  |                         break; | ||||||
|  |                     case '8': | ||||||
|  |                         newEncodedDataBuilder[i++] = 'B'; | ||||||
|  |                         break; | ||||||
|  |                     default: | ||||||
|  |                         if (('A' <= ch && ch <= 'Z') || ('a' <= ch && ch <= 'z') || ('2' <= ch && ch <= '7')) | ||||||
|  |                         { | ||||||
|  |                             newEncodedDataBuilder[i++] = ch; | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             string newEncodedData = newEncodedDataBuilder.ToString().Substring(0, i); | ||||||
|  |  | ||||||
|  |             return AddPadding(newEncodedData); | ||||||
|  |          | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         string AddPadding(string encodedData) | ||||||
|  |         { | ||||||
|  |             if (encodedData.Length <= 0 || encodedData.Length % 8 == 0) { | ||||||
|  |                 return encodedData; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             int rBytes = encodedData.Length % 8; | ||||||
|  |             // rBytes must be a member of {2, 4, 5, 7} | ||||||
|  |             if (1 == rBytes || 3 == rBytes || 6 == rBytes) { | ||||||
|  |                 return encodedData; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             string newEncodedData = encodedData; | ||||||
|  |             for (int nPads = 8 - rBytes; nPads > 0; --nPads) | ||||||
|  |             { | ||||||
|  |                 newEncodedData += "="; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return newEncodedData; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         enum TotpEncoding | ||||||
|  |         { | ||||||
|  | 			Default, Steam, Custom | ||||||
|  |         } | ||||||
|  |  | ||||||
|  | 		private string BuildOtpString(string entryTitle, string userName, string secret, string totpLength, string timeStep, TotpEncoding encoding, int algorithm) | ||||||
|  |         { | ||||||
|  |             string entryEncoded = string.IsNullOrWhiteSpace(entryTitle) | ||||||
|  |                 ? "Keepass2Android" | ||||||
|  |                 : System.Uri.EscapeUriString(entryTitle); | ||||||
|  | 			return $"otpauth://totp/{entryEncoded}:{System.Uri.EscapeUriString(userName)}?" + | ||||||
|  |                    $"secret={SanitizeInput(secret)}" + | ||||||
|  | 				   $"&issuer={ entryEncoded}" | ||||||
|  | 					   + (encoding != TotpEncoding.Custom? "" : $"&period={timeStep}&digits={totpLength}&algorithm={AlgorithmIndexToString(algorithm)}") + | ||||||
|  |                    (encoding  == TotpEncoding.Steam ? "&encoder=steam" : ""); | ||||||
|  |  | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  |         private string AlgorithmIndexToString(in int algorithm) | ||||||
|  |         { | ||||||
|  |             switch (algorithm) | ||||||
|  |             { | ||||||
|  | 				case 0: | ||||||
|  |                     return "SHA1"; | ||||||
|  | 				case 1: | ||||||
|  |                     return "SHA256"; | ||||||
|  |                 case 2: | ||||||
|  |                     return "SHA512"; | ||||||
|  | 				default: | ||||||
|  |                     return ""; | ||||||
|  | 			} | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void EditAdvancedString(View sender) | ||||||
| 		{ | 		{ | ||||||
| 			AlertDialog.Builder builder = new AlertDialog.Builder(this); | 			AlertDialog.Builder builder = new AlertDialog.Builder(this); | ||||||
| 			View dlgView = LayoutInflater.Inflate(Resource.Layout. | 			View dlgView = LayoutInflater.Inflate(Resource.Layout. | ||||||
|   | |||||||
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 539 B | 
| @@ -0,0 +1,13 @@ | |||||||
|  | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     android:width="24dp" | ||||||
|  |     android:height="24dp" | ||||||
|  |     android:viewportWidth="24" | ||||||
|  |     android:viewportHeight="24" | ||||||
|  |     android:tint="?attr/colorControlNormal"> | ||||||
|  |   <path | ||||||
|  |       android:fillColor="@android:color/white" | ||||||
|  |       android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/> | ||||||
|  |   <path | ||||||
|  |       android:fillColor="@android:color/white" | ||||||
|  |       android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"/> | ||||||
|  | </vector> | ||||||
| @@ -0,0 +1,69 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <LinearLayout | ||||||
|  |     xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     android:orientation="vertical" | ||||||
|  |     android:layout_width="fill_parent" | ||||||
|  |     android:layout_height="wrap_content"> | ||||||
|  |  | ||||||
|  |     <EditText | ||||||
|  |         android:id="@+id/totp_secret_key" | ||||||
|  |         android:singleLine="true" | ||||||
|  |         android:inputType="text" | ||||||
|  |         android:hint="@string/totp_secret_key" | ||||||
|  |         android:dropDownWidth="match_parent" | ||||||
|  |         android:layout_width="fill_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         /> | ||||||
|  |  | ||||||
|  |     <RadioGroup | ||||||
|  |         android:layout_width="fill_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:id="@+id/totp_encoding"> | ||||||
|  |         <RadioButton | ||||||
|  |             android:layout_width="fill_parent" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:checked="true" | ||||||
|  |             android:text="@string/totp_encoding_rfc6238" | ||||||
|  |             android:id="@+id/totp_encoding_rfc6238" /> | ||||||
|  |         <RadioButton | ||||||
|  |             android:layout_width="fill_parent" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:text="@string/totp_encoding_steam" | ||||||
|  |             android:id="@+id/totp_encoding_steam" /> | ||||||
|  |         <RadioButton | ||||||
|  |             android:layout_width="fill_parent" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:text="@string/totp_encoding_custom" | ||||||
|  |             android:id="@+id/totp_encoding_custom" /> | ||||||
|  |     </RadioGroup> | ||||||
|  |     <LinearLayout | ||||||
|  |         android:orientation="vertical" | ||||||
|  |         android:minWidth="25px" | ||||||
|  |         android:minHeight="25px" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:id="@+id/totp_custom_settings_group"> | ||||||
|  |         <Spinner | ||||||
|  |             android:layout_width="match_parent" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:entries="@array/totp_algorithms" | ||||||
|  |             android:prompt="@string/algorithm" | ||||||
|  |             android:id="@+id/totp_algorithm" /> | ||||||
|  |         <EditText | ||||||
|  |             android:inputType="numberDecimal" | ||||||
|  |             android:layout_width="match_parent" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:hint="@string/totp_time_step" | ||||||
|  |             android:id="@+id/totp_time_step" /> | ||||||
|  |  | ||||||
|  |         <EditText | ||||||
|  |             android:inputType="numberDecimal" | ||||||
|  |             android:layout_width="match_parent" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:hint="@string/totp_length" | ||||||
|  |             android:id="@+id/totp_length" /> | ||||||
|  |  | ||||||
|  |     </LinearLayout> | ||||||
|  |  | ||||||
|  |  | ||||||
|  | </LinearLayout> | ||||||
| @@ -177,6 +177,19 @@ | |||||||
|     android:layout_weight="1" |     android:layout_weight="1" | ||||||
|     android:layout_height="wrap_content" |     android:layout_height="wrap_content" | ||||||
|     android:text="@string/add_extra_string"/> |     android:text="@string/add_extra_string"/> | ||||||
|  |  | ||||||
|  |             <Button | ||||||
|  |               android:id="@+id/configure_totp" | ||||||
|  |               android:background="?android:selectableItemBackground" | ||||||
|  |               android:textColor="?android:attr/textColorPrimary" | ||||||
|  |               android:textAppearance="?android:attr/textAppearanceSmall" | ||||||
|  |               android:textAllCaps="true" | ||||||
|  |               android:paddingLeft="8dp" | ||||||
|  |               android:layout_width="match_parent" | ||||||
|  |               android:layout_weight="1" | ||||||
|  |               android:layout_height="wrap_content" | ||||||
|  |               android:drawableLeft="@drawable/baseline_schedule_white_24" | ||||||
|  |               android:text="@string/configure_totp"/> | ||||||
|                  |                  | ||||||
|             </LinearLayout> |             </LinearLayout> | ||||||
|         </LinearLayout> |         </LinearLayout> | ||||||
|   | |||||||
| @@ -177,6 +177,12 @@ | |||||||
|     	<item>28</item> |     	<item>28</item> | ||||||
|     </string-array> |     </string-array> | ||||||
|  |  | ||||||
|  |   <string-array name="totp_algorithms"> | ||||||
|  |     <item>SHA-1</item> | ||||||
|  |     <item>SHA-256</item> | ||||||
|  |     <item>SHA-512</item> | ||||||
|  |   </string-array> | ||||||
|  |  | ||||||
| 	<string name="design_default">System</string> | 	<string name="design_default">System</string> | ||||||
| 	<string-array name="design_values"> | 	<string-array name="design_values"> | ||||||
| 		<item>Light</item> | 		<item>Light</item> | ||||||
|   | |||||||
| @@ -346,7 +346,15 @@ | |||||||
|   <string name="protection">Protected field</string> |   <string name="protection">Protected field</string> | ||||||
|   <string name="add_binary">Add file attachment</string> |   <string name="add_binary">Add file attachment</string> | ||||||
|   <string name="add_extra_string">Add additional string</string> |   <string name="add_extra_string">Add additional string</string> | ||||||
| 	<string name="delete_extra_string">Delete additional string</string> |   <string name="configure_totp">Configure TOTP</string> | ||||||
|  |   <string name="totp_secret_key">Secret key</string> | ||||||
|  |   <string name="totp_encoding_rfc6238">Default RFC6238 token settings</string> | ||||||
|  |   <string name="totp_encoding_steam">Steam token settings</string> | ||||||
|  |   <string name="totp_encoding_custom">Custom token settings</string> | ||||||
|  |   <string name="totp_time_step">Time step</string> | ||||||
|  |   <string name="totp_length">Code length</string> | ||||||
|  |    | ||||||
|  |   <string name="delete_extra_string">Delete additional string</string> | ||||||
| 	<string name="database_loaded_quickunlock_enabled">%1$s: Locked. QuickUnlock enabled.</string> | 	<string name="database_loaded_quickunlock_enabled">%1$s: Locked. QuickUnlock enabled.</string> | ||||||
| 	<string name="database_loaded_unlocked">%1$s: Unlocked.</string> | 	<string name="database_loaded_unlocked">%1$s: Unlocked.</string> | ||||||
|   <string name="credentials_dialog_title">Enter server credentials</string> |   <string name="credentials_dialog_title">Enter server credentials</string> | ||||||
|   | |||||||
| @@ -33,9 +33,9 @@ namespace keepass2android | |||||||
|                 res.Encoder = parsedQuery.Get("encoder"); |                 res.Encoder = parsedQuery.Get("encoder"); | ||||||
|                 string algo = parsedQuery.Get("algorithm"); |                 string algo = parsedQuery.Get("algorithm"); | ||||||
|                 if (algo == "SHA512") |                 if (algo == "SHA512") | ||||||
|                     res.HashAlgorithm = "HMAC-SHA-512"; |                     res.HashAlgorithm = TotpData.HashSha512; | ||||||
|                 if (algo == "SHA256") |                 if (algo == "SHA256") | ||||||
|                     res.HashAlgorithm = "HMAC-SHA-256"; |                     res.HashAlgorithm = TotpData.HashSha256; | ||||||
|  |  | ||||||
|  |  | ||||||
|                 //set defaults according to https://github.com/google/google-authenticator/wiki/Key-Uri-Format |                 //set defaults according to https://github.com/google/google-authenticator/wiki/Key-Uri-Format | ||||||
|   | |||||||
| @@ -42,7 +42,7 @@ namespace PluginTOTP | |||||||
|             string strAlg; |             string strAlg; | ||||||
|             entryFields.TryGetValue("TimeOtp-Algorithm", out strAlg); |             entryFields.TryGetValue("TimeOtp-Algorithm", out strAlg); | ||||||
|  |  | ||||||
|  |             res.HashAlgorithm = strAlg; | ||||||
|             res.TotpSecret = pbSecret; |             res.TotpSecret = pbSecret; | ||||||
|             res.Length = uLength.ToString(); |             res.Length = uLength.ToString(); | ||||||
|             res.Duration = uPeriod.ToString(); |             res.Duration = uPeriod.ToString(); | ||||||
|   | |||||||
| @@ -19,6 +19,23 @@ namespace keepass2android | |||||||
|             new Keepass2TotpPluginAdapter(), |             new Keepass2TotpPluginAdapter(), | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         public TotpData TryGetTotpData(PwEntryOutput entry) | ||||||
|  |         { | ||||||
|  |             if (entry == null) | ||||||
|  |                 return null; | ||||||
|  |             foreach (ITotpPluginAdapter adapter in _pluginAdapters) | ||||||
|  |             { | ||||||
|  |                 TotpData totpData = adapter.GetTotpData(entry.OutputStrings.ToDictionary(pair => StrUtil.SafeXmlString(pair.Key), pair => pair.Value.ReadString()), Application.Context, false); | ||||||
|  |                 if (totpData.IsTotpEntry) | ||||||
|  |                 { | ||||||
|  |                     return totpData; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         public ITotpPluginAdapter TryGetAdapter(PwEntryOutput entry) |         public ITotpPluginAdapter TryGetAdapter(PwEntryOutput entry) | ||||||
|         { |         { | ||||||
|             if (entry == null) |             if (entry == null) | ||||||
|   | |||||||
| @@ -127,7 +127,7 @@ namespace KeeTrayTOTP.Libraries | |||||||
|                 this.encoder = TOTPEncoder.rfc6238; |                 this.encoder = TOTPEncoder.rfc6238; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if(data.Url != null) |             if(data.TimeCorrectionUrl != null) | ||||||
|             { |             { | ||||||
|                 { |                 { | ||||||
|                     this.TimeCorrection = TimeSpan.Zero; |                     this.TimeCorrection = TimeSpan.Zero; | ||||||
|   | |||||||
| @@ -7,11 +7,14 @@ namespace PluginTOTP | |||||||
|     { |     { | ||||||
|         public TotpData() |         public TotpData() | ||||||
|         { |         { | ||||||
|             HashAlgorithm = "HMAC-SHA-1"; |             HashAlgorithm = HashSha1; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public const string EncoderSteam = "steam"; |         public const string EncoderSteam = "steam"; | ||||||
|         public const string EncoderRfc6238 = "rfc6238"; |         public const string EncoderRfc6238 = "rfc6238"; | ||||||
|  |         public const string HashSha1 = "HMAC-SHA-1"; | ||||||
|  |         public const string HashSha256 = "HMAC-SHA-256"; | ||||||
|  |         public const string HashSha512 = "HMAC-SHA-512"; | ||||||
|  |  | ||||||
|  |  | ||||||
|         public bool IsTotpEntry { get; set; } |         public bool IsTotpEntry { get; set; } | ||||||
| @@ -20,12 +23,28 @@ namespace PluginTOTP | |||||||
| 		public string TotpSeed | 		public string TotpSeed | ||||||
|         { |         { | ||||||
|             set { TotpSecret = Base32.Decode(value.Trim()); } |             set { TotpSecret = Base32.Decode(value.Trim()); } | ||||||
|  |             get { return Base32.Encode(TotpSecret); } | ||||||
|         } |         } | ||||||
| 		public string Duration { get; set; } | 		public string Duration { get; set; } | ||||||
|         public string Encoder { get; set; } |         public string Encoder { get; set; } | ||||||
|         public string Length { get; set; } |         public string Length { get; set; } | ||||||
| 		public string Url { get; set; } | 		public string TimeCorrectionUrl { get; set; } | ||||||
|  |  | ||||||
|         public string HashAlgorithm { get; set; } |         public string HashAlgorithm { get; set; } | ||||||
|  |  | ||||||
|  |         public bool IsDefaultRfc6238 | ||||||
|  |         { | ||||||
|  |             get { return Length == "6" && Duration == "30" && (HashAlgorithm == null || HashAlgorithm == HashSha1); } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public static TotpData MakeDefaultRfc6238() | ||||||
|  |         { | ||||||
|  |             return new TotpData() | ||||||
|  |             { | ||||||
|  |                 Duration = "30", | ||||||
|  |                 Length = "6", | ||||||
|  |                 HashAlgorithm = HashSha1 | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -122,7 +122,7 @@ namespace PluginTOTP | |||||||
| 						if (ValidUrl) | 						if (ValidUrl) | ||||||
| 						{ | 						{ | ||||||
| 							NoTimeCorrection = true; | 							NoTimeCorrection = true; | ||||||
| 							res.Url = Settings[2]; | 							res.TimeCorrectionUrl = Settings[2]; | ||||||
| 							/*var CurrentTimeCorrection = TimeCorrections[Settings[2]]; | 							/*var CurrentTimeCorrection = TimeCorrections[Settings[2]]; | ||||||
| 							if (CurrentTimeCorrection != null) | 							if (CurrentTimeCorrection != null) | ||||||
| 							{ | 							{ | ||||||
|   | |||||||
| @@ -314,9 +314,6 @@ | |||||||
|     <None Include="Resources\drawable-xhdpi\2_action_about.png"> |     <None Include="Resources\drawable-xhdpi\2_action_about.png"> | ||||||
|       <Visible>False</Visible> |       <Visible>False</Visible> | ||||||
|     </None> |     </None> | ||||||
|     <AndroidResource Include="Resources\layout\edit_extra_string_dialog.xml"> |  | ||||||
|       <SubType>AndroidResource</SubType> |  | ||||||
|     </AndroidResource> |  | ||||||
|     <AndroidResource Include="Resources\layout\file_storage_setup.xml"> |     <AndroidResource Include="Resources\layout\file_storage_setup.xml"> | ||||||
|       <SubType>AndroidResource</SubType> |       <SubType>AndroidResource</SubType> | ||||||
|     </AndroidResource> |     </AndroidResource> | ||||||
| @@ -2070,6 +2067,21 @@ | |||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <AndroidResource Include="Resources\menu\menu_selectdb.xml" /> |     <AndroidResource Include="Resources\menu\menu_selectdb.xml" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|  |   <ItemGroup> | ||||||
|  |     <AndroidResource Include="Resources\layout\configure_totp_dialog.xml"> | ||||||
|  |       <SubType>AndroidResource</SubType> | ||||||
|  |       <Generator>MSBuild:UpdateGeneratedFiles</Generator> | ||||||
|  |     </AndroidResource> | ||||||
|  |   </ItemGroup> | ||||||
|  |   <ItemGroup> | ||||||
|  |     <AndroidResource Include="Resources\layout\edit_extra_string_dialog.xml" /> | ||||||
|  |   </ItemGroup> | ||||||
|  |   <ItemGroup> | ||||||
|  |     <AndroidResource Include="Resources\drawable\baseline_schedule_24.xml" /> | ||||||
|  |   </ItemGroup> | ||||||
|  |   <ItemGroup> | ||||||
|  |     <AndroidResource Include="Resources\drawable-xhdpi\baseline_schedule_white_24.png" /> | ||||||
|  |   </ItemGroup> | ||||||
|   <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" /> |   <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" /> | ||||||
|   <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> |   <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> | ||||||
|     <PropertyGroup> |     <PropertyGroup> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Philipp Crocoll
					Philipp Crocoll