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:
Philipp Crocoll
2017-10-16 07:36:49 +02:00
parent dbca918f07
commit 911c630b91
5 changed files with 118 additions and 38 deletions

View File

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

View File

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

View File

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

View File

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

View File

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