Skip to content

Commit 5f3c8ed

Browse files
committed
Multiple attempts for Quick Unlock
Quick Unlock now allows up to 3 attempts to unlock a locked DB. This setting is DB specific and thus requires DB specific settings to be used.
1 parent f346812 commit 5f3c8ed

File tree

11 files changed

+194
-100
lines changed

11 files changed

+194
-100
lines changed

Translations/LockAssist.de.language.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Bitte bearbeiten und Quick Unlock PIN als Password setzen.</value>
5656
</item>
5757
<item>
5858
<key>OptionsQUSettingsPerDB_UnlockAttempts</key>
59-
<value>Quick Unlock Versuche:</value>
59+
<value>Quick Unlock Versuche (NUR für aktive Datenbank):</value>
6060
</item>
6161
<item>
6262
<key>OptionsSwitchDBToGeneral</key>

Translations/LockAssist.template.language.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Please edit and set Quick Unlock PIN as password</value>
5656
</item>
5757
<item>
5858
<key>OptionsQUSettingsPerDB_UnlockAttempts</key>
59-
<value>Quick Unlock Attempts:</value>
59+
<value>Quick Unlock Attempts (requires DB specific settings):</value>
6060
</item>
6161
<item>
6262
<key>OptionsSwitchDBToGeneral</key>

src/Config/LockAssistConfig_QU.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ private void GetQUOptionsInternal(PwDatabase db)
5050

5151
public int GetQU_DBSpecificUnlockAttempts()
5252
{
53-
if (QU_DBSpecific) return 1;
54-
return Math.Max(Program.Config.Security.MasterKeyTries, QU_DBSpecificUnlockAttempts);
53+
if (!QU_DBSpecific) return 1;
54+
return Math.Max(1, QU_DBSpecificUnlockAttempts);
5555
}
5656

5757
public static bool QU_FirstTime
@@ -126,8 +126,7 @@ public void QU_DeleteDBConfig(PwDatabase db)
126126
deleted |= db.CustomData.Remove(LockAssistKeyFromEnd);
127127
deleted |= db.CustomData.Remove(LockAssistQuickUnlockDBSpecific);
128128
deleted |= db.CustomData.Remove(LockAssistQU_ValiditySeconds);
129-
//Don't delete this value!
130-
//deleted |= db.CustomData.Remove(LockAssistQU_DBSpecificUnlockAttempts);
129+
deleted |= db.CustomData.Remove(LockAssistQU_DBSpecificUnlockAttempts);
131130
if (deleted)
132131
{
133132
QU_FlagDBChanged(db);

src/OptionsForm.Designer.cs

Lines changed: 59 additions & 59 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/OptionsForm.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ public OptionsForm()
6161
cbSLValidityInterval.SelectedIndex = 0;
6262

6363
lQUAttempts.Text = PluginTranslate.OptionsQUSettingsPerDB_UnlockAttempts;
64-
lQUAttempts.Left = nQUAttempts.Left - 10 - lQUAttempts.Width;
6564
cbPINDBSpecific_CheckedChanged(null, null);
6665
nQUAttempts.Maximum = Program.Config.Security.MasterKeyTries;
6766
}
@@ -255,6 +254,12 @@ private void cbPINDBSpecific_CheckedChanged(object sender, EventArgs e)
255254
{
256255
lQUAttempts.Enabled = cbPINDBSpecific.Checked;
257256
nQUAttempts.Enabled = cbPINDBSpecific.Checked;
257+
if (cbPINDBSpecific.Checked && nQUAttempts.Tag != null) nQUAttempts.Value = (int)nQUAttempts.Tag;
258+
else
259+
{
260+
nQUAttempts.Tag = (int)nQUAttempts.Value;
261+
nQUAttempts.Value = 1;
262+
}
258263
}
259264
}
260265
}

src/PluginTranslation.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,9 @@ public static class PluginTranslate
9090
/// </summary>
9191
public static readonly string OptionsQUSettingsPerDB = @"Settings are DB specific";
9292
/// <summary>
93-
/// Quick Unlock Attempts:
93+
/// Quick Unlock Attempts (requires DB specific settings):
9494
/// </summary>
95-
public static readonly string OptionsQUSettingsPerDB_UnlockAttempts = @"Quick Unlock Attempts:";
95+
public static readonly string OptionsQUSettingsPerDB_UnlockAttempts = @"Quick Unlock Attempts (requires DB specific settings):";
9696
/// <summary>
9797
/// Database specific settings switched off.
9898
///

src/Properties/AssemblyInfo.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
[assembly: AssemblyConfiguration("")]
1111
[assembly: AssemblyCompany("Rookiestyle")]
1212
[assembly: AssemblyProduct("KeePass Plugin")]
13-
[assembly: AssemblyCopyright("Copyright © 2021-2025")]
13+
[assembly: AssemblyCopyright("Copyright © rookiestyle")]
1414
[assembly: AssemblyTrademark("")]
1515
[assembly: AssemblyCulture("")]
1616

