implemented loading of files with KeepassXC Challenge (#4).
requires write support, handling of Challenge/Response errors (or user cancels). Caution: saving corrupts the file at the moment!
This commit is contained in:
		| @@ -55,7 +55,10 @@ namespace KeePassLib.Cryptography.KeyDerivation | ||||
| 			get { return "AES-KDF"; } | ||||
| 		} | ||||
|  | ||||
| 		public AesKdf() | ||||
|         public override byte[] GetSeed(KdfParameters p) | ||||
|         { return p.GetByteArray(ParamSeed); } | ||||
|  | ||||
|         public AesKdf() | ||||
| 		{ | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -68,7 +68,10 @@ namespace KeePassLib.Cryptography.KeyDerivation | ||||
| 			get { return "Argon2"; } | ||||
| 		} | ||||
|  | ||||
| 		public Argon2Kdf() | ||||
|         public override byte[] GetSeed(KdfParameters p) | ||||
|         { return p.GetByteArray(ParamSalt); } | ||||
|  | ||||
|         public Argon2Kdf() | ||||
| 		{ | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -36,7 +36,9 @@ namespace KeePassLib.Cryptography.KeyDerivation | ||||
| 			get; | ||||
| 		} | ||||
|  | ||||
| 		public virtual KdfParameters GetDefaultParameters() | ||||
|         public abstract byte[] GetSeed(KdfParameters p); | ||||
|  | ||||
|         public virtual KdfParameters GetDefaultParameters() | ||||
| 		{ | ||||
| 			return new KdfParameters(this.Uuid); | ||||
| 		} | ||||
|   | ||||
| @@ -20,11 +20,8 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Diagnostics; | ||||
| using System.Text; | ||||
|  | ||||
| using KeePassLib.Cryptography; | ||||
| using KeePassLib.Cryptography.KeyDerivation; | ||||
| using KeePassLib.Native; | ||||
| using KeePassLib.Resources; | ||||
| using KeePassLib.Security; | ||||
| using KeePassLib.Utility; | ||||
| @@ -168,7 +165,7 @@ namespace KeePassLib.Keys | ||||
| 		/// Creates the composite key from the supplied user key sources (password, | ||||
| 		/// key file, user account, computer ID, etc.). | ||||
| 		/// </summary> | ||||
| 		private byte[] CreateRawCompositeKey32(byte[] mPbMasterSeed) | ||||
| 		private byte[] CreateRawCompositeKey32(byte[] mPbMasterSeed, byte[] mPbKdfSeed) | ||||
| 		{ | ||||
| 			ValidateUserKeys(); | ||||
|  | ||||
| @@ -178,7 +175,7 @@ namespace KeePassLib.Keys | ||||
| 			foreach(IUserKey pKey in m_vUserKeys) | ||||
| 			{ | ||||
| 				if (pKey is ISeedBasedUserKey) | ||||
| 					((ISeedBasedUserKey)pKey).SetParams(mPbMasterSeed); | ||||
| 					((ISeedBasedUserKey)pKey).SetParams(mPbMasterSeed, mPbKdfSeed); | ||||
| 				ProtectedBinary b = pKey.KeyData; | ||||
| 				if(b != null) | ||||
| 				{ | ||||
| @@ -211,15 +208,17 @@ namespace KeePassLib.Keys | ||||
| 		{ | ||||
| 			if(p == null) { Debug.Assert(false); throw new ArgumentNullException("p"); } | ||||
|  | ||||
| 			byte[] pbRaw32 = CreateRawCompositeKey32(mPbMasterSeed); | ||||
|  | ||||
| 		    KdfEngine kdf = KdfPool.Get(p.KdfUuid); | ||||
| 		    if (kdf == null) // CryptographicExceptions are translated to "file corrupted" | ||||
| 		        throw new Exception(KLRes.UnknownKdf + MessageService.NewParagraph + | ||||
| 		                            KLRes.FileNewVerOrPlgReq + MessageService.NewParagraph + | ||||
| 		                            "UUID: " + p.KdfUuid.ToHexString() + "."); | ||||
|  | ||||
|             byte[] pbRaw32 = CreateRawCompositeKey32(mPbMasterSeed, kdf.GetSeed(p)); | ||||
| 			if((pbRaw32 == null) || (pbRaw32.Length != 32)) | ||||
| 				{ Debug.Assert(false); return null; } | ||||
|  | ||||
| 			KdfEngine kdf = KdfPool.Get(p.KdfUuid); | ||||
| 			if(kdf == null) // CryptographicExceptions are translated to "file corrupted" | ||||
| 				throw new Exception(KLRes.UnknownKdf + MessageService.NewParagraph + | ||||
| 					KLRes.FileNewVerOrPlgReq + MessageService.NewParagraph + | ||||
| 					"UUID: " + p.KdfUuid.ToHexString() + "."); | ||||
|  | ||||
| 			byte[] pbTrf32 = kdf.Transform(pbRaw32, p); | ||||
| 			if(pbTrf32 == null) { Debug.Assert(false); return null; } | ||||
| @@ -256,7 +255,7 @@ namespace KeePassLib.Keys | ||||
|  | ||||
| 	public interface ISeedBasedUserKey | ||||
| 	{ | ||||
| 		void SetParams(byte[] masterSeed); | ||||
| 		void SetParams(byte[] masterSeed, byte[] mPbKdfSeed); | ||||
| 	} | ||||
|  | ||||
| 	public sealed class InvalidCompositeKeyException : Exception | ||||
|   | ||||
							
								
								
									
										84
									
								
								src/keepass2android/ChallengeXCKey.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/keepass2android/ChallengeXCKey.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| using Java.Lang; | ||||
| using KeePassLib.Cryptography; | ||||
| using KeePassLib.Keys; | ||||
| using KeePassLib.Security; | ||||
| using Exception = System.Exception; | ||||
|  | ||||
| namespace keepass2android | ||||
| { | ||||
|     class ChallengeXCKey : IUserKey, ISeedBasedUserKey | ||||
|     { | ||||
|         private readonly int _requestCode; | ||||
|  | ||||
|         public ProtectedBinary KeyData | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 if (Activity == null) | ||||
|                     throw new Exception("Need an active Keepass2Android activity to challenge Yubikey!"); | ||||
|                 Activity.RunOnUiThread( | ||||
|                     () => | ||||
|                     { | ||||
|                         byte[] challenge = _kdfSeed; | ||||
|                         byte[] challenge64 = new byte[64]; | ||||
|                         for (int i = 0; i < 64; i++) | ||||
|                         { | ||||
|                             if (i < challenge.Length) | ||||
|                             { | ||||
|                                 challenge64[i] = challenge[i]; | ||||
|                             } | ||||
|                             else | ||||
|                             { | ||||
|                                 challenge64[i] = (byte)(challenge64.Length - challenge.Length); | ||||
|                             } | ||||
|  | ||||
|                         } | ||||
|                         var chalIntent = Activity.TryGetYubichallengeIntentOrPrompt(challenge64, true); | ||||
|  | ||||
|                         if (chalIntent == null) | ||||
|                             throw new Exception("YubiChallenge not installed."); | ||||
|  | ||||
|                         Activity.StartActivityForResult(chalIntent, _requestCode); | ||||
| 		                 | ||||
| 		                 | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|                     }); | ||||
|                 while ((Response == null) && (Error == null)) | ||||
|                 { | ||||
|                     Thread.Sleep(50); | ||||
|                 } | ||||
|                 if (Error != null) | ||||
|                     throw new Exception("YubiChallenge failed: " + Error); | ||||
|  | ||||
|                 return new ProtectedBinary(true, CryptoUtil.HashSha256(Response)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private byte[] _kdfSeed; | ||||
|  | ||||
|         public ChallengeXCKey(LockingActivity activity, int requestCode) | ||||
|         { | ||||
|             this.Activity = activity; | ||||
|             _requestCode = requestCode; | ||||
|             Response = null; | ||||
|         } | ||||
|  | ||||
|         public void SetParams(byte[] masterSeed, byte[] mPbKdfSeed) | ||||
|         { | ||||
|             _kdfSeed = mPbKdfSeed; | ||||
|         } | ||||
|  | ||||
|         public byte[] Response { get; set; } | ||||
|  | ||||
|         public string Error { get; set; } | ||||
|  | ||||
|         public LockingActivity Activity | ||||
|         { | ||||
|             get; | ||||
|             set; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -16,6 +16,10 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file | ||||
|   */ | ||||
|  | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using Android.App; | ||||
| using Android.Content; | ||||
| using Android.Content.PM; | ||||
| using Android.Runtime; | ||||
|  | ||||
| namespace keepass2android | ||||
| @@ -35,7 +39,38 @@ namespace keepass2android | ||||
| 		{ | ||||
| 		} | ||||
|  | ||||
| 		protected override void OnPause() { | ||||
| 	    protected override void OnStart() | ||||
| 	    { | ||||
| 	        base.OnStart(); | ||||
|  | ||||
| 	        if (App.Kp2a.GetDb().Loaded) | ||||
| 	        { | ||||
| 	            var xcKey = App.Kp2a.GetDb().KpDatabase.MasterKey.GetUserKey<ChallengeXCKey>(); | ||||
| 	            if (xcKey != null) | ||||
| 	            { | ||||
| 	                xcKey.Activity = this; | ||||
| 	            } | ||||
|  | ||||
| 	        } | ||||
|  | ||||
|         } | ||||
|  | ||||
| 	    protected override void OnStop() | ||||
| 	    { | ||||
| 	        base.OnStop(); | ||||
| 	        if (App.Kp2a.GetDb().Loaded) | ||||
| 	        { | ||||
| 	            var xcKey = App.Kp2a.GetDb().KpDatabase.MasterKey.GetUserKey<ChallengeXCKey>(); | ||||
| 	            if (xcKey != null) | ||||
| 	            { | ||||
|                     //don't store a pointer to this activity in the static database object to avoid memory leak | ||||
| 	                xcKey.Activity = null; | ||||
| 	            } | ||||
|  | ||||
| 	        } | ||||
|         } | ||||
|  | ||||
| 	    protected override void OnPause() { | ||||
| 			base.OnPause(); | ||||
| 			 | ||||
| 			TimeoutHelper.Pause(this); | ||||
| @@ -52,6 +87,30 @@ namespace keepass2android | ||||
| 			 | ||||
| 			TimeoutHelper.Resume(this); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|  | ||||
| 	    public Intent TryGetYubichallengeIntentOrPrompt(byte[] challenge, bool promptToInstall) | ||||
| 	    { | ||||
| 	        Intent chalIntent = new Intent("com.yubichallenge.NFCActivity.CHALLENGE"); | ||||
| 	        chalIntent.PutExtra("challenge", challenge); | ||||
| 	        chalIntent.PutExtra("slot", 2); | ||||
| 	        IList<ResolveInfo> activities = PackageManager.QueryIntentActivities(chalIntent, 0); | ||||
| 	        bool isIntentSafe = activities.Count > 0; | ||||
| 	        if (isIntentSafe) | ||||
| 	        { | ||||
| 	            return chalIntent; | ||||
| 	        } | ||||
| 	        if (promptToInstall) | ||||
| 	        { | ||||
| 	            AlertDialog.Builder b = new AlertDialog.Builder(this); | ||||
| 	            b.SetMessage(Resource.String.YubiChallengeNotInstalled); | ||||
| 	            b.SetPositiveButton(Android.Resource.String.Ok, | ||||
| 	                delegate { Util.GotoUrl(this, GetString(Resource.String.MarketURL) + "com.yubichallenge"); }); | ||||
| 	            b.SetNegativeButton(Resource.String.cancel, delegate { }); | ||||
| 	            b.Create().Show(); | ||||
| 	        } | ||||
| 	        return null; | ||||
| 	    } | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -19,6 +19,7 @@ using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Security.Cryptography; | ||||
| using System.Threading.Tasks; | ||||
| using System.Xml; | ||||
| using System.Xml.Serialization; | ||||
| @@ -60,7 +61,6 @@ using Process = Android.OS.Process; | ||||
|  | ||||
| using KeeChallenge; | ||||
| using KeePassLib.Cryptography.KeyDerivation; | ||||
| using KeePassLib.Security; | ||||
| using AlertDialog = Android.App.AlertDialog; | ||||
| using Enum = System.Enum; | ||||
| using Exception = System.Exception; | ||||
| @@ -69,72 +69,7 @@ using Toolbar = Android.Support.V7.Widget.Toolbar; | ||||
|  | ||||
| namespace keepass2android | ||||
| { | ||||
| 	class ChallengeXCKey : IUserKey, ISeedBasedUserKey | ||||
| 	{ | ||||
| 		private readonly Activity _activity; | ||||
| 		private readonly int _requestCode; | ||||
|  | ||||
| 		public ProtectedBinary KeyData | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				 | ||||
| 				_activity.RunOnUiThread( | ||||
| 					() => | ||||
| 					{ | ||||
| 						//TODO refactor to use code from PasswordActivity including notice to install Yubichallenge | ||||
| 						Intent chalIntent = new Intent("com.yubichallenge.NFCActivity.CHALLENGE"); | ||||
| 						byte[] challenge = _masterSeed; | ||||
| 						byte[] challenge64 = new byte[64]; | ||||
| 						for (int i = 0; i < 64; i++) | ||||
| 						{ | ||||
| 							if (i < challenge.Length) | ||||
| 							{ | ||||
| 								challenge64[i] = challenge[i]; | ||||
| 							} | ||||
| 							else | ||||
| 							{ | ||||
| 								challenge64[i] = (byte) (challenge64.Length - challenge.Length); | ||||
| 							} | ||||
| 							 | ||||
| 						} | ||||
|  | ||||
| 						Kp2aLog.Log(MemUtil.ByteArrayToHexString(challenge64)); | ||||
|  | ||||
| 						chalIntent.PutExtra("challenge", challenge64); | ||||
| 						chalIntent.PutExtra("slot", 2); | ||||
| 						IList<ResolveInfo> activities = _activity.PackageManager.QueryIntentActivities(chalIntent, 0); | ||||
| 						bool isIntentSafe = activities.Count > 0; | ||||
| 						if (isIntentSafe) | ||||
| 						{ | ||||
| 							_activity.StartActivityForResult(chalIntent, _requestCode); | ||||
| 						} | ||||
| 						else throw new Exception("TODO implement: you need YubiChallenge"); | ||||
| 					}); | ||||
| 				while (Response == null) | ||||
| 					Thread.Sleep(100); | ||||
|  | ||||
| 				return new ProtectedBinary(true, Response); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		private byte[] _masterSeed; | ||||
|  | ||||
| 		public ChallengeXCKey(Activity activity, int requestCode) | ||||
| 		{ | ||||
| 			this._activity = activity; | ||||
| 			_requestCode = requestCode; | ||||
| 			Response = null; | ||||
| 		} | ||||
|  | ||||
| 		public void SetParams(byte[] masterSeed) | ||||
| 		{ | ||||
| 			_masterSeed = masterSeed; | ||||
| 		} | ||||
|  | ||||
| 		public byte[] Response { get; set; } | ||||
| 	} | ||||
| 	[Activity(Label = "@string/app_name", | ||||
|     [Activity(Label = "@string/app_name", | ||||
| 		ConfigurationChanges = ConfigChanges.Orientation, | ||||
| 		LaunchMode = LaunchMode.SingleInstance, | ||||
| 		WindowSoftInputMode = SoftInput.AdjustResize, | ||||
| @@ -434,69 +369,91 @@ namespace keepass2android | ||||
| 				 | ||||
| 				GetAuxFileLoader().LoadAuxFile(false); | ||||
| 			} | ||||
|             if (requestCode == RequestCodeChallengeYubikey && resultCode == Result.Ok)  | ||||
| 			{ | ||||
| 				try | ||||
| 				{ | ||||
| 					byte[] challengeResponse = data.GetByteArrayExtra("response"); | ||||
| 					if (_currentlyWaitingKey != null) | ||||
| 					{ | ||||
| 						_currentlyWaitingKey.Response = challengeResponse; | ||||
| 						return; | ||||
| 					} | ||||
| 					else | ||||
| 					{ | ||||
| 						_challengeProv = new KeeChallengeProv(); | ||||
| 						_challengeSecret = _challengeProv.GetSecret(_chalInfo, challengeResponse); | ||||
| 						Array.Clear(challengeResponse, 0, challengeResponse.Length);	 | ||||
| 					} | ||||
|                      | ||||
| 				} | ||||
| 				catch (Exception e) | ||||
| 				{ | ||||
| 					Kp2aLog.Log(e.ToString()); | ||||
| 					Toast.MakeText(this, "Error: " + e.Message, ToastLength.Long).Show(); | ||||
| 					return; | ||||
| 				} | ||||
| 				 | ||||
|                 UpdateOkButtonState(); | ||||
| 				FindViewById(Resource.Id.otpInitView).Visibility = ViewStates.Gone; | ||||
| 			 | ||||
| 				if (_challengeSecret != null) | ||||
|                 { | ||||
| 					new LoadingDialog<object, object, object>(this, true, | ||||
| 						//doInBackground | ||||
| 					delegate | ||||
| 					{ | ||||
| 						//save aux file | ||||
| 						try | ||||
| 						{ | ||||
| 							ChallengeInfo temp = _challengeProv.Encrypt(_challengeSecret); | ||||
| 							if (!temp.Save(_otpAuxIoc)) | ||||
| 							{ | ||||
| 								Toast.MakeText(this, Resource.String.ErrorUpdatingChalAuxFile, ToastLength.Long).Show(); | ||||
| 								return false; | ||||
| 							} | ||||
| 							 | ||||
| 						} | ||||
| 						catch (Exception e) | ||||
| 						{ | ||||
| 							Kp2aLog.LogUnexpectedError(e); | ||||
| 						} | ||||
| 						return null; | ||||
| 					} | ||||
| 					, delegate | ||||
| 					{ | ||||
| 						 | ||||
| 					}).Execute(); | ||||
|              | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     Toast.MakeText(this, Resource.String.bad_resp, ToastLength.Long).Show(); | ||||
|                     return; | ||||
|                 } | ||||
| 			} | ||||
| 		    if (requestCode == RequestCodeChallengeYubikey) | ||||
| 		    { | ||||
| 		        if (resultCode == Result.Ok) | ||||
| 		        { | ||||
|  | ||||
|  | ||||
| 		            try | ||||
| 		            { | ||||
| 		                byte[] challengeResponse = data.GetByteArrayExtra("response"); | ||||
| 		                if (_currentlyWaitingKey != null) | ||||
| 		                { | ||||
| 		                    if ((challengeResponse != null) && (challengeResponse.Length > 0)) | ||||
| 		                    { | ||||
| 		                        _currentlyWaitingKey.Response = challengeResponse; | ||||
| 		                    } | ||||
| 		                    else | ||||
| 		                        _currentlyWaitingKey.Error = "Did not receive a valid response."; | ||||
|  | ||||
| 		                    return; | ||||
|  | ||||
| 		                } | ||||
| 		                else | ||||
| 		                { | ||||
| 		                    _challengeProv = new KeeChallengeProv(); | ||||
| 		                    _challengeSecret = _challengeProv.GetSecret(_chalInfo, challengeResponse); | ||||
| 		                    Array.Clear(challengeResponse, 0, challengeResponse.Length); | ||||
| 		                } | ||||
|  | ||||
| 		            } | ||||
| 		            catch (Exception e) | ||||
| 		            { | ||||
| 		                if (_currentlyWaitingKey != null) | ||||
| 		                { | ||||
| 		                    _currentlyWaitingKey.Error = e.Message; | ||||
| 		                } | ||||
| 		                Kp2aLog.Log(e.ToString()); | ||||
| 		                Toast.MakeText(this, "Error: " + e.Message, ToastLength.Long).Show(); | ||||
| 		                return; | ||||
| 		            } | ||||
|  | ||||
| 		            UpdateOkButtonState(); | ||||
| 		            FindViewById(Resource.Id.otpInitView).Visibility = ViewStates.Gone; | ||||
|  | ||||
| 		            if (_challengeSecret != null) | ||||
| 		            { | ||||
| 		                new LoadingDialog<object, object, object>(this, true, | ||||
| 		                    //doInBackground | ||||
| 		                    delegate | ||||
| 		                    { | ||||
| 		                        //save aux file | ||||
| 		                        try | ||||
| 		                        { | ||||
| 		                            ChallengeInfo temp = _challengeProv.Encrypt(_challengeSecret); | ||||
| 		                            if (!temp.Save(_otpAuxIoc)) | ||||
| 		                            { | ||||
| 		                                Toast.MakeText(this, Resource.String.ErrorUpdatingChalAuxFile, ToastLength.Long) | ||||
| 		                                    .Show(); | ||||
| 		                                return false; | ||||
| 		                            } | ||||
|  | ||||
| 		                        } | ||||
| 		                        catch (Exception e) | ||||
| 		                        { | ||||
| 		                            Kp2aLog.LogUnexpectedError(e); | ||||
| 		                        } | ||||
| 		                        return null; | ||||
| 		                    } | ||||
| 		                    , delegate | ||||
| 		                    { | ||||
|  | ||||
| 		                    }).Execute(); | ||||
|  | ||||
| 		            } | ||||
| 		            else | ||||
| 		            { | ||||
| 		                Toast.MakeText(this, Resource.String.bad_resp, ToastLength.Long).Show(); | ||||
| 		                return; | ||||
| 		            } | ||||
| 		        } | ||||
| 		    } | ||||
| 		    else | ||||
| 		    { | ||||
| 		        if (_currentlyWaitingKey != null) | ||||
| 		            _currentlyWaitingKey.Error = "Cancelled Yubichallenge."; | ||||
| 		    } | ||||
| 		} | ||||
|  | ||||
| 		private AuxFileLoader GetAuxFileLoader() | ||||
| @@ -690,27 +647,12 @@ namespace keepass2android | ||||
|  | ||||
| 			protected override void HandleSuccess() | ||||
| 			{ | ||||
| 				Intent chalIntent = new Intent("com.yubichallenge.NFCActivity.CHALLENGE"); | ||||
| 				chalIntent.PutExtra("challenge", Activity._chalInfo.Challenge); | ||||
| 				chalIntent.PutExtra("slot", 2); | ||||
| 				IList<ResolveInfo> activities = Activity.PackageManager.QueryIntentActivities(chalIntent, 0); | ||||
| 				bool isIntentSafe = activities.Count > 0; | ||||
| 				if (isIntentSafe) | ||||
| 				{ | ||||
| 					Activity.StartActivityForResult(chalIntent, RequestCodeChallengeYubikey); | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					AlertDialog.Builder b = new AlertDialog.Builder(Activity); | ||||
| 					b.SetMessage(Resource.String.YubiChallengeNotInstalled); | ||||
| 					b.SetPositiveButton(Android.Resource.String.Ok, | ||||
| 										delegate | ||||
| 										{ | ||||
| 											Util.GotoUrl(Activity, Activity.GetString(Resource.String.MarketURL) + "com.yubichallenge"); | ||||
| 										}); | ||||
| 					b.SetNegativeButton(Resource.String.cancel, delegate { }); | ||||
| 					b.Create().Show(); | ||||
| 				} | ||||
| 			    var chalIntent = Activity.TryGetYubichallengeIntentOrPrompt(Activity._chalInfo.Challenge, true); | ||||
|  | ||||
|                 if (chalIntent != null) | ||||
| 			    { | ||||
| 			        Activity.StartActivityForResult(chalIntent, RequestCodeChallengeYubikey); | ||||
|                 } | ||||
| 			} | ||||
|  | ||||
| 			protected override string GetErrorMessage() | ||||
| @@ -746,7 +688,8 @@ namespace keepass2android | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		private void ShowOtpEntry(IList<string> prefilledOtps) | ||||
|  | ||||
| 	    private void ShowOtpEntry(IList<string> prefilledOtps) | ||||
| 		{ | ||||
| 			FindViewById(Resource.Id.otpInitView).Visibility = ViewStates.Gone; | ||||
| 			FindViewById(Resource.Id.otpEntry).Visibility = ViewStates.Visible; | ||||
|   | ||||
| @@ -864,6 +864,7 @@ Erstes öffentliches Release</string> | ||||
|     <item>Kennwort + OTP Secret (Recovery-Modus)</item> | ||||
|     <item>Passwort + Challenge-Response</item> | ||||
|     <item>Passwort + Challenge-Response-Secret (Recovery-Modus)</item> | ||||
|     <item>Password + Challenge-Response for KeepassXC database</item> | ||||
|   </string-array> | ||||
|   <string-array name="AcceptAllServerCertificates_options"> | ||||
|     <item>Fehler bei Zertifikatsvalidierung ignorieren</item> | ||||
|   | ||||
| @@ -1069,6 +1069,7 @@ Initial public release | ||||
| 		<item>Password + OTP secret (recovery mode)</item> | ||||
| 		<item>Password + Challenge-Response</item> | ||||
| 		<item>Password + Challenge-Response secret (recovery mode)</item> | ||||
| 	  <item>Password + Challenge-Response for KeepassXC database</item> | ||||
| 	</string-array> | ||||
| 	<string-array name="AcceptAllServerCertificates_options"> | ||||
| 		<item>Ignore certificate validation failures</item> | ||||
|   | ||||
| @@ -171,6 +171,7 @@ | ||||
|     </Reference> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <Compile Include="ChallengeXCKey.cs" /> | ||||
|     <Compile Include="EntryActivityClasses\ViewImagePopupItem.cs" /> | ||||
|     <Compile Include="ImageViewActivity.cs" /> | ||||
|     <Compile Include="addons\OtpKeyProv\EncodingUtil.cs" /> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Philipp Crocoll
					Philipp Crocoll