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
This commit is contained in:
@@ -163,11 +163,12 @@ namespace KeePassLib.Keys
|
||||
{
|
||||
return (T) GetUserKey(typeof (T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the composite key from the supplied user key sources (password,
|
||||
/// key file, user account, computer ID, etc.).
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a 32-byte (256-bit) key from the composite key.
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<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,
|
||||
@@ -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<MemoryStream> _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<EditText>(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;
|
||||
}
|
||||
|
||||
@@ -823,6 +823,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>Passwort + Challenge-Response für KeepassXC-Datenbank</item>
|
||||
</string-array>
|
||||
<string-array name="AcceptAllServerCertificates_options">
|
||||
<item>Fehler bei Zertifikatsvalidierung ignorieren</item>
|
||||
|
||||
@@ -1002,6 +1002,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>
|
||||
|
||||
Reference in New Issue
Block a user