mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-02-02 17:44:46 -05:00
Add support for Windows Hello
* Special thanks to @HexF and @smlu for their contributions towards this feature. * Add MVP support for Windows Hello as a Quick Unlock solution using the WinRT API. This works by signing a random challenge vector with the Windows Hello protected key store (typically from TPM). The signed challenge is hashed using SHA-256 and then used as the encryption key to encrypt the database credentials. Credentials are encrypted using AES-256/GCM. This ensures the database password can only be decrypted following a successful authentication with Windows Hello in the future. * Unify Touch ID and Windows Hello behavior under the Quick Unlock branding. Remove all timeout features of Touch ID as they are unnecessary and complicate the feature for no security gain. * Quick Unlock is automatically reset only when the database key is changed vice whenever database settings are modified. * Don't set database unlock dialog as always on top. This allows Touch ID and Windows Hello prompts to appear above the dialog properly. * Prevent quick unlock when using AutoOpen or opening from the command line.
This commit is contained in:
parent
a76daeb4c5
commit
4f0710350f
@ -58,9 +58,6 @@ option(WITH_XC_UPDATECHECK "Include automatic update checks; disable for control
|
||||
if(UNIX AND NOT APPLE)
|
||||
option(WITH_XC_FDOSECRETS "Implement freedesktop.org Secret Storage Spec server side API." OFF)
|
||||
endif()
|
||||
if(APPLE)
|
||||
option(WITH_XC_TOUCHID "Include TouchID support for macOS." OFF)
|
||||
endif()
|
||||
option(WITH_XC_DOCS "Enable building of documentation" ON)
|
||||
|
||||
if(WITH_CCACHE)
|
||||
@ -81,9 +78,6 @@ if(WITH_XC_ALL)
|
||||
set(WITH_XC_YUBIKEY ON)
|
||||
set(WITH_XC_SSHAGENT ON)
|
||||
set(WITH_XC_KEESHARE ON)
|
||||
if(APPLE)
|
||||
set(WITH_XC_TOUCHID ON)
|
||||
endif()
|
||||
if(UNIX AND NOT APPLE)
|
||||
set(WITH_XC_FDOSECRETS ON)
|
||||
endif()
|
||||
|
3
COPYING
3
COPYING
@ -141,7 +141,7 @@ Files: share/icons/badges/2_Expired.svg
|
||||
share/icons/database/C46_Help.svg
|
||||
share/icons/database/C53_Apply.svg
|
||||
share/icons/database/C61_Services.svg
|
||||
Copyright: 2020 KeePassXC Team <team@keepassxc.org>
|
||||
Copyright: 2022 KeePassXC Team <team@keepassxc.org>
|
||||
License: MIT
|
||||
|
||||
Files: share/icons/application/scalable/actions/chevron-double-down.svg
|
||||
@ -166,6 +166,7 @@ Files: share/icons/application/scalable/actions/chevron-double-down.svg
|
||||
share/icons/application/scalable/actions/entry-edit.svg
|
||||
share/icons/application/scalable/actions/entry-new.svg
|
||||
share/icons/application/scalable/actions/favicon-download.svg
|
||||
share/icons/application/scalable/actions/fingerprint.svg
|
||||
share/icons/application/scalable/actions/group-clone.svg
|
||||
share/icons/application/scalable/actions/group-delete.svg
|
||||
share/icons/application/scalable/actions/group-edit.svg
|
||||
|
@ -99,7 +99,6 @@ These steps place the compiled KeePassXC binary inside the `./build/src/` direct
|
||||
-DWITH_XC_BROWSER=[ON|OFF] Enable/Disable KeePassXC-Browser extension support (default: OFF)
|
||||
-DWITH_XC_NETWORKING=[ON|OFF] Enable/Disable Networking support (e.g., favicon downloading) (default: OFF)
|
||||
-DWITH_XC_SSHAGENT=[ON|OFF] Enable/Disable SSHAgent support (default: OFF)
|
||||
-DWITH_XC_TOUCHID=[ON|OFF] (macOS Only) Enable/Disable Touch ID unlock (default:OFF)
|
||||
-DWITH_XC_FDOSECRETS=[ON|OFF] (Linux Only) Enable/Disable Freedesktop.org Secrets Service support (default:OFF)
|
||||
-DWITH_XC_KEESHARE=[ON|OFF] Enable/Disable KeeShare group synchronization extension (default: OFF)
|
||||
-DWITH_XC_ALL=[ON|OFF] Enable/Disable compiling all plugins above (default: OFF)
|
||||
|
1
share/icons/application/scalable/actions/fingerprint.svg
Normal file
1
share/icons/application/scalable/actions/fingerprint.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M17.81,4.47C17.73,4.47 17.65,4.45 17.58,4.41C15.66,3.42 14,3 12,3C10.03,3 8.15,3.47 6.44,4.41C6.2,4.54 5.9,4.45 5.76,4.21C5.63,3.97 5.72,3.66 5.96,3.53C7.82,2.5 9.86,2 12,2C14.14,2 16,2.47 18.04,3.5C18.29,3.65 18.38,3.95 18.25,4.19C18.16,4.37 18,4.47 17.81,4.47M3.5,9.72C3.4,9.72 3.3,9.69 3.21,9.63C3,9.47 2.93,9.16 3.09,8.93C4.08,7.53 5.34,6.43 6.84,5.66C10,4.04 14,4.03 17.15,5.65C18.65,6.42 19.91,7.5 20.9,8.9C21.06,9.12 21,9.44 20.78,9.6C20.55,9.76 20.24,9.71 20.08,9.5C19.18,8.22 18.04,7.23 16.69,6.54C13.82,5.07 10.15,5.07 7.29,6.55C5.93,7.25 4.79,8.25 3.89,9.5C3.81,9.65 3.66,9.72 3.5,9.72M9.75,21.79C9.62,21.79 9.5,21.74 9.4,21.64C8.53,20.77 8.06,20.21 7.39,19C6.7,17.77 6.34,16.27 6.34,14.66C6.34,11.69 8.88,9.27 12,9.27C15.12,9.27 17.66,11.69 17.66,14.66A0.5,0.5 0 0,1 17.16,15.16A0.5,0.5 0 0,1 16.66,14.66C16.66,12.24 14.57,10.27 12,10.27C9.43,10.27 7.34,12.24 7.34,14.66C7.34,16.1 7.66,17.43 8.27,18.5C8.91,19.66 9.35,20.15 10.12,20.93C10.31,21.13 10.31,21.44 10.12,21.64C10,21.74 9.88,21.79 9.75,21.79M16.92,19.94C15.73,19.94 14.68,19.64 13.82,19.05C12.33,18.04 11.44,16.4 11.44,14.66A0.5,0.5 0 0,1 11.94,14.16A0.5,0.5 0 0,1 12.44,14.66C12.44,16.07 13.16,17.4 14.38,18.22C15.09,18.7 15.92,18.93 16.92,18.93C17.16,18.93 17.56,18.9 17.96,18.83C18.23,18.78 18.5,18.96 18.54,19.24C18.59,19.5 18.41,19.77 18.13,19.82C17.56,19.93 17.06,19.94 16.92,19.94M14.91,22C14.87,22 14.82,22 14.78,22C13.19,21.54 12.15,20.95 11.06,19.88C9.66,18.5 8.89,16.64 8.89,14.66C8.89,13.04 10.27,11.72 11.97,11.72C13.67,11.72 15.05,13.04 15.05,14.66C15.05,15.73 16,16.6 17.13,16.6C18.28,16.6 19.21,15.73 19.21,14.66C19.21,10.89 15.96,7.83 11.96,7.83C9.12,7.83 6.5,9.41 5.35,11.86C4.96,12.67 4.76,13.62 4.76,14.66C4.76,15.44 4.83,16.67 5.43,18.27C5.53,18.53 5.4,18.82 5.14,18.91C4.88,19 4.59,18.87 4.5,18.62C4,17.31 3.77,16 3.77,14.66C3.77,13.46 4,12.37 4.45,11.42C5.78,8.63 8.73,6.82 11.96,6.82C16.5,6.82 20.21,10.33 20.21,14.65C20.21,16.27 18.83,17.59 17.13,17.59C15.43,17.59 14.05,16.27 14.05,14.65C14.05,13.58 13.12,12.71 11.97,12.71C10.82,12.71 9.89,13.58 9.89,14.65C9.89,16.36 10.55,17.96 11.76,19.16C12.71,20.1 13.62,20.62 15.03,21C15.3,21.08 15.45,21.36 15.38,21.62C15.33,21.85 15.12,22 14.91,22Z" /></svg>
|
After Width: | Height: | Size: 2.4 KiB |
@ -39,6 +39,7 @@
|
||||
<file>application/scalable/actions/entry-edit.svg</file>
|
||||
<file>application/scalable/actions/entry-new.svg</file>
|
||||
<file>application/scalable/actions/favicon-download.svg</file>
|
||||
<file>application/scalable/actions/fingerprint.svg</file>
|
||||
<file>application/scalable/actions/getting-started.svg</file>
|
||||
<file>application/scalable/actions/group-delete.svg</file>
|
||||
<file>application/scalable/actions/group-edit.svg</file>
|
||||
@ -80,7 +81,6 @@
|
||||
<file>application/scalable/actions/username-copy.svg</file>
|
||||
<file>application/scalable/actions/view-history.svg</file>
|
||||
<file>application/scalable/actions/web.svg</file>
|
||||
|
||||
<file>application/scalable/apps/freedesktop.svg</file>
|
||||
<file>application/scalable/apps/internet-web-browser.svg</file>
|
||||
<file>application/scalable/apps/keepassxc.svg</file>
|
||||
|
@ -508,14 +508,6 @@
|
||||
<source>Lock databases after inactivity of</source>
|
||||
<translation>Lock databases after inactivity of</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source> min</source>
|
||||
<translation> min</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Forget TouchID after inactivity of</source>
|
||||
<translation>Forget TouchID after inactivity of</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Convenience</source>
|
||||
<translation>Convenience</translation>
|
||||
@ -524,10 +516,6 @@
|
||||
<source>Lock databases when session is locked or lid is closed</source>
|
||||
<translation>Lock databases when session is locked or lid is closed</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Forget TouchID when session is locked or lid is closed</source>
|
||||
<translation>Forget TouchID when session is locked or lid is closed</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Lock databases after minimizing the window</source>
|
||||
<translation>Lock databases after minimizing the window</translation>
|
||||
@ -552,10 +540,6 @@
|
||||
<source>Clipboard clear seconds</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Touch ID inactivity reset</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Database lock timeout seconds</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -589,6 +573,10 @@
|
||||
<source>Enable double click to copy the username/password entry columns</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable database quick unlock (Touch ID / Windows Hello)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>AutoType</name>
|
||||
@ -1474,10 +1462,6 @@ Backup database located at %2</source>
|
||||
<source>Hardware key help</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>TouchID for Quick Unlock</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unlock failed and no password given</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -1501,10 +1485,6 @@ To prevent this error from appearing, you must go to "Database Settings / S
|
||||
<source>Key file help</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot use database file as key file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -1577,6 +1557,26 @@ We recommend you update your KeePassXC installation.</source>
|
||||
<source>Database unlock canceled.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unlock</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to authenticate with Windows Hello</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unlock Database</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cancel</source>
|
||||
<translation type="unfinished">Cancel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to authenticate with Touch ID</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DatabaseSettingWidgetMetaData</name>
|
||||
@ -6837,10 +6837,6 @@ Kernel: %3 %4</source>
|
||||
<source>YubiKey</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>TouchID</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>None</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -7770,6 +7766,18 @@ Please consider generating a new key file.</source>
|
||||
<source>Browser Statistics</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Quick Unlock</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to create Windows Hello credential.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to sign challenge using Windows Hello.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QtIOCompressor</name>
|
||||
@ -8762,6 +8770,25 @@ Example: JBSWY3DPEHPK3PXP</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>WindowsHello</name>
|
||||
<message>
|
||||
<source>Failed to init KeePassXC crypto.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to encrypt key data.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to get Windows Hello credential.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to decrypt key data.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>YubiKey</name>
|
||||
<message>
|
||||
|
@ -216,6 +216,9 @@ if(WIN32)
|
||||
${keepassx_SOURCES}
|
||||
gui/osutils/winutils/ScreenLockListenerWin.cpp
|
||||
gui/osutils/winutils/WinUtils.cpp)
|
||||
if (MSVC)
|
||||
list(APPEND keepassx_SOURCES winhello/WindowsHello.cpp)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(keepassx_SOURCES ${keepassx_SOURCES}
|
||||
@ -234,9 +237,6 @@ add_feature_info(UpdateCheck WITH_XC_UPDATECHECK "Automatic update checking")
|
||||
if(UNIX AND NOT APPLE)
|
||||
add_feature_info(FdoSecrets WITH_XC_FDOSECRETS "Implement freedesktop.org Secret Storage Spec server side API.")
|
||||
endif()
|
||||
if(APPLE)
|
||||
add_feature_info(TouchID WITH_XC_TOUCHID "TouchID integration")
|
||||
endif()
|
||||
|
||||
add_subdirectory(browser)
|
||||
add_subdirectory(proxy)
|
||||
@ -308,7 +308,7 @@ if(WITH_XC_NETWORKING)
|
||||
updatecheck/UpdateChecker.cpp)
|
||||
endif()
|
||||
|
||||
if(WITH_XC_TOUCHID)
|
||||
if(APPLE)
|
||||
list(APPEND keepassx_SOURCES touchid/TouchID.mm)
|
||||
# TODO: Remove -Wno-error once deprecation warnings have been resolved.
|
||||
set_source_files_properties(touchid/TouchID.mm PROPERTY COMPILE_FLAGS "-Wno-old-style-cast -Wno-error")
|
||||
@ -347,13 +347,10 @@ if(WITH_XC_KEESHARE)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
target_link_libraries(keepassx_core "-framework Foundation -framework AppKit -framework Carbon")
|
||||
target_link_libraries(keepassx_core "-framework Foundation -framework AppKit -framework Carbon -framework Security -framework LocalAuthentication")
|
||||
if(Qt5MacExtras_FOUND)
|
||||
target_link_libraries(keepassx_core Qt5::MacExtras)
|
||||
endif()
|
||||
if(WITH_XC_TOUCHID)
|
||||
target_link_libraries(keepassx_core "-framework Security -framework LocalAuthentication")
|
||||
endif()
|
||||
endif()
|
||||
if(HAIKU)
|
||||
target_link_libraries(keepassx_core network)
|
||||
@ -364,6 +361,9 @@ if(UNIX AND NOT APPLE)
|
||||
endif()
|
||||
if(WIN32)
|
||||
target_link_libraries(keepassx_core Wtsapi32.lib Ws2_32.lib)
|
||||
if (MSVC)
|
||||
target_link_libraries(keepassx_core WindowsApp.lib)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
@ -388,12 +388,8 @@ if(APPLE AND WITH_APP_BUNDLE)
|
||||
configure_file(${CMAKE_SOURCE_DIR}/share/macosx/Info.plist.cmake ${CMAKE_CURRENT_BINARY_DIR}/Info.plist)
|
||||
set_target_properties(${PROGNAME} PROPERTIES
|
||||
MACOSX_BUNDLE ON
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_BINARY_DIR}/Info.plist)
|
||||
|
||||
if(WITH_XC_TOUCHID)
|
||||
set_target_properties(${PROGNAME} PROPERTIES
|
||||
CPACK_BUNDLE_APPLE_ENTITLEMENTS "${CMAKE_SOURCE_DIR}/share/macosx/keepassxc.entitlements")
|
||||
endif()
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_BINARY_DIR}/Info.plist
|
||||
CPACK_BUNDLE_APPLE_ENTITLEMENTS "${CMAKE_SOURCE_DIR}/share/macosx/keepassxc.entitlements")
|
||||
|
||||
if(QT_MAC_USE_COCOA AND EXISTS "${QT_LIBRARY_DIR}/Resources/qt_menu.nib")
|
||||
install(DIRECTORY "${QT_LIBRARY_DIR}/Resources/qt_menu.nib"
|
||||
|
@ -19,7 +19,6 @@
|
||||
#cmakedefine WITH_XC_SSHAGENT
|
||||
#cmakedefine WITH_XC_KEESHARE
|
||||
#cmakedefine WITH_XC_UPDATECHECK
|
||||
#cmakedefine WITH_XC_TOUCHID
|
||||
#cmakedefine WITH_XC_FDOSECRETS
|
||||
|
||||
#cmakedefine KEEPASSXC_BUILD_TYPE "@KEEPASSXC_BUILD_TYPE@"
|
||||
|
@ -81,7 +81,6 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
|
||||
{Config::GlobalAutoTypeRetypeTime,{QS("GlobalAutoTypeRetypeTime"), Roaming, 15}},
|
||||
{Config::FaviconDownloadTimeout,{QS("FaviconDownloadTimeout"), Roaming, 10}},
|
||||
{Config::UpdateCheckMessageShown,{QS("UpdateCheckMessageShown"), Roaming, false}},
|
||||
{Config::UseTouchID,{QS("UseTouchID"), Roaming, false}},
|
||||
|
||||
{Config::LastDatabases, {QS("LastDatabases"), Local, {}}},
|
||||
{Config::LastKeyFiles, {QS("LastKeyFiles"), Local, {}}},
|
||||
@ -140,11 +139,9 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
|
||||
{Config::Security_HidePasswordPreviewPanel, {QS("Security/HidePasswordPreviewPanel"), Roaming, true}},
|
||||
{Config::Security_AutoTypeAsk, {QS("Security/AutotypeAsk"), Roaming, true}},
|
||||
{Config::Security_IconDownloadFallback, {QS("Security/IconDownloadFallback"), Roaming, false}},
|
||||
{Config::Security_ResetTouchId, {QS("Security/ResetTouchId"), Roaming, false}},
|
||||
{Config::Security_ResetTouchIdTimeout, {QS("Security/ResetTouchIdTimeout"), Roaming, 30}},
|
||||
{Config::Security_ResetTouchIdScreenlock,{QS("Security/ResetTouchIdScreenlock"), Roaming, true}},
|
||||
{Config::Security_NoConfirmMoveEntryToRecycleBin,{QS("Security/NoConfirmMoveEntryToRecycleBin"), Roaming, true}},
|
||||
{Config::Security_EnableCopyOnDoubleClick,{QS("Security/EnableCopyOnDoubleClick"), Roaming, false}},
|
||||
{Config::Security_QuickUnlock, {QS("Security/QuickUnlock"), Local, true}},
|
||||
|
||||
// Browser
|
||||
{Config::Browser_Enabled, {QS("Browser/Enabled"), Roaming, false}},
|
||||
@ -329,9 +326,6 @@ static const QHash<QString, Config::ConfigKey> deprecationMap = {
|
||||
{QS("security/HidePasswordPreviewPanel"), Config::Security_HidePasswordPreviewPanel},
|
||||
{QS("security/passwordsrepeat"), Config::Security_PasswordsRepeatVisible},
|
||||
{QS("security/hidenotes"), Config::Security_HideNotes},
|
||||
{QS("security/resettouchid"), Config::Security_ResetTouchId},
|
||||
{QS("security/resettouchidtimeout"), Config::Security_ResetTouchIdTimeout},
|
||||
{QS("security/resettouchidscreenlock"), Config::Security_ResetTouchIdScreenlock},
|
||||
{QS("KeeShare/Settings.own"), Config::KeeShare_Own},
|
||||
{QS("KeeShare/Settings.foreign"), Config::KeeShare_Foreign},
|
||||
{QS("KeeShare/Settings.active"), Config::KeeShare_Active},
|
||||
@ -369,7 +363,11 @@ static const QHash<QString, Config::ConfigKey> deprecationMap = {
|
||||
{QS("LastAttachmentDir"), Config::Deleted},
|
||||
{QS("KeeShare/LastDir"), Config::Deleted},
|
||||
{QS("KeeShare/LastKeyDir"), Config::Deleted},
|
||||
{QS("KeeShare/LastShareDir"), Config::Deleted}};
|
||||
{QS("KeeShare/LastShareDir"), Config::Deleted},
|
||||
{QS("UseTouchID"), Config::Deleted},
|
||||
{QS("Security/ResetTouchId"), Config::Deleted},
|
||||
{QS("Security/ResetTouchIdTimeout"), Config::Deleted},
|
||||
{QS("Security/ResetTouchIdScreenlock"), Config::Deleted}};
|
||||
|
||||
/**
|
||||
* Migrate settings from previous versions.
|
||||
|
@ -63,7 +63,6 @@ public:
|
||||
GlobalAutoTypeRetypeTime,
|
||||
FaviconDownloadTimeout,
|
||||
UpdateCheckMessageShown,
|
||||
UseTouchID,
|
||||
|
||||
LastDatabases,
|
||||
LastKeyFiles,
|
||||
@ -120,11 +119,9 @@ public:
|
||||
Security_HidePasswordPreviewPanel,
|
||||
Security_AutoTypeAsk,
|
||||
Security_IconDownloadFallback,
|
||||
Security_ResetTouchId,
|
||||
Security_ResetTouchIdTimeout,
|
||||
Security_ResetTouchIdScreenlock,
|
||||
Security_NoConfirmMoveEntryToRecycleBin,
|
||||
Security_EnableCopyOnDoubleClick,
|
||||
Security_QuickUnlock,
|
||||
|
||||
Browser_Enabled,
|
||||
Browser_ShowNotification,
|
||||
|
@ -99,8 +99,8 @@ namespace Tools
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
extensions += "\n- " + QObject::tr("YubiKey");
|
||||
#endif
|
||||
#ifdef WITH_XC_TOUCHID
|
||||
extensions += "\n- " + QObject::tr("TouchID");
|
||||
#if defined(Q_OS_MACOS) || defined(Q_CC_MSVC)
|
||||
extensions += "\n- " + QObject::tr("Quick Unlock");
|
||||
#endif
|
||||
#ifdef WITH_XC_FDOSECRETS
|
||||
extensions += "\n- " + QObject::tr("Secret Service Integration");
|
||||
|
@ -176,6 +176,8 @@ SymmetricCipher::Mode SymmetricCipher::stringToMode(const QString& cipher)
|
||||
return Aes128_CTR;
|
||||
} else if (cipher.compare("aes-256-ctr", cs) == 0 || cipher.compare("aes256-ctr", cs) == 0) {
|
||||
return Aes256_CTR;
|
||||
} else if (cipher.compare("aes-256-gcm", cs) == 0 || cipher.compare("aes256-gcm", cs) == 0) {
|
||||
return Aes256_GCM;
|
||||
} else if (cipher.startsWith("twofish", cs)) {
|
||||
return Twofish_CBC;
|
||||
} else if (cipher.startsWith("salsa", cs)) {
|
||||
@ -198,6 +200,8 @@ QString SymmetricCipher::modeToString(const Mode mode)
|
||||
return QStringLiteral("CTR(AES-128)");
|
||||
case Aes256_CTR:
|
||||
return QStringLiteral("CTR(AES-256)");
|
||||
case Aes256_GCM:
|
||||
return QStringLiteral("AES-256/GCM");
|
||||
case Twofish_CBC:
|
||||
return QStringLiteral("Twofish/CBC");
|
||||
case Salsa20:
|
||||
@ -217,6 +221,7 @@ int SymmetricCipher::defaultIvSize(Mode mode)
|
||||
case Aes256_CBC:
|
||||
case Aes128_CTR:
|
||||
case Aes256_CTR:
|
||||
case Aes256_GCM:
|
||||
case Twofish_CBC:
|
||||
return 16;
|
||||
case Salsa20:
|
||||
@ -235,6 +240,7 @@ int SymmetricCipher::keySize(Mode mode)
|
||||
return 16;
|
||||
case Aes256_CBC:
|
||||
case Aes256_CTR:
|
||||
case Aes256_GCM:
|
||||
case Twofish_CBC:
|
||||
case Salsa20:
|
||||
case ChaCha20:
|
||||
@ -249,6 +255,7 @@ int SymmetricCipher::blockSize(Mode mode)
|
||||
switch (mode) {
|
||||
case Aes128_CBC:
|
||||
case Aes256_CBC:
|
||||
case Aes256_GCM:
|
||||
case Twofish_CBC:
|
||||
return 16;
|
||||
case Aes128_CTR:
|
||||
|
@ -39,6 +39,7 @@ public:
|
||||
Twofish_CBC,
|
||||
ChaCha20,
|
||||
Salsa20,
|
||||
Aes256_GCM,
|
||||
InvalidMode = -1,
|
||||
};
|
||||
|
||||
|
@ -35,6 +35,9 @@
|
||||
#ifdef Q_OS_MACOS
|
||||
#include "touchid/TouchID.h"
|
||||
#endif
|
||||
#ifdef Q_CC_MSVC
|
||||
#include "winhello/WindowsHello.h"
|
||||
#endif
|
||||
|
||||
class ApplicationSettingsWidget::ExtraPage
|
||||
{
|
||||
@ -129,8 +132,6 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
|
||||
m_secUi->clearSearchSpinBox, SLOT(setEnabled(bool)));
|
||||
connect(m_secUi->lockDatabaseIdleCheckBox, SIGNAL(toggled(bool)),
|
||||
m_secUi->lockDatabaseIdleSpinBox, SLOT(setEnabled(bool)));
|
||||
connect(m_secUi->touchIDResetCheckBox, SIGNAL(toggled(bool)),
|
||||
m_secUi->touchIDResetSpinBox, SLOT(setEnabled(bool)));
|
||||
// clang-format on
|
||||
|
||||
// Disable mouse wheel grab when scrolling
|
||||
@ -155,16 +156,14 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
|
||||
m_generalUi->faviconTimeoutSpinBox->setVisible(false);
|
||||
#endif
|
||||
|
||||
#ifndef WITH_XC_TOUCHID
|
||||
bool hideTouchID = true;
|
||||
#else
|
||||
bool hideTouchID = !TouchID::getInstance().isAvailable();
|
||||
bool showQuickUnlock = false;
|
||||
#if defined(Q_OS_MACOS)
|
||||
showQuickUnlock = TouchID::getInstance().isAvailable();
|
||||
#elif defined(Q_CC_MSVC)
|
||||
showQuickUnlock = getWindowsHello()->isAvailable();
|
||||
connect(getWindowsHello(), &WindowsHello::availableChanged, m_secUi->quickUnlockCheckBox, &QCheckBox::setVisible);
|
||||
#endif
|
||||
if (hideTouchID) {
|
||||
m_secUi->touchIDResetCheckBox->setVisible(false);
|
||||
m_secUi->touchIDResetSpinBox->setVisible(false);
|
||||
m_secUi->touchIDResetOnScreenLockCheckBox->setVisible(false);
|
||||
}
|
||||
m_secUi->quickUnlockCheckBox->setVisible(showQuickUnlock);
|
||||
}
|
||||
|
||||
ApplicationSettingsWidget::~ApplicationSettingsWidget()
|
||||
@ -313,10 +312,7 @@ void ApplicationSettingsWidget::loadSettings()
|
||||
m_secUi->EnableCopyOnDoubleClickCheckBox->setChecked(
|
||||
config()->get(Config::Security_EnableCopyOnDoubleClick).toBool());
|
||||
|
||||
m_secUi->touchIDResetCheckBox->setChecked(config()->get(Config::Security_ResetTouchId).toBool());
|
||||
m_secUi->touchIDResetSpinBox->setValue(config()->get(Config::Security_ResetTouchIdTimeout).toInt());
|
||||
m_secUi->touchIDResetOnScreenLockCheckBox->setChecked(
|
||||
config()->get(Config::Security_ResetTouchIdScreenlock).toBool());
|
||||
m_secUi->quickUnlockCheckBox->setChecked(config()->get(Config::Security_QuickUnlock).toBool());
|
||||
|
||||
for (const ExtraPage& page : asConst(m_extraPages)) {
|
||||
page.loadSettings();
|
||||
@ -425,9 +421,7 @@ void ApplicationSettingsWidget::saveSettings()
|
||||
m_secUi->NoConfirmMoveEntryToRecycleBinCheckBox->isChecked());
|
||||
config()->set(Config::Security_EnableCopyOnDoubleClick, m_secUi->EnableCopyOnDoubleClickCheckBox->isChecked());
|
||||
|
||||
config()->set(Config::Security_ResetTouchId, m_secUi->touchIDResetCheckBox->isChecked());
|
||||
config()->set(Config::Security_ResetTouchIdTimeout, m_secUi->touchIDResetSpinBox->value());
|
||||
config()->set(Config::Security_ResetTouchIdScreenlock, m_secUi->touchIDResetOnScreenLockCheckBox->isChecked());
|
||||
config()->set(Config::Security_QuickUnlock, m_secUi->quickUnlockCheckBox->isChecked());
|
||||
|
||||
// Security: clear storage if related settings are disabled
|
||||
if (!config()->get(Config::RememberLastDatabases).toBool()) {
|
||||
|
@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>595</width>
|
||||
<height>567</height>
|
||||
<width>364</width>
|
||||
<height>493</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
@ -28,93 +28,7 @@
|
||||
<property name="title">
|
||||
<string>Timeouts</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout" columnstretch="0,0,1">
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="clearClipboardSpinBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Clipboard clear seconds</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string comment="Seconds"> sec</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>999</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>10</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="touchIDResetCheckBox">
|
||||
<property name="text">
|
||||
<string>Forget TouchID after inactivity of</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="touchIDResetSpinBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Touch ID inactivity reset</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> min</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1440</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>30</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="lockDatabaseIdleCheckBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Lock databases after inactivity of</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<layout class="QGridLayout" name="gridLayout" columnstretch="0,0,0">
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="lockDatabaseIdleSpinBox">
|
||||
<property name="enabled">
|
||||
@ -143,6 +57,20 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="clearClipboardCheckBox">
|
||||
<property name="text">
|
||||
<string>Clear clipboard after</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="clearSearchCheckBox">
|
||||
<property name="text">
|
||||
<string>Clear search query after</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="clearSearchSpinBox">
|
||||
<property name="enabled">
|
||||
@ -171,17 +99,57 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="clearClipboardCheckBox">
|
||||
<property name="text">
|
||||
<string>Clear clipboard after</string>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="clearClipboardSpinBox">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Clipboard clear seconds</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string comment="Seconds"> sec</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>999</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>10</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="clearSearchCheckBox">
|
||||
<item row="0" column="2">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="lockDatabaseIdleCheckBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Clear search query after</string>
|
||||
<string>Lock databases after inactivity of</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -195,16 +163,16 @@
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="lockDatabaseOnScreenLockCheckBox">
|
||||
<widget class="QCheckBox" name="quickUnlockCheckBox">
|
||||
<property name="text">
|
||||
<string>Lock databases when session is locked or lid is closed</string>
|
||||
<string>Enable database quick unlock (Touch ID / Windows Hello)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="touchIDResetOnScreenLockCheckBox">
|
||||
<widget class="QCheckBox" name="lockDatabaseOnScreenLockCheckBox">
|
||||
<property name="text">
|
||||
<string>Forget TouchID when session is locked or lid is closed</string>
|
||||
<string>Lock databases when session is locked or lid is closed</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -308,10 +276,7 @@
|
||||
<tabstop>lockDatabaseIdleSpinBox</tabstop>
|
||||
<tabstop>clearSearchCheckBox</tabstop>
|
||||
<tabstop>clearSearchSpinBox</tabstop>
|
||||
<tabstop>touchIDResetCheckBox</tabstop>
|
||||
<tabstop>touchIDResetSpinBox</tabstop>
|
||||
<tabstop>lockDatabaseOnScreenLockCheckBox</tabstop>
|
||||
<tabstop>touchIDResetOnScreenLockCheckBox</tabstop>
|
||||
<tabstop>lockDatabaseMinimizeCheckBox</tabstop>
|
||||
<tabstop>passwordsRepeatVisibleCheckBox</tabstop>
|
||||
<tabstop>passwordsHiddenCheckBox</tabstop>
|
||||
|
@ -35,7 +35,7 @@ DatabaseOpenDialog::DatabaseOpenDialog(QWidget* parent)
|
||||
, m_tabBar(new QTabBar(this))
|
||||
{
|
||||
setWindowTitle(tr("Unlock Database - KeePassXC"));
|
||||
setWindowFlags(Qt::Dialog | Qt::WindowStaysOnTopHint);
|
||||
setWindowFlags(Qt::Dialog);
|
||||
// block input to the main window/application while the dialog is open
|
||||
setWindowModality(Qt::ApplicationModal);
|
||||
#ifdef Q_OS_WIN
|
||||
|
@ -30,14 +30,43 @@
|
||||
#ifdef Q_OS_MACOS
|
||||
#include "touchid/TouchID.h"
|
||||
#endif
|
||||
#ifdef Q_CC_MSVC
|
||||
#include "winhello/WindowsHello.h"
|
||||
#endif
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDesktopServices>
|
||||
#include <QFont>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr int clearFormsDelay = 30000;
|
||||
}
|
||||
|
||||
bool isQuickUnlockAvailable()
|
||||
{
|
||||
if (config()->get(Config::Security_QuickUnlock).toBool()) {
|
||||
#if defined(Q_CC_MSVC)
|
||||
return getWindowsHello()->isAvailable();
|
||||
#elif defined(Q_OS_MACOS)
|
||||
return TouchID::getInstance().isAvailable();
|
||||
#endif
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool canPerformQuickUnlock(const QString& filename)
|
||||
{
|
||||
if (isQuickUnlockAvailable()) {
|
||||
#if defined(Q_CC_MSVC)
|
||||
return getWindowsHello()->hasKey(filename);
|
||||
#elif defined(Q_OS_MACOS)
|
||||
return TouchID::getInstance().containsKey(filename);
|
||||
#endif
|
||||
}
|
||||
Q_UNUSED(filename);
|
||||
return false;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
|
||||
: DialogyWidget(parent)
|
||||
@ -62,8 +91,16 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
|
||||
m_ui->labelHeadline->setFont(font);
|
||||
m_ui->labelHeadline->setText(tr("Unlock KeePassXC Database"));
|
||||
|
||||
m_ui->quickUnlockButton->setFont(font);
|
||||
m_ui->quickUnlockButton->setIcon(
|
||||
icons()->icon("fingerprint", true, palette().color(QPalette::Active, QPalette::HighlightedText)));
|
||||
m_ui->quickUnlockButton->setIconSize({32, 32});
|
||||
|
||||
connect(m_ui->buttonBrowseFile, SIGNAL(clicked()), SLOT(browseKeyFile()));
|
||||
|
||||
auto okBtn = m_ui->buttonBox->button(QDialogButtonBox::Ok);
|
||||
okBtn->setText(tr("Unlock"));
|
||||
okBtn->setDefault(true);
|
||||
connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(openDatabase()));
|
||||
connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject()));
|
||||
|
||||
@ -98,13 +135,9 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
|
||||
m_ui->hardwareKeyProgress->setVisible(false);
|
||||
#endif
|
||||
|
||||
#ifndef WITH_XC_TOUCHID
|
||||
m_ui->touchIDContainer->setVisible(false);
|
||||
#else
|
||||
if (!TouchID::getInstance().isAvailable()) {
|
||||
m_ui->checkTouchID->setVisible(false);
|
||||
}
|
||||
#endif
|
||||
// QuickUnlock actions
|
||||
connect(m_ui->quickUnlockButton, &QPushButton::pressed, this, [this] { openDatabase(); });
|
||||
connect(m_ui->resetQuickUnlockButton, &QPushButton::pressed, this, [this] { resetQuickUnlock(); });
|
||||
}
|
||||
|
||||
DatabaseOpenWidget::~DatabaseOpenWidget()
|
||||
@ -114,7 +147,14 @@ DatabaseOpenWidget::~DatabaseOpenWidget()
|
||||
void DatabaseOpenWidget::showEvent(QShowEvent* event)
|
||||
{
|
||||
DialogyWidget::showEvent(event);
|
||||
m_ui->editPassword->setFocus();
|
||||
if (isOnQuickUnlockScreen()) {
|
||||
m_ui->quickUnlockButton->setFocus();
|
||||
if (!canPerformQuickUnlock(m_filename)) {
|
||||
resetQuickUnlock();
|
||||
}
|
||||
} else {
|
||||
m_ui->editPassword->setFocus();
|
||||
}
|
||||
m_hideTimer.stop();
|
||||
}
|
||||
|
||||
@ -142,8 +182,12 @@ void DatabaseOpenWidget::load(const QString& filename)
|
||||
}
|
||||
}
|
||||
|
||||
QHash<QString, QVariant> useTouchID = config()->get(Config::UseTouchID).toHash();
|
||||
m_ui->checkTouchID->setChecked(useTouchID.value(m_filename, false).toBool());
|
||||
if (canPerformQuickUnlock(m_filename)) {
|
||||
m_ui->centralStack->setCurrentIndex(1);
|
||||
m_ui->quickUnlockButton->setFocus();
|
||||
} else {
|
||||
m_ui->editPassword->setFocus();
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
// Only auto-poll for hardware keys if we previously used one with this database file
|
||||
@ -158,12 +202,13 @@ void DatabaseOpenWidget::load(const QString& filename)
|
||||
|
||||
void DatabaseOpenWidget::clearForms()
|
||||
{
|
||||
setUserInteractionLock(false);
|
||||
m_ui->editPassword->setText("");
|
||||
m_ui->editPassword->setShowPassword(false);
|
||||
m_ui->keyFileLineEdit->clear();
|
||||
m_ui->keyFileLineEdit->setShowPassword(false);
|
||||
m_ui->checkTouchID->setChecked(false);
|
||||
m_ui->challengeResponseCombo->clear();
|
||||
m_ui->centralStack->setCurrentIndex(0);
|
||||
m_db.reset();
|
||||
}
|
||||
|
||||
@ -181,73 +226,70 @@ void DatabaseOpenWidget::enterKey(const QString& pw, const QString& keyFile)
|
||||
{
|
||||
m_ui->editPassword->setText(pw);
|
||||
m_ui->keyFileLineEdit->setText(keyFile);
|
||||
m_blockQuickUnlock = true;
|
||||
openDatabase();
|
||||
}
|
||||
|
||||
void DatabaseOpenWidget::openDatabase()
|
||||
{
|
||||
m_ui->messageWidget->hide();
|
||||
// Cache this variable for future use then reset
|
||||
bool blockQuickUnlock = m_blockQuickUnlock || isOnQuickUnlockScreen();
|
||||
m_blockQuickUnlock = false;
|
||||
|
||||
QSharedPointer<CompositeKey> databaseKey = buildDatabaseKey();
|
||||
setUserInteractionLock(true);
|
||||
m_ui->messageWidget->hide();
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
const auto databaseKey = buildDatabaseKey();
|
||||
if (!databaseKey) {
|
||||
setUserInteractionLock(false);
|
||||
return;
|
||||
}
|
||||
|
||||
m_ui->editPassword->setShowPassword(false);
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
m_db.reset(new Database());
|
||||
QString error;
|
||||
|
||||
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
|
||||
m_ui->passwordFormFrame->setEnabled(false);
|
||||
QCoreApplication::processEvents();
|
||||
m_db.reset(new Database());
|
||||
bool ok = m_db->open(m_filename, databaseKey, &error);
|
||||
QApplication::restoreOverrideCursor();
|
||||
m_ui->passwordFormFrame->setEnabled(true);
|
||||
|
||||
if (ok && m_db->hasMinorVersionMismatch()) {
|
||||
QScopedPointer<QMessageBox> msgBox(new QMessageBox(this));
|
||||
msgBox->setIcon(QMessageBox::Warning);
|
||||
msgBox->setWindowTitle(tr("Database Version Mismatch"));
|
||||
msgBox->setText(tr("The database you are trying to open was most likely\n"
|
||||
"created by a newer version of KeePassXC.\n\n"
|
||||
"You can try to open it anyway, but it may be incomplete\n"
|
||||
"and saving any changes may incur data loss.\n\n"
|
||||
"We recommend you update your KeePassXC installation."));
|
||||
auto btn = msgBox->addButton(tr("Open database anyway"), QMessageBox::ButtonRole::AcceptRole);
|
||||
msgBox->setDefaultButton(btn);
|
||||
msgBox->addButton(QMessageBox::Cancel);
|
||||
msgBox->exec();
|
||||
if (msgBox->clickedButton() != btn) {
|
||||
m_db.reset(new Database());
|
||||
m_ui->messageWidget->showMessage(tr("Database unlock canceled."), MessageWidget::MessageType::Error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
#ifdef WITH_XC_TOUCHID
|
||||
QHash<QString, QVariant> useTouchID = config()->get(Config::UseTouchID).toHash();
|
||||
|
||||
// check if TouchID can & should be used to unlock the database next time
|
||||
if (m_ui->checkTouchID->isChecked() && TouchID::getInstance().isAvailable()) {
|
||||
// encrypt and store key blob
|
||||
if (TouchID::getInstance().storeKey(m_filename, PasswordKey(m_ui->editPassword->text()).rawKey())) {
|
||||
useTouchID.insert(m_filename, true);
|
||||
// Warn user about minor version mismatch to halt loading if necessary
|
||||
if (m_db->hasMinorVersionMismatch()) {
|
||||
QScopedPointer<QMessageBox> msgBox(new QMessageBox(this));
|
||||
msgBox->setIcon(QMessageBox::Warning);
|
||||
msgBox->setWindowTitle(tr("Database Version Mismatch"));
|
||||
msgBox->setText(tr("The database you are trying to open was most likely\n"
|
||||
"created by a newer version of KeePassXC.\n\n"
|
||||
"You can try to open it anyway, but it may be incomplete\n"
|
||||
"and saving any changes may incur data loss.\n\n"
|
||||
"We recommend you update your KeePassXC installation."));
|
||||
auto btn = msgBox->addButton(tr("Open database anyway"), QMessageBox::ButtonRole::AcceptRole);
|
||||
msgBox->setDefaultButton(btn);
|
||||
msgBox->addButton(QMessageBox::Cancel);
|
||||
msgBox->exec();
|
||||
if (msgBox->clickedButton() != btn) {
|
||||
m_db.reset(new Database());
|
||||
m_ui->messageWidget->showMessage(tr("Database unlock canceled."), MessageWidget::MessageType::Error);
|
||||
setUserInteractionLock(false);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// when TouchID not available or unchecked, reset for the current database
|
||||
TouchID::getInstance().reset(m_filename);
|
||||
useTouchID.insert(m_filename, false);
|
||||
}
|
||||
|
||||
config()->set(Config::UseTouchID, useTouchID);
|
||||
// Save Quick Unlock credentials if available
|
||||
if (!blockQuickUnlock && isQuickUnlockAvailable()) {
|
||||
auto keyData = databaseKey->serialize();
|
||||
#if defined(Q_CC_MSVC)
|
||||
// Store the password using Windows Hello
|
||||
getWindowsHello()->storeKey(m_filename, keyData);
|
||||
#elif defined(Q_OS_MACOS)
|
||||
// Store the password using TouchID
|
||||
TouchID::getInstance().storeKey(m_filename, keyData);
|
||||
#endif
|
||||
m_ui->messageWidget->hideMessage();
|
||||
}
|
||||
|
||||
emit dialogFinished(true);
|
||||
clearForms();
|
||||
} else {
|
||||
if (m_ui->editPassword->text().isEmpty() && !m_retryUnlockWithEmptyPassword) {
|
||||
if (!isOnQuickUnlockScreen() && m_ui->editPassword->text().isEmpty() && !m_retryUnlockWithEmptyPassword) {
|
||||
QScopedPointer<QMessageBox> msgBox(new QMessageBox(this));
|
||||
msgBox->setIcon(QMessageBox::Critical);
|
||||
msgBox->setWindowTitle(tr("Unlock failed and no password given"));
|
||||
@ -262,21 +304,24 @@ void DatabaseOpenWidget::openDatabase()
|
||||
|
||||
if (msgBox->clickedButton() == btn) {
|
||||
m_retryUnlockWithEmptyPassword = true;
|
||||
setUserInteractionLock(false);
|
||||
openDatabase();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setUserInteractionLock(false);
|
||||
|
||||
// Reset quick unlock for the current database
|
||||
if (isOnQuickUnlockScreen()) {
|
||||
resetQuickUnlock();
|
||||
}
|
||||
|
||||
m_retryUnlockWithEmptyPassword = false;
|
||||
m_ui->messageWidget->showMessage(error, MessageWidget::MessageType::Error);
|
||||
// Focus on the password field and select the input for easy retry
|
||||
m_ui->editPassword->selectAll();
|
||||
m_ui->editPassword->setFocus();
|
||||
|
||||
#ifdef WITH_XC_TOUCHID
|
||||
// unable to unlock database, reset TouchID for the current database
|
||||
TouchID::getInstance().reset(m_filename);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ -284,30 +329,30 @@ QSharedPointer<CompositeKey> DatabaseOpenWidget::buildDatabaseKey()
|
||||
{
|
||||
auto databaseKey = QSharedPointer<CompositeKey>::create();
|
||||
|
||||
if (canPerformQuickUnlock(m_filename)) {
|
||||
// try to retrieve the stored password using Windows Hello
|
||||
QByteArray keyData;
|
||||
#ifdef Q_CC_MSVC
|
||||
if (!getWindowsHello()->getKey(m_filename, keyData)) {
|
||||
// Failed to retrieve Quick Unlock data
|
||||
m_ui->messageWidget->showMessage(tr("Failed to authenticate with Windows Hello"), MessageWidget::Error);
|
||||
return {};
|
||||
}
|
||||
#elif defined(Q_OS_MACOS)
|
||||
if (!TouchID::getInstance().getKey(m_filename, keyData)) {
|
||||
// Failed to retrieve Quick Unlock data
|
||||
m_ui->messageWidget->showMessage(tr("Failed to authenticate with Touch ID"), MessageWidget::Error);
|
||||
return {};
|
||||
}
|
||||
#endif
|
||||
databaseKey->setRawKey(keyData);
|
||||
return databaseKey;
|
||||
}
|
||||
|
||||
if (!m_ui->editPassword->text().isEmpty() || m_retryUnlockWithEmptyPassword) {
|
||||
databaseKey->addKey(QSharedPointer<PasswordKey>::create(m_ui->editPassword->text()));
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_TOUCHID
|
||||
// check if TouchID is available and enabled for unlocking the database
|
||||
if (m_ui->checkTouchID->isChecked() && TouchID::getInstance().isAvailable()
|
||||
&& m_ui->editPassword->text().isEmpty()) {
|
||||
// clear empty password from composite key
|
||||
databaseKey->clear();
|
||||
|
||||
// try to get, decrypt and use PasswordKey
|
||||
QByteArray passwordKey;
|
||||
if (TouchID::getInstance().getKey(m_filename, passwordKey)) {
|
||||
// check if the user cancelled the operation
|
||||
if (passwordKey.isNull()) {
|
||||
return QSharedPointer<CompositeKey>();
|
||||
}
|
||||
|
||||
databaseKey->addKey(PasswordKey::fromRawKey(passwordKey));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
auto lastKeyFiles = config()->get(Config::LastKeyFiles).toHash();
|
||||
lastKeyFiles.remove(m_filename);
|
||||
|
||||
@ -465,3 +510,32 @@ void DatabaseOpenWidget::openKeyFileHelp()
|
||||
{
|
||||
QDesktopServices::openUrl(QUrl("https://keepassxc.org/docs#faq-cat-keyfile"));
|
||||
}
|
||||
|
||||
void DatabaseOpenWidget::setUserInteractionLock(bool state)
|
||||
{
|
||||
if (state) {
|
||||
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
|
||||
m_ui->centralStack->setEnabled(false);
|
||||
} else {
|
||||
// Ensure no override cursors remain
|
||||
while (QApplication::overrideCursor()) {
|
||||
QApplication::restoreOverrideCursor();
|
||||
}
|
||||
m_ui->centralStack->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
bool DatabaseOpenWidget::isOnQuickUnlockScreen()
|
||||
{
|
||||
return m_ui->centralStack->currentIndex() == 1;
|
||||
}
|
||||
|
||||
void DatabaseOpenWidget::resetQuickUnlock()
|
||||
{
|
||||
#if defined(Q_CC_MSVC)
|
||||
getWindowsHello()->reset(m_filename);
|
||||
#elif defined(Q_OS_MACOS)
|
||||
TouchID::getInstance().reset(m_filename);
|
||||
#endif
|
||||
load(m_filename);
|
||||
}
|
||||
|
@ -53,6 +53,10 @@ protected:
|
||||
void showEvent(QShowEvent* event) override;
|
||||
void hideEvent(QHideEvent* event) override;
|
||||
QSharedPointer<CompositeKey> buildDatabaseKey();
|
||||
void setUserInteractionLock(bool state);
|
||||
// Quick Unlock helper functions
|
||||
bool isOnQuickUnlockScreen();
|
||||
void resetQuickUnlock();
|
||||
|
||||
const QScopedPointer<Ui::DatabaseOpenWidget> m_ui;
|
||||
QSharedPointer<Database> m_db;
|
||||
@ -73,6 +77,7 @@ private slots:
|
||||
|
||||
private:
|
||||
bool m_pollingHardwareKey = false;
|
||||
bool m_blockQuickUnlock = false;
|
||||
QTimer m_hideTimer;
|
||||
|
||||
Q_DISABLE_COPY(DatabaseOpenWidget)
|
||||
|
@ -6,41 +6,22 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>588</width>
|
||||
<height>448</height>
|
||||
<width>520</width>
|
||||
<height>436</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Unlock KeePassXC Database</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0,2">
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0">
|
||||
<item>
|
||||
<widget class="MessageWidget" name="messageWidget" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>5</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="0,1,0">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetDefaultConstraint</enum>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
@ -59,16 +40,32 @@
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="formContainer" native="true">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>500</width>
|
||||
<height>400</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>700</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetMinimumSize</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4" stretch="1,0,0,0,0,2">
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelHeadline">
|
||||
<property name="font">
|
||||
@ -107,142 +104,223 @@
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="loginFrame">
|
||||
<widget class="QStackedWidget" name="centralStack">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>550</width>
|
||||
<height>0</height>
|
||||
<width>0</width>
|
||||
<height>250</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>2</number>
|
||||
<number>1</number>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetMinimumSize</enum>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QFrame" name="passwordFormFrame">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>700</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="mainPage">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<property name="leftMargin">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Enter Password:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>editPassword</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="PasswordEdit" name="editPassword">
|
||||
<property name="accessibleName">
|
||||
<string>Password field</string>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>5</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Enter Additional Credentials (if any):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Enter Password:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>editPassword</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="PasswordEdit" name="editPassword">
|
||||
<property name="accessibleName">
|
||||
<string>Password field</string>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_4">
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>5</height>
|
||||
<width>15</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Enter Additional Credentials (if any):</string>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<property name="topMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetMinimumSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<item row="0" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<property name="spacing">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>15</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
<item>
|
||||
<widget class="QLabel" name="keyFileLabel">
|
||||
<property name="text">
|
||||
<string>Key File:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>keyFileLineEdit</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="keyFileLabelHelp">
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::ClickFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><p>In addition to a password, you can use a secret file to enhance the security of your database. This file can be generated in your database's security settings.</p><p>This is <strong>not</strong> your *.kdbx database file!<br>If you do not have a key file, leave this field empty.</p><p>Click for more information…</p></string>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Key file help</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QToolButton {
|
||||
border: none;
|
||||
background: none;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">?</string>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>12</width>
|
||||
<height>12</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::InstantPopup</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetMinimumSize</enum>
|
||||
<item row="1" column="3">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>3</number>
|
||||
<item row="1" column="2">
|
||||
<widget class="QProgressBar" name="hardwareKeyProgress">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>2</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
<property name="textVisible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QComboBox" name="challengeResponseCombo">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Hardware key slot selection</string>
|
||||
</property>
|
||||
<property name="editable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<property name="spacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<property name="spacing">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="keyFileLabel">
|
||||
<widget class="QLabel" name="hardwareKeyLabel">
|
||||
<property name="text">
|
||||
<string>Key File:</string>
|
||||
<string>Hardware Key:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>keyFileLineEdit</cstring>
|
||||
<cstring>challengeResponseCombo</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="keyFileLabelHelp">
|
||||
<widget class="QToolButton" name="hardwareKeyLabelHelp">
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
@ -250,10 +328,11 @@
|
||||
<enum>Qt::ClickFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><p>In addition to a password, you can use a secret file to enhance the security of your database. This file can be generated in your database's security settings.</p><p>This is <strong>not</strong> your *.kdbx database file!<br>If you do not have a key file, leave this field empty.</p><p>Click for more information…</p></string>
|
||||
<string><p>You can use a hardware security key such as a <strong>YubiKey</strong> or <strong>OnlyKey</strong> with slots configured for HMAC-SHA1.</p>
|
||||
<p>Click for more information…</p></string>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Key file help</string>
|
||||
<string>Hardware key help</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QToolButton {
|
||||
@ -262,7 +341,7 @@
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>?</string>
|
||||
<string notr="true">?</string>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
@ -277,272 +356,242 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<item row="1" column="2">
|
||||
<widget class="QProgressBar" name="hardwareKeyProgress">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>2</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
<property name="textVisible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QComboBox" name="challengeResponseCombo">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Hardware key slot selection</string>
|
||||
</property>
|
||||
<property name="editable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<property name="spacing">
|
||||
<number>2</number>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<property name="spacing">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="hardwareKeyLabel">
|
||||
<property name="text">
|
||||
<string>Hardware Key:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>challengeResponseCombo</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="hardwareKeyLabelHelp">
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::ClickFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><p>You can use a hardware security key such as a <strong>YubiKey</strong> or <strong>OnlyKey</strong> with slots configured for HMAC-SHA1.</p>
|
||||
<p>Click for more information…</p></string>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Hardware key help</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QToolButton {
|
||||
border: none;
|
||||
background: none;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">?</string>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>12</width>
|
||||
<height>12</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::InstantPopup</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>2</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<property name="verticalSpacing">
|
||||
<number>0</number>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>2</height>
|
||||
</size>
|
||||
</property>
|
||||
<item row="0" column="1">
|
||||
<widget class="PasswordEdit" name="keyFileLineEdit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Key file to unlock the database</string>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="4">
|
||||
<widget class="QPushButton" name="buttonBrowseFile">
|
||||
<property name="toolTip">
|
||||
<string>Browse for key file</string>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<property name="verticalSpacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="1">
|
||||
<widget class="PasswordEdit" name="keyFileLineEdit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Browse for key file</string>
|
||||
<string>Key file to unlock the database</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Browse…</string>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
<property name="clearButtonEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="4">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="4">
|
||||
<widget class="QPushButton" name="buttonBrowseFile">
|
||||
<property name="toolTip">
|
||||
<string>Browse for key file</string>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Browse for key file</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Browse…</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="4">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonRedetectYubikey">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonRedetectYubikey">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Refresh hardware tokens</string>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Refresh hardware tokens</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Refresh</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>2</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
<property name="toolTip">
|
||||
<string>Refresh hardware tokens</string>
|
||||
</property>
|
||||
<property name="accessibleName">
|
||||
<string>Refresh hardware tokens</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Refresh</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>2</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="dialogButtonsLayout">
|
||||
<property name="topMargin">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<item alignment="Qt::AlignRight">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Close|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="quickUnlockPage">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QWidget" name="touchIDContainer" native="true">
|
||||
<layout class="QHBoxLayout" name="touchIDLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkTouchID">
|
||||
<property name="text">
|
||||
<string>TouchID for Quick Unlock</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<spacer name="verticalSpacer_7">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="quickUnlockButton">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>60</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>10</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Unlock Database</string>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="dialogButtonsLayout">
|
||||
<property name="topMargin">
|
||||
<number>15</number>
|
||||
<widget class="QPushButton" name="resetQuickUnlockButton">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
<item alignment="Qt::AlignRight">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_8">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>55</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -564,22 +613,6 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>55</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
@ -607,7 +640,8 @@
|
||||
<tabstop>buttonBrowseFile</tabstop>
|
||||
<tabstop>challengeResponseCombo</tabstop>
|
||||
<tabstop>buttonRedetectYubikey</tabstop>
|
||||
<tabstop>checkTouchID</tabstop>
|
||||
<tabstop>quickUnlockButton</tabstop>
|
||||
<tabstop>resetQuickUnlockButton</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
@ -36,13 +36,14 @@
|
||||
class AdaptiveIconEngine : public QIconEngine
|
||||
{
|
||||
public:
|
||||
explicit AdaptiveIconEngine(QIcon baseIcon);
|
||||
explicit AdaptiveIconEngine(QIcon baseIcon, QColor overrideColor = {});
|
||||
void paint(QPainter* painter, const QRect& rect, QIcon::Mode mode, QIcon::State state) override;
|
||||
QPixmap pixmap(const QSize& size, QIcon::Mode mode, QIcon::State state) override;
|
||||
QIconEngine* clone() const override;
|
||||
|
||||
private:
|
||||
QIcon m_baseIcon;
|
||||
QColor m_overrideColor;
|
||||
};
|
||||
|
||||
Icons* Icons::m_instance(nullptr);
|
||||
@ -113,9 +114,10 @@ QIcon Icons::trayIconUnlocked()
|
||||
return trayIcon("unlocked");
|
||||
}
|
||||
|
||||
AdaptiveIconEngine::AdaptiveIconEngine(QIcon baseIcon)
|
||||
AdaptiveIconEngine::AdaptiveIconEngine(QIcon baseIcon, QColor overrideColor)
|
||||
: QIconEngine()
|
||||
, m_baseIcon(std::move(baseIcon))
|
||||
, m_overrideColor(overrideColor)
|
||||
{
|
||||
}
|
||||
|
||||
@ -133,7 +135,10 @@ void AdaptiveIconEngine::paint(QPainter* painter, const QRect& rect, QIcon::Mode
|
||||
|
||||
m_baseIcon.paint(&p, img.rect(), Qt::AlignCenter, mode, state);
|
||||
|
||||
if (getMainWindow()) {
|
||||
if (m_overrideColor.isValid()) {
|
||||
p.setCompositionMode(QPainter::CompositionMode_SourceIn);
|
||||
p.fillRect(img.rect(), m_overrideColor);
|
||||
} else if (getMainWindow()) {
|
||||
QPalette palette = getMainWindow()->palette();
|
||||
p.setCompositionMode(QPainter::CompositionMode_SourceIn);
|
||||
|
||||
@ -188,7 +193,7 @@ QIcon Icons::icon(const QString& name, bool recolor, const QColor& overrideColor
|
||||
|
||||
icon = QIcon::fromTheme(name);
|
||||
if (recolor) {
|
||||
icon = QIcon(new AdaptiveIconEngine(icon));
|
||||
icon = QIcon(new AdaptiveIconEngine(icon, overrideColor));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
|
||||
icon.setIsMask(true);
|
||||
#endif
|
||||
|
@ -44,12 +44,6 @@
|
||||
#include "gui/SearchWidget.h"
|
||||
#include "gui/osutils/OSUtils.h"
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
#ifdef WITH_XC_TOUCHID
|
||||
#include "touchid/TouchID.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef WITH_XC_UPDATECHECK
|
||||
#include "gui/UpdateCheckDialog.h"
|
||||
#include "updatecheck/UpdateChecker.h"
|
||||
@ -259,10 +253,6 @@ MainWindow::MainWindow()
|
||||
|
||||
m_inactivityTimer = new InactivityTimer(this);
|
||||
connect(m_inactivityTimer, SIGNAL(inactivityDetected()), this, SLOT(lockDatabasesAfterInactivity()));
|
||||
#ifdef WITH_XC_TOUCHID
|
||||
m_touchIDinactivityTimer = new InactivityTimer(this);
|
||||
connect(m_touchIDinactivityTimer, SIGNAL(inactivityDetected()), this, SLOT(forgetTouchIDAfterInactivity()));
|
||||
#endif
|
||||
applySettingsChanges();
|
||||
|
||||
m_ui->actionDatabaseNew->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_N);
|
||||
@ -1537,21 +1527,6 @@ void MainWindow::applySettingsChanges()
|
||||
m_inactivityTimer->deactivate();
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_TOUCHID
|
||||
if (config()->get(Config::Security_ResetTouchId).toBool()) {
|
||||
// Calculate TouchID timeout in milliseconds
|
||||
timeout = config()->get(Config::Security_ResetTouchIdTimeout).toInt() * 60 * 1000;
|
||||
if (timeout <= 0) {
|
||||
timeout = 30 * 60 * 1000;
|
||||
}
|
||||
|
||||
m_touchIDinactivityTimer->setInactivityTimeout(timeout);
|
||||
m_touchIDinactivityTimer->activate();
|
||||
} else {
|
||||
m_touchIDinactivityTimer->deactivate();
|
||||
}
|
||||
#endif
|
||||
|
||||
m_ui->toolBar->setHidden(config()->get(Config::GUI_HideToolbar).toBool());
|
||||
m_ui->toolBar->setMovable(config()->get(Config::GUI_MovableToolbar).toBool());
|
||||
|
||||
@ -1715,13 +1690,6 @@ void MainWindow::lockDatabasesAfterInactivity()
|
||||
m_ui->tabWidget->lockDatabases();
|
||||
}
|
||||
|
||||
void MainWindow::forgetTouchIDAfterInactivity()
|
||||
{
|
||||
#ifdef WITH_XC_TOUCHID
|
||||
TouchID::getInstance().reset();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool MainWindow::isTrayIconEnabled() const
|
||||
{
|
||||
return m_trayIcon && m_trayIcon->isVisible();
|
||||
@ -1778,12 +1746,6 @@ void MainWindow::handleScreenLock()
|
||||
if (config()->get(Config::Security_LockDatabaseScreenLock).toBool()) {
|
||||
lockDatabasesAfterInactivity();
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_TOUCHID
|
||||
if (config()->get(Config::Security_ResetTouchIdScreenlock).toBool()) {
|
||||
forgetTouchIDAfterInactivity();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
QStringList MainWindow::kdbxFilesFromUrls(const QList<QUrl>& urls)
|
||||
|
@ -134,7 +134,6 @@ private slots:
|
||||
void trayIconTriggered(QSystemTrayIcon::ActivationReason reason);
|
||||
void processTrayIconTrigger();
|
||||
void lockDatabasesAfterInactivity();
|
||||
void forgetTouchIDAfterInactivity();
|
||||
void handleScreenLock();
|
||||
void showErrorMessage(const QString& message);
|
||||
void selectNextDatabaseTab();
|
||||
|
@ -16,7 +16,7 @@
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>800</width>
|
||||
<height>400</height>
|
||||
<height>500</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -1091,18 +1091,18 @@
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>PasswordGeneratorWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/PasswordGeneratorWidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>MessageWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/MessageWidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>PasswordGeneratorWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/PasswordGeneratorWidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>DatabaseTabWidget</class>
|
||||
<extends>QTabWidget</extends>
|
||||
|
@ -32,9 +32,6 @@
|
||||
#ifdef WITH_XC_FDOSECRETS
|
||||
#include "fdosecrets/DatabaseSettingsPageFdoSecrets.h"
|
||||
#endif
|
||||
#ifdef Q_OS_MACOS
|
||||
#include "touchid/TouchID.h"
|
||||
#endif
|
||||
|
||||
#include "core/Config.h"
|
||||
#include "core/Database.h"
|
||||
@ -184,10 +181,6 @@ void DatabaseSettingsDialog::save()
|
||||
extraPage.saveSettings();
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_TOUCHID
|
||||
TouchID::getInstance().reset(m_db ? m_db->filePath() : "");
|
||||
#endif
|
||||
|
||||
emit editFinished(true);
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,13 @@
|
||||
#include "keys/FileKey.h"
|
||||
#include "keys/PasswordKey.h"
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
#include "touchid/TouchID.h"
|
||||
#endif
|
||||
#ifdef Q_CC_MSVC
|
||||
#include "winhello/WindowsHello.h"
|
||||
#endif
|
||||
|
||||
#include <QLayout>
|
||||
#include <QPushButton>
|
||||
|
||||
@ -193,6 +200,12 @@ bool DatabaseSettingsWidgetDatabaseKey::save()
|
||||
|
||||
m_db->setKey(newKey, true, false, false);
|
||||
|
||||
#if defined(Q_OS_MACOS)
|
||||
TouchID::getInstance().reset(m_db->filePath());
|
||||
#elif defined(Q_CC_MSVC)
|
||||
getWindowsHello()->reset(m_db->filePath());
|
||||
#endif
|
||||
|
||||
emit editFinished(true);
|
||||
if (m_isDirty) {
|
||||
m_db->markAsModified();
|
||||
|
@ -121,14 +121,6 @@ void ReportsDialog::addPage(QSharedPointer<IReportsPage> page)
|
||||
|
||||
void ReportsDialog::reject()
|
||||
{
|
||||
for (const ExtraPage& extraPage : asConst(m_extraPages)) {
|
||||
extraPage.saveSettings();
|
||||
}
|
||||
|
||||
#ifdef WITH_XC_TOUCHID
|
||||
TouchID::getInstance().reset(m_db ? m_db->filePath() : "");
|
||||
#endif
|
||||
|
||||
emit editFinished(true);
|
||||
}
|
||||
|
||||
|
@ -37,8 +37,8 @@ EntryPreviewWidget TagsEdit
|
||||
border: none;
|
||||
}
|
||||
|
||||
DatabaseOpenWidget #loginFrame {
|
||||
border: 2px groove palette(mid);
|
||||
DatabaseOpenWidget #centralStack {
|
||||
border: 1px solid palette(mid);
|
||||
background: palette(light);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
DatabaseOpenWidget #loginFrame {
|
||||
DatabaseOpenWidget #centralStack {
|
||||
border: 2px groove palette(mid);
|
||||
background: palette(light);
|
||||
}
|
||||
|
@ -209,7 +209,6 @@ QByteArray CompositeKey::serialize() const
|
||||
for (auto const& key : m_challengeResponseKeys) {
|
||||
stream << key->uuid().toRfc4122() << key->serialize();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,8 @@ public:
|
||||
|
||||
bool getKey(const QString& databasePath, QByteArray& passwordKey) const;
|
||||
|
||||
bool containsKey(const QString& databasePath) const;
|
||||
|
||||
bool isAvailable();
|
||||
|
||||
bool authenticate(const QString& message = "") const;
|
||||
|
@ -57,11 +57,11 @@ bool TouchID::storeKey(const QString& databasePath, const QByteArray& passwordKe
|
||||
}
|
||||
|
||||
// generate random AES 256bit key and IV
|
||||
QByteArray randomKey = randomGen()->randomArray(32);
|
||||
QByteArray randomIV = randomGen()->randomArray(16);
|
||||
QByteArray randomKey = randomGen()->randomArray(SymmetricCipher::keySize(SymmetricCipher::Aes256_GCM));
|
||||
QByteArray randomIV = randomGen()->randomArray(SymmetricCipher::defaultIvSize(SymmetricCipher::Aes256_GCM));
|
||||
|
||||
SymmetricCipher aes256Encrypt;
|
||||
if (!aes256Encrypt.init(SymmetricCipher::Aes256_CBC, SymmetricCipher::Encrypt, randomKey, randomIV)) {
|
||||
if (!aes256Encrypt.init(SymmetricCipher::Aes256_GCM, SymmetricCipher::Encrypt, randomKey, randomIV)) {
|
||||
debug("TouchID::storeKey - Error initializing encryption: %s",
|
||||
aes256Encrypt.errorString().toUtf8().constData());
|
||||
return false;
|
||||
@ -69,8 +69,9 @@ bool TouchID::storeKey(const QString& databasePath, const QByteArray& passwordKe
|
||||
|
||||
// encrypt and keep result in memory
|
||||
QByteArray encryptedMasterKey = passwordKey;
|
||||
if (!aes256Encrypt.process(encryptedMasterKey)) {
|
||||
if (!aes256Encrypt.finish(encryptedMasterKey)) {
|
||||
debug("TouchID::storeKey - Error encrypting: %s", aes256Encrypt.errorString().toUtf8().constData());
|
||||
debug(aes256Encrypt.errorString().toUtf8().constData());
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -166,7 +167,7 @@ bool TouchID::getKey(const QString& databasePath, QByteArray& passwordKey) const
|
||||
}
|
||||
|
||||
// checks if encrypted PasswordKey is available and is stored for the given database
|
||||
if (!this->m_encryptedMasterKeys.contains(databasePath)) {
|
||||
if (!containsKey(databasePath)) {
|
||||
debug("TouchID::getKey - No stored key found");
|
||||
return false;
|
||||
}
|
||||
@ -205,18 +206,19 @@ bool TouchID::getKey(const QString& databasePath, QByteArray& passwordKey) const
|
||||
CFRelease(valueData);
|
||||
|
||||
// extract AES key and IV from data bytes
|
||||
QByteArray key = dataBytes.left(32);
|
||||
QByteArray iv = dataBytes.right(16);
|
||||
QByteArray key = dataBytes.left(SymmetricCipher::keySize(SymmetricCipher::Aes256_GCM));
|
||||
QByteArray iv = dataBytes.right(SymmetricCipher::defaultIvSize(SymmetricCipher::Aes256_GCM));
|
||||
|
||||
SymmetricCipher aes256Decrypt;
|
||||
if (!aes256Decrypt.init(SymmetricCipher::Aes256_CBC, SymmetricCipher::Decrypt, key, iv)) {
|
||||
if (!aes256Decrypt.init(SymmetricCipher::Aes256_GCM, SymmetricCipher::Decrypt, key, iv)) {
|
||||
debug("TouchID::getKey - Error initializing decryption: %s", aes256Decrypt.errorString().toUtf8().constData());
|
||||
return false;
|
||||
}
|
||||
|
||||
// decrypt PasswordKey from memory using AES
|
||||
passwordKey = m_encryptedMasterKeys[databasePath];
|
||||
if (!aes256Decrypt.process(passwordKey)) {
|
||||
if (!aes256Decrypt.finish(passwordKey)) {
|
||||
passwordKey.clear();
|
||||
debug("TouchID::getKey - Error decryption: %s", aes256Decrypt.errorString().toUtf8().constData());
|
||||
return false;
|
||||
}
|
||||
@ -224,6 +226,11 @@ bool TouchID::getKey(const QString& databasePath, QByteArray& passwordKey) const
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TouchID::containsKey(const QString& dbPath) const
|
||||
{
|
||||
return m_encryptedMasterKeys.contains(dbPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic check if TouchID is available on the current machine.
|
||||
*/
|
||||
|
202
src/winhello/WindowsHello.cpp
Normal file
202
src/winhello/WindowsHello.cpp
Normal file
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright (C) 2022 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "WindowsHello.h"
|
||||
|
||||
#include <Userconsentverifierinterop.h>
|
||||
#include <winrt/base.h>
|
||||
#include <winrt/windows.foundation.h>
|
||||
#include <winrt/windows.security.credentials.h>
|
||||
#include <winrt/windows.security.cryptography.h>
|
||||
#include <winrt/windows.storage.streams.h>
|
||||
|
||||
#include "core/AsyncTask.h"
|
||||
#include "crypto/CryptoHash.h"
|
||||
#include "crypto/Random.h"
|
||||
#include "crypto/SymmetricCipher.h"
|
||||
|
||||
#include <QTimer>
|
||||
#include <QWindow>
|
||||
|
||||
using namespace winrt;
|
||||
using namespace Windows::Foundation;
|
||||
using namespace Windows::Security::Credentials;
|
||||
using namespace Windows::Security::Cryptography;
|
||||
using namespace Windows::Storage::Streams;
|
||||
|
||||
namespace
|
||||
{
|
||||
const std::wstring s_winHelloKeyName{L"keepassxc_winhello"};
|
||||
|
||||
void queueSecurityPromptFocus(int delay = 500)
|
||||
{
|
||||
QTimer::singleShot(delay, [] {
|
||||
auto hWnd = ::FindWindowA("Credential Dialog Xaml Host", nullptr);
|
||||
if (hWnd) {
|
||||
::SetForegroundWindow(hWnd);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool deriveEncryptionKey(QByteArray& challenge, QByteArray& key, QString& error)
|
||||
{
|
||||
error.clear();
|
||||
auto challengeBuffer = CryptographicBuffer::CreateFromByteArray(
|
||||
array_view<uint8_t>(reinterpret_cast<uint8_t*>(challenge.data()), challenge.size()));
|
||||
|
||||
return AsyncTask::runAndWaitForFuture([&] {
|
||||
// The first time this is used a key-pair will be generated using the common name
|
||||
auto result =
|
||||
KeyCredentialManager::RequestCreateAsync(s_winHelloKeyName, KeyCredentialCreationOption::FailIfExists)
|
||||
.get();
|
||||
|
||||
if (result.Status() == KeyCredentialStatus::CredentialAlreadyExists) {
|
||||
result = KeyCredentialManager::OpenAsync(s_winHelloKeyName).get();
|
||||
} else if (result.Status() != KeyCredentialStatus::Success) {
|
||||
error = QObject::tr("Failed to create Windows Hello credential.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto signature = result.Credential().RequestSignAsync(challengeBuffer).get();
|
||||
if (signature.Status() != KeyCredentialStatus::Success) {
|
||||
error = QObject::tr("Failed to sign challenge using Windows Hello.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use the SHA-256 hash of the challenge signature as the encryption key
|
||||
const auto response = signature.Result();
|
||||
CryptoHash hasher(CryptoHash::Sha256);
|
||||
hasher.addData({reinterpret_cast<const char*>(response.data()), static_cast<int>(response.Length())});
|
||||
key = hasher.result();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
} // namespace
|
||||
|
||||
WindowsHello* WindowsHello::m_instance{nullptr};
|
||||
WindowsHello* WindowsHello::instance()
|
||||
{
|
||||
if (!m_instance) {
|
||||
m_instance = new WindowsHello();
|
||||
}
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
WindowsHello::WindowsHello(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
concurrency::create_task([this] {
|
||||
bool state = KeyCredentialManager::IsSupportedAsync().get();
|
||||
m_available = state;
|
||||
emit availableChanged(m_available);
|
||||
});
|
||||
}
|
||||
|
||||
bool WindowsHello::isAvailable() const
|
||||
{
|
||||
return m_available;
|
||||
}
|
||||
|
||||
QString WindowsHello::errorString() const
|
||||
{
|
||||
return m_error;
|
||||
}
|
||||
|
||||
bool WindowsHello::storeKey(const QString& dbPath, const QByteArray& data)
|
||||
{
|
||||
queueSecurityPromptFocus();
|
||||
|
||||
// Generate a random challenge that will be signed by Windows Hello
|
||||
// to create the key. The challenge is also used as the IV.
|
||||
auto ivSize = SymmetricCipher::defaultIvSize(SymmetricCipher::Aes256_GCM);
|
||||
auto challenge = Random::instance()->randomArray(ivSize);
|
||||
QByteArray key;
|
||||
if (!deriveEncryptionKey(challenge, key, m_error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Encrypt the data using AES-256-CBC
|
||||
SymmetricCipher cipher;
|
||||
if (!cipher.init(SymmetricCipher::Aes256_GCM, SymmetricCipher::Encrypt, key, challenge)) {
|
||||
m_error = tr("Failed to init KeePassXC crypto.");
|
||||
return false;
|
||||
}
|
||||
QByteArray encrypted = data;
|
||||
if (!cipher.finish(encrypted)) {
|
||||
m_error = tr("Failed to encrypt key data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prepend the challenge/IV to the encrypted data
|
||||
encrypted.prepend(challenge);
|
||||
m_encryptedKeys.insert(dbPath, encrypted);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowsHello::getKey(const QString& dbPath, QByteArray& data)
|
||||
{
|
||||
data.clear();
|
||||
if (!hasKey(dbPath)) {
|
||||
m_error = tr("Failed to get Windows Hello credential.");
|
||||
return false;
|
||||
}
|
||||
|
||||
queueSecurityPromptFocus();
|
||||
|
||||
// Read the previously used challenge and encrypted data
|
||||
auto ivSize = SymmetricCipher::defaultIvSize(SymmetricCipher::Aes256_GCM);
|
||||
const auto& keydata = m_encryptedKeys.value(dbPath);
|
||||
auto challenge = keydata.left(ivSize);
|
||||
auto encrypted = keydata.mid(ivSize);
|
||||
QByteArray key;
|
||||
|
||||
if (!deriveEncryptionKey(challenge, key, m_error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decrypt the data using the generated key and IV from above
|
||||
SymmetricCipher cipher;
|
||||
if (!cipher.init(SymmetricCipher::Aes256_GCM, SymmetricCipher::Decrypt, key, challenge)) {
|
||||
m_error = tr("Failed to init KeePassXC crypto.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store the decrypted data into the passed parameter
|
||||
data = encrypted;
|
||||
if (!cipher.finish(data)) {
|
||||
data.clear();
|
||||
m_error = tr("Failed to decrypt key data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WindowsHello::reset(const QString& dbPath)
|
||||
{
|
||||
m_encryptedKeys.remove(dbPath);
|
||||
}
|
||||
|
||||
bool WindowsHello::hasKey(const QString& dbPath) const
|
||||
{
|
||||
return m_encryptedKeys.contains(dbPath);
|
||||
}
|
||||
|
||||
void WindowsHello::reset()
|
||||
{
|
||||
m_encryptedKeys.clear();
|
||||
}
|
58
src/winhello/WindowsHello.h
Normal file
58
src/winhello/WindowsHello.h
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2022 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSXC_WINDOWSHELLO_H
|
||||
#define KEEPASSXC_WINDOWSHELLO_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
|
||||
class WindowsHello : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static WindowsHello* instance();
|
||||
bool isAvailable() const;
|
||||
QString errorString() const;
|
||||
void reset();
|
||||
|
||||
bool storeKey(const QString& dbPath, const QByteArray& key);
|
||||
bool getKey(const QString& dbPath, QByteArray& key);
|
||||
bool hasKey(const QString& dbPath) const;
|
||||
void reset(const QString& dbPath);
|
||||
|
||||
signals:
|
||||
void availableChanged(bool state);
|
||||
|
||||
private:
|
||||
bool m_available = false;
|
||||
QString m_error;
|
||||
QHash<QString, QByteArray> m_encryptedKeys;
|
||||
|
||||
static WindowsHello* m_instance;
|
||||
WindowsHello(QObject* parent = nullptr);
|
||||
~WindowsHello() override = default;
|
||||
Q_DISABLE_COPY(WindowsHello);
|
||||
};
|
||||
|
||||
inline WindowsHello* getWindowsHello()
|
||||
{
|
||||
return WindowsHello::instance();
|
||||
}
|
||||
|
||||
#endif // KEEPASSXC_WINDOWSHELLO_H
|
Loading…
x
Reference in New Issue
Block a user