From 911c630b9126c91efa7673f937c826b817c214da Mon Sep 17 00:00:00 2001 From: Philipp Crocoll Date: Mon, 16 Oct 2017 07:36:49 +0200 Subject: [PATCH] start implementing KeepassXC Challenge-Response support (see #4), currently waiting for https://github.com/keepassxreboot/keepassxc/issues/1060), also missing support for saving at the moment and mem-leaking PasswordActivity --- src/KeePassLib2Android/Keys/CompositeKey.cs | 42 ++----- .../Serialization/KdbxFile.cs | 9 +- src/keepass2android/PasswordActivity.cs | 103 +++++++++++++++++- .../Resources/values-de/strings.xml | 1 + .../Resources/values/strings.xml | 1 + 5 files changed, 118 insertions(+), 38 deletions(-) diff --git a/src/KeePassLib2Android/Keys/CompositeKey.cs b/src/KeePassLib2Android/Keys/CompositeKey.cs index 180734c0..a347fc11 100644 --- a/src/KeePassLib2Android/Keys/CompositeKey.cs +++ b/src/KeePassLib2Android/Keys/CompositeKey.cs @@ -163,11 +163,12 @@ namespace KeePassLib.Keys { return (T) GetUserKey(typeof (T)); } + /// /// Creates the composite key from the supplied user key sources (password, /// key file, user account, computer ID, etc.). /// - private byte[] CreateRawCompositeKey32() + private byte[] CreateRawCompositeKey32(byte[] mPbMasterSeed) { ValidateUserKeys(); @@ -176,6 +177,8 @@ namespace KeePassLib.Keys int cbData = 0; foreach(IUserKey pKey in m_vUserKeys) { + if (pKey is ISeedBasedUserKey) + ((ISeedBasedUserKey)pKey).SetParams(mPbMasterSeed); ProtectedBinary b = pKey.KeyData; if(b != null) { @@ -200,43 +203,15 @@ namespace KeePassLib.Keys return pbHash; } - public bool EqualsValue(CompositeKey ckOther) - { - if(ckOther == null) throw new ArgumentNullException("ckOther"); - - byte[] pbThis = CreateRawCompositeKey32(); - byte[] pbOther = ckOther.CreateRawCompositeKey32(); - bool bResult = MemUtil.ArraysEqual(pbThis, pbOther); - MemUtil.ZeroByteArray(pbOther); - MemUtil.ZeroByteArray(pbThis); - - return bResult; - } - - [Obsolete] - public ProtectedBinary GenerateKey32(byte[] pbKeySeed32, ulong uNumRounds) - { - Debug.Assert(pbKeySeed32 != null); - if(pbKeySeed32 == null) throw new ArgumentNullException("pbKeySeed32"); - Debug.Assert(pbKeySeed32.Length == 32); - if(pbKeySeed32.Length != 32) throw new ArgumentException("pbKeySeed32"); - - AesKdf kdf = new AesKdf(); - KdfParameters p = kdf.GetDefaultParameters(); - p.SetUInt64(AesKdf.ParamRounds, uNumRounds); - p.SetByteArray(AesKdf.ParamSeed, pbKeySeed32); - - return GenerateKey32(p); - } /// /// Generate a 32-byte (256-bit) key from the composite key. /// - public ProtectedBinary GenerateKey32(KdfParameters p) + public ProtectedBinary GenerateKey32(KdfParameters p, byte[] mPbMasterSeed) { if(p == null) { Debug.Assert(false); throw new ArgumentNullException("p"); } - byte[] pbRaw32 = CreateRawCompositeKey32(); + byte[] pbRaw32 = CreateRawCompositeKey32(mPbMasterSeed); if((pbRaw32 == null) || (pbRaw32.Length != 32)) { Debug.Assert(false); return null; } @@ -279,6 +254,11 @@ namespace KeePassLib.Keys } } + public interface ISeedBasedUserKey + { + void SetParams(byte[] masterSeed); + } + public sealed class InvalidCompositeKeyException : Exception { public override string Message diff --git a/src/KeePassLib2Android/Serialization/KdbxFile.cs b/src/KeePassLib2Android/Serialization/KdbxFile.cs index 261e4fd7..e61e2632 100644 --- a/src/KeePassLib2Android/Serialization/KdbxFile.cs +++ b/src/KeePassLib2Android/Serialization/KdbxFile.cs @@ -413,12 +413,15 @@ namespace KeePassLib.Serialization Debug.Assert(m_pbMasterSeed.Length == 32); if(m_pbMasterSeed.Length != 32) throw new FormatException(KLRes.MasterSeedLengthInvalid); - Array.Copy(m_pbMasterSeed, 0, pbCmp, 0, 32); + Debug.Assert(m_pwDatabase != null); Debug.Assert(m_pwDatabase.MasterKey != null); - ProtectedBinary pbinUser = m_pwDatabase.MasterKey.GenerateKey32( - m_pwDatabase.KdfParameters); + ProtectedBinary pbinUser = m_pwDatabase.MasterKey.GenerateKey32(m_pwDatabase.KdfParameters, + m_pbMasterSeed); + + Array.Copy(m_pbMasterSeed, 0, pbCmp, 0, 32); + Debug.Assert(pbinUser != null); if(pbinUser == null) throw new SecurityException(KLRes.InvalidCompositeKey); diff --git a/src/keepass2android/PasswordActivity.cs b/src/keepass2android/PasswordActivity.cs index 6eb4ffc5..b48a03d2 100644 --- a/src/keepass2android/PasswordActivity.cs +++ b/src/keepass2android/PasswordActivity.cs @@ -59,6 +59,8 @@ using Object = Java.Lang.Object; 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; @@ -67,6 +69,71 @@ 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 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", ConfigurationChanges = ConfigChanges.Orientation, LaunchMode = LaunchMode.SingleInstance, @@ -84,7 +151,8 @@ namespace keepass2android Otp = 2, OtpRecovery = 3, Challenge = 4, - ChalRecovery = 5 + ChalRecovery = 5, + ChallengeXC = 6 //KeepassXC compatible Challenge-Response } public const String KeyDefaultFilename = "defaultFileName"; @@ -102,6 +170,8 @@ namespace keepass2android private const string KeyProviderIdOtpRecovery = "KP2A-OTPSecret"; private const string KeyProviderIdChallenge = "KP2A-Chal"; private const string KeyProviderIdChallengeRecovery = "KP2A-ChalSecret"; + private const string KeyProviderIdChallengeXC = "KP2A-ChalXC"; + private const int RequestCodePrepareDbFile = 1000; private const int RequestCodePrepareOtpAuxFile = 1001; @@ -114,6 +184,8 @@ namespace keepass2android private Task _loadDbFileTask; private bool _loadDbTaskOffline; //indicate if preloading was started with offline mode + private ChallengeXCKey _currentlyWaitingKey; + private IOConnectionInfo _ioConnection; private String _keyFileOrProvider; bool _showPassword; @@ -140,6 +212,8 @@ namespace keepass2android return KeyProviders.Challenge; if (_keyFileOrProvider == KeyProviderIdChallengeRecovery) return KeyProviders.ChalRecovery; + if (_keyFileOrProvider == KeyProviderIdChallengeXC) + return KeyProviders.ChallengeXC; return KeyProviders.KeyFile; } } @@ -364,10 +438,19 @@ namespace keepass2android { try { - _challengeProv = new KeeChallengeProv(); byte[] challengeResponse = data.GetByteArrayExtra("response"); - _challengeSecret = _challengeProv.GetSecret(_chalInfo, challengeResponse); - Array.Clear(challengeResponse, 0, challengeResponse.Length); + 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) { @@ -1250,6 +1333,9 @@ namespace keepass2android case 5: _keyFileOrProvider = KeyProviderIdChallengeRecovery; break; + case 6: + _keyFileOrProvider = KeyProviderIdChallengeXC; + break; default: throw new Exception("Unexpected position " + args.Position + " / " + ((ICursor) ((AdapterView) sender).GetItemAtPosition(args.Position)).GetString(1)); @@ -1336,6 +1422,9 @@ namespace keepass2android case KeyProviders.ChalRecovery: enabled = FindViewById(Resource.Id.pass_otpsecret).Text != ""; break; + case KeyProviders.ChallengeXC: + enabled = true; + break; case KeyProviders.Challenge: enabled = _challengeSecret != null; break; @@ -1378,6 +1467,7 @@ namespace keepass2android private void PerformLoadDatabase() { + _currentlyWaitingKey = null; //put loading into background thread to allow loading the key file (potentially over network) new SimpleLoadingDialog(this, GetString(Resource.String.loading), true, () => @@ -1511,6 +1601,11 @@ namespace keepass2android else if (KeyProviderType == KeyProviders.Challenge) { compositeKey.AddUserKey(new KcpCustomKey(KeeChallengeProv.Name, _challengeSecret, true)); + } + else if (KeyProviderType == KeyProviders.ChallengeXC) + { + _currentlyWaitingKey = new ChallengeXCKey(this, RequestCodeChallengeYubikey); + compositeKey.AddUserKey(_currentlyWaitingKey); } return true; } diff --git a/src/keepass2android/Resources/values-de/strings.xml b/src/keepass2android/Resources/values-de/strings.xml index 51f0ec0f..1a7dcbec 100644 --- a/src/keepass2android/Resources/values-de/strings.xml +++ b/src/keepass2android/Resources/values-de/strings.xml @@ -823,6 +823,7 @@ Erstes öffentliches Release Kennwort + OTP Secret (Recovery-Modus) Passwort + Challenge-Response Passwort + Challenge-Response-Secret (Recovery-Modus) + Passwort + Challenge-Response für KeepassXC-Datenbank Fehler bei Zertifikatsvalidierung ignorieren diff --git a/src/keepass2android/Resources/values/strings.xml b/src/keepass2android/Resources/values/strings.xml index 6a607b9c..f49ecbea 100644 --- a/src/keepass2android/Resources/values/strings.xml +++ b/src/keepass2android/Resources/values/strings.xml @@ -1002,6 +1002,7 @@ Initial public release Password + OTP secret (recovery mode) Password + Challenge-Response Password + Challenge-Response secret (recovery mode) + Password + Challenge-Response for KeepassXC database Ignore certificate validation failures