diff --git a/src/Exceptions/KeyStorageException.cs b/src/Exceptions/KeyStorageException.cs new file mode 100644 index 0000000..63774d8 --- /dev/null +++ b/src/Exceptions/KeyStorageException.cs @@ -0,0 +1,17 @@ +using System; + +namespace KeePassWinHello +{ + [Serializable] + public class KeyStorageException : KeePassWinHelloException + { + public override bool IsPresentable { get { return true; } } + + public KeyStorageException(string message) : base(message) { } + public KeyStorageException(string message, Exception inner) : base(message, inner) { } + protected KeyStorageException() { } + protected KeyStorageException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } +} diff --git a/src/KeePassWinHello.csproj b/src/KeePassWinHello.csproj index 4157642..4e96bc3 100644 --- a/src/KeePassWinHello.csproj +++ b/src/KeePassWinHello.csproj @@ -61,6 +61,7 @@ + @@ -124,4 +125,4 @@ - \ No newline at end of file + diff --git a/src/KeyManagement/KeyManager.cs b/src/KeyManagement/KeyManager.cs index d55eed8..dacc792 100644 --- a/src/KeyManagement/KeyManager.cs +++ b/src/KeyManagement/KeyManager.cs @@ -198,6 +198,10 @@ private void Lock(bool isDbLocking, string dbPath, CompositeKey databaseMasterKe { // it's OK } + catch (KeyStorageException ex) + { + _uiContextManager.CurrentContext.ShowError(ex); + } } public void RevokeAll() diff --git a/src/KeyManagement/Storage/KeyWindowsStorage.cs b/src/KeyManagement/Storage/KeyWindowsStorage.cs index 1dca357..a863b44 100644 --- a/src/KeyManagement/Storage/KeyWindowsStorage.cs +++ b/src/KeyManagement/Storage/KeyWindowsStorage.cs @@ -11,6 +11,7 @@ internal class KeyWindowsStorage : IKeyStorage { #region Credential Manager API private const int ERROR_NOT_FOUND = 0x490; + private const int ERROR_NO_SUCH_LOGON_SESSION = 0x520; private const int CRED_TYPE_GENERIC = 0x1; @@ -96,7 +97,7 @@ public void AddOrUpdate(string dbPath, ProtectedKey protectedKey) Marshal.Copy(data, 0, ncred.CredentialBlob, data.Length); ncred.CredentialBlobSize = (uint)data.Length; - CredWrite(ref ncred, 0).ThrowOnError("CredWrite"); + WriteCredential(ref ncred); } finally { @@ -111,6 +112,38 @@ public void AddOrUpdate(string dbPath, ProtectedKey protectedKey) } } + private static void WriteCredential(ref CREDENTIAL credential) + { + if (CredWrite(ref credential, 0).Result) + return; + + int errorCode = Marshal.GetLastWin32Error(); + if (errorCode == ERROR_NO_SUCH_LOGON_SESSION) + throw new KeyStorageException(GetNoLogonSessionMessage()); + + throw new EnviromentErrorException("CredWrite", errorCode); + } + + private static string GetNoLogonSessionMessage() + { + string message = "Windows Credential Manager cannot save the database key because Windows reported that the current logon session is not available. The database key was not saved persistently."; + if (IsCurrentProcessElevatedSafe()) + message += " This can happen when KeePass is running as Administrator; restart KeePass without elevation to use Credential Manager storage."; + return message; + } + + private static bool IsCurrentProcessElevatedSafe() + { + try + { + return UAC.IsCurrentProcessElevated; + } + catch + { + return false; + } + } + public bool ContainsKey(string dbPath) { IntPtr ncredPtr = IntPtr.Zero;