@@ -32,5 +32,5 @@
3232
// Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden,
3333
// indem Sie "*" wie unten gezeigt eingeben:
3434
// [assembly: AssemblyVersion("1.0.*")]
35-
[assembly: AssemblyVersion("3.7")]
36-
[assembly: AssemblyFileVersion("3.7")]
35+
[assembly: AssemblyVersion("3.8")]
36+
[assembly: AssemblyFileVersion("3.8")]

src/QuickUnlockKeyProv.cs

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.IO;
34
using System.Reflection;
45
using System.Security.Cryptography;
56
using System.Windows.Forms;
7+
using KeePass.Resources;
8+
using KeePass.Util;
69
using KeePassLib;
710
using KeePassLib.Cryptography;
811
using KeePassLib.Cryptography.Cipher;
@@ -29,6 +32,7 @@ public class QuickUnlockOldKeyInfo
2932
public ProtectedString QuickUnlockKey = ProtectedString.EmptyEx;
3033
public ProtectedBinary pwHash = null;
3134
public string keyFile = string.Empty;
35+
public ProtectedBinary pbKeyFile = null;
3236
public string CustomKeyProviderName = string.Empty;
3337
public ProtectedBinary CustomKeyProviderData = null;
3438
public bool account = false;
@@ -80,13 +84,14 @@ public override byte[] GetKey(KeyProviderQueryContext ctx)
8084
Tools.ShowError(PluginTranslate.KeyProvNoQuickUnlock);
8185
return null;
8286
}
83-
87+
8488
int iAttempt = 0;
8589
int iMaxFailed = 1;
8690
m_DBSpecificUnlockAttempts.TryGetValue(ctx.DatabasePath, out iMaxFailed);
8791
while (iAttempt++ < iMaxFailed)
8892
{
8993
var fQuickUnlock = new UnlockForm();
94+
fQuickUnlock.SetAttempts(iMaxFailed, iAttempt);
9095
if (KeePass.UI.UIUtil.ShowDialogNotValue(fQuickUnlock, DialogResult.OK)) return null;
9196
ProtectedString psQuickUnlockKey = fQuickUnlock.QuickUnlockKey;
9297
KeePass.UI.UIUtil.DestroyForm(fQuickUnlock);
@@ -130,10 +135,24 @@ private static void RestoreOldMasterKeyInternal(PwDatabase db, IOConnectionInfo
130135
ck.AddUserKey(p);
131136
lMsg.Add("Add password to masterkey: " + (bRestoreSuccess ? "Password decrypted and restored" : "Password restore failed, empty password set"));
132137
}
138+
bool bFileError = false;
133139
if (!string.IsNullOrEmpty(quOldKey.keyFile))
134140
{
135-
ck.AddUserKey(new KcpKeyFile(quOldKey.keyFile));
136-
lMsg.Add("Add key file to masterkey");
141+
var kfRestored = RestoreKeyFile(quOldKey, lMsg, out bFileError);
142+
if (kfRestored != null)
143+
{
144+
ck.AddUserKey(kfRestored);
145+
lMsg.Add("Add key file to masterkey");
146+
}
147+
else
148+
{
149+
lMsg.Add("Error adding key file to masterkey");
150+
string sKeyFileError = KPRes.KeyFileError;
151+
if (sKeyFileError.EndsWith(".")) sKeyFileError = sKeyFileError.Substring(0, sKeyFileError.Length - 1);
152+
sKeyFileError += ": " + quOldKey.keyFile;
153+
MessageService.ShowWarning(new string[] { sKeyFileError, KPRes.MasterKeyChangeInfo});
154+
bFileError = true;
155+
}
137156
}
138157
if (!string.IsNullOrEmpty(quOldKey.CustomKeyProviderName))
139158
{
@@ -154,8 +173,62 @@ private static void RestoreOldMasterKeyInternal(PwDatabase db, IOConnectionInfo
154173
}
155174
KeePass.Program.Config.Defaults.SetKeySources(ioConnection, ck);
156175
lMsg.Add("Set database key sources");
157-
if (bRestoreSuccess) PluginDebug.AddSuccess("Restore old masterkey", 0, lMsg.ToArray());
176+
if (bRestoreSuccess && !bFileError) PluginDebug.AddSuccess("Restore old masterkey", 0, lMsg.ToArray());
177+
else if (bRestoreSuccess && bFileError) PluginDebug.AddWarning("Restore old masterkey", 0, lMsg.ToArray());
158178
else PluginDebug.AddError("Restore old masterkey", 0, lMsg.ToArray());
179+
if (bFileError) ShowChangeMasterKeyForm(db);
180+
}
181+
182+
private static MethodInfo m_miChangeMasterKey = null;
183+
private static void ShowChangeMasterKeyForm(PwDatabase db)
184+
{
185+
if (m_miChangeMasterKey == null)
186+
{
187+
m_miChangeMasterKey = KeePass.Program.MainForm.GetType().GetMethod("ChangeMasterKey", BindingFlags.Instance | BindingFlags.NonPublic);
188+
}
189+
if (m_miChangeMasterKey != null) m_miChangeMasterKey.Invoke(KeePass.Program.MainForm, new object[] { db });
190+
}
191+
192+
private static KcpKeyFile RestoreKeyFile(QuickUnlockOldKeyInfo quOldKey, List<string> lErrors, out bool bFileError)
193+
{
194+
bFileError = false;
195+
KcpKeyFile kf = RestoreKeyFileFromBuffer(quOldKey, lErrors);
196+
if (kf != null) return kf;
197+
try
198+
{
199+
return new KcpKeyFile(quOldKey.keyFile);
200+
}
201+
catch (Exception ex) { lErrors.Add("Access to key file not possible: " + ex.Message); }
202+
bFileError = true;
203+
return null;
204+
}
205+
206+
private static KcpKeyFile m_kcpKeyFileHelp = null;
207+
private static KcpKeyFile RestoreKeyFileFromBuffer(QuickUnlockOldKeyInfo quOldKey, List<string> lErrors)
208+
{
209+
if (m_kcpKeyFileHelp == null)
210+
{
211+
string strFile = Path.GetTempFileName();
212+
File.WriteAllText(strFile, Guid.NewGuid().ToString());
213+
m_kcpKeyFileHelp = new KcpKeyFile(strFile);
214+
File.Delete(strFile);
215+
}
216+
217+
var fi = m_kcpKeyFileHelp.GetType().GetField("m_pbKeyData", BindingFlags.Instance | BindingFlags.NonPublic);
218+
if (fi == null)
219+
{
220+
lErrors.Add("Can't restore m_pbKeyData");
221+
return null;
222+
}
223+
fi.SetValue(m_kcpKeyFileHelp, quOldKey.pbKeyFile);
224+
fi = m_kcpKeyFileHelp.GetType().GetField("m_strPath", BindingFlags.Instance | BindingFlags.NonPublic);
225+
if (fi == null)
226+
{
227+
lErrors.Add("Can't restore m_strPath");
228+
return null;
229+
}
230+
fi.SetValue(m_kcpKeyFileHelp, quOldKey.keyFile);
231+
return m_kcpKeyFileHelp;
159232
}
160233

161234
internal static void RestoreOldMasterKey(PwDatabase db, QuickUnlockOldKeyInfo quOldKey)
@@ -222,7 +295,10 @@ private static void AddOldMasterKey(PwDatabase db, ProtectedString QuickUnlockKe
222295
quOldKey.pwHash = EncryptKey(QuickUnlockKey, pbPasswordSerialized);
223296
}
224297
if (db.MasterKey.ContainsType(typeof(KcpKeyFile)))
298+
{
225299
quOldKey.keyFile = (db.MasterKey.GetUserKey(typeof(KcpKeyFile)) as KcpKeyFile).Path;
300+
quOldKey.pbKeyFile = (db.MasterKey.GetUserKey(typeof(KcpKeyFile)) as KcpKeyFile).KeyData;
301+
}
226302
if (db.MasterKey.ContainsType(typeof(KcpCustomKey)))
227303
{
228304
quOldKey.CustomKeyProviderName = (db.MasterKey.GetUserKey(typeof(KcpCustomKey)) as KcpCustomKey).Name;

src/UnlockForm.Designer.cs

Lines changed: 23 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/UnlockForm.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Drawing;
1+
using System;
2+
using System.Drawing;
23
using System.Windows.Forms;
34
using KeePassLib.Security;
45
using PluginTools;
@@ -47,5 +48,17 @@ private void cbContinueUnlock_CheckedChanged(object sender, System.EventArgs e)
4748
{
4849
LockWorkspace.SetContinueUnlock(cbContinueUnlock.Checked);
4950
}
51+
52+
internal void SetAttempts(int iMaxFailed, int iAttempt)
53+
{
54+
if (iMaxFailed == 1) return;
55+
var bEndsWithColon = lLabel.Text.EndsWith(":");
56+
if (bEndsWithColon)
57+
{
58+
lLabel.Text = lLabel.Text.Substring(0, lLabel.Text.Length - 1);
59+
}
60+
lLabel.Text += " (" + iAttempt.ToString() + "/" + iMaxFailed.ToString() + ")";
61+
if (bEndsWithColon) lLabel.Text += ":";
62+
}
5063
}
5164
}

0 commit comments

Comments
 (0)