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:
Philipp Crocoll
2018-04-10 07:15:19 +02:00
parent cdbb492f2c
commit f14aad0c50
10 changed files with 265 additions and 169 deletions

View File

@@ -55,6 +55,9 @@ namespace KeePassLib.Cryptography.KeyDerivation
get { return "AES-KDF"; }
}
public override byte[] GetSeed(KdfParameters p)
{ return p.GetByteArray(ParamSeed); }
public AesKdf()
{
}

View File

@@ -68,6 +68,9 @@ namespace KeePassLib.Cryptography.KeyDerivation
get { return "Argon2"; }
}
public override byte[] GetSeed(KdfParameters p)
{ return p.GetByteArray(ParamSalt); }
public Argon2Kdf()
{
}

View File

@@ -36,6 +36,8 @@ namespace KeePassLib.Cryptography.KeyDerivation
get;
}
public abstract byte[] GetSeed(KdfParameters p);
public virtual KdfParameters GetDefaultParameters()
{
return new KdfParameters(this.Uuid);

View File

@@ -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,9 +208,6 @@ namespace KeePassLib.Keys
{
if(p == null) { Debug.Assert(false); throw new ArgumentNullException("p"); }
byte[] pbRaw32 = CreateRawCompositeKey32(mPbMasterSeed);
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"
@@ -221,6 +215,11 @@ namespace KeePassLib.Keys
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; }
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

View 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;
}
}
}

View File

@@ -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,6 +39,37 @@ namespace keepass2android
{
}
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();
@@ -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;
}
}
}

View File

@@ -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,71 +69,6 @@ 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",
ConfigurationChanges = ConfigChanges.Orientation,
LaunchMode = LaunchMode.SingleInstance,
@@ -434,15 +369,26 @@ namespace keepass2android
GetAuxFileLoader().LoadAuxFile(false);
}
if (requestCode == RequestCodeChallengeYubikey && resultCode == Result.Ok)
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
{
@@ -454,6 +400,10 @@ namespace keepass2android
}
catch (Exception e)
{
if (_currentlyWaitingKey != null)
{
_currentlyWaitingKey.Error = e.Message;
}
Kp2aLog.Log(e.ToString());
Toast.MakeText(this, "Error: " + e.Message, ToastLength.Long).Show();
return;
@@ -474,7 +424,8 @@ namespace keepass2android
ChallengeInfo temp = _challengeProv.Encrypt(_challengeSecret);
if (!temp.Save(_otpAuxIoc))
{
Toast.MakeText(this, Resource.String.ErrorUpdatingChalAuxFile, ToastLength.Long).Show();
Toast.MakeText(this, Resource.String.ErrorUpdatingChalAuxFile, ToastLength.Long)
.Show();
return false;
}
@@ -498,6 +449,12 @@ namespace keepass2android
}
}
}
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)
var chalIntent = Activity.TryGetYubichallengeIntentOrPrompt(Activity._chalInfo.Challenge, true);
if (chalIntent != null)
{
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();
}
}
protected override string GetErrorMessage()
@@ -746,6 +688,7 @@ namespace keepass2android
}
}
private void ShowOtpEntry(IList<string> prefilledOtps)
{
FindViewById(Resource.Id.otpInitView).Visibility = ViewStates.Gone;

View File

@@ -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>

View File

@@ -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>

View File

@@ -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" />