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,6 +55,9 @@ namespace KeePassLib.Cryptography.KeyDerivation
|
||||
get { return "AES-KDF"; }
|
||||
}
|
||||
|
||||
public override byte[] GetSeed(KdfParameters p)
|
||||
{ return p.GetByteArray(ParamSeed); }
|
||||
|
||||
public AesKdf()
|
||||
{
|
||||
}
|
||||
|
@@ -68,6 +68,9 @@ namespace KeePassLib.Cryptography.KeyDerivation
|
||||
get { return "Argon2"; }
|
||||
}
|
||||
|
||||
public override byte[] GetSeed(KdfParameters p)
|
||||
{ return p.GetByteArray(ParamSalt); }
|
||||
|
||||
public Argon2Kdf()
|
||||
{
|
||||
}
|
||||
|
@@ -36,6 +36,8 @@ namespace KeePassLib.Cryptography.KeyDerivation
|
||||
get;
|
||||
}
|
||||
|
||||
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,16 +208,18 @@ 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"
|
||||
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; }
|
||||
|
||||
|
||||
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,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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