mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-02-18 13:24:10 -05:00
Automatically detect USB device changes
This commit is contained in:
parent
79ca00604a
commit
6a273363c4
@ -570,6 +570,12 @@ include_directories(SYSTEM ${ZLIB_INCLUDE_DIR})
|
|||||||
if(WITH_XC_YUBIKEY)
|
if(WITH_XC_YUBIKEY)
|
||||||
find_package(PCSC REQUIRED)
|
find_package(PCSC REQUIRED)
|
||||||
include_directories(SYSTEM ${PCSC_INCLUDE_DIRS})
|
include_directories(SYSTEM ${PCSC_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
if(UNIX AND NOT APPLE)
|
||||||
|
find_library(LIBUSB_LIBRARIES NAMES usb-1.0 REQUIRED)
|
||||||
|
find_path(LIBUSB_INCLUDE_DIR NAMES libusb.h PATH_SUFFIXES "libusb-1.0" "libusb" REQUIRED)
|
||||||
|
include_directories(SYSTEM ${LIBUSB_INCLUDE_DIR})
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(UNIX)
|
if(UNIX)
|
||||||
|
1
COPYING
1
COPYING
@ -221,6 +221,7 @@ Files: share/icons/application/scalable/actions/application-exit.svg
|
|||||||
share/icons/application/scalable/actions/username-copy.svg
|
share/icons/application/scalable/actions/username-copy.svg
|
||||||
share/icons/application/scalable/actions/view-history.svg
|
share/icons/application/scalable/actions/view-history.svg
|
||||||
share/icons/application/scalable/actions/web.svg
|
share/icons/application/scalable/actions/web.svg
|
||||||
|
share/icons/application/scalable/actions/yubikey-refresh.svg
|
||||||
share/icons/application/scalable/apps/internet-web-browser.svg
|
share/icons/application/scalable/apps/internet-web-browser.svg
|
||||||
share/icons/application/scalable/apps/keepassxc.svg
|
share/icons/application/scalable/apps/keepassxc.svg
|
||||||
share/icons/application/scalable/apps/keepassxc-dark.svg
|
share/icons/application/scalable/apps/keepassxc-dark.svg
|
||||||
|
10
share/icons/application/scalable/actions/yubikey-refresh.svg
Normal file
10
share/icons/application/scalable/actions/yubikey-refresh.svg
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<g transform="matrix(0.722673,-0.722673,0.722673,0.722673,-9.84661,10.66)">
|
||||||
|
<path d="M6.086,22.736C3.148,21.904 1,19.206 1,16C1,14.07 1.78,12.32 3.05,11.05L9.77,4.33L11.288,5.868L15.078,2.078C15.258,1.898 15.508,1.788 15.788,1.788C16.068,1.788 16.318,1.898 16.498,2.078L21.919,7.502L21.919,7.512C22.059,7.682 22.139,7.902 22.139,8.142C22.139,8.442 22.009,8.712 21.799,8.902L18.069,12.642L19.67,14.23L18.255,15.645L9.77,7.16L4.46,12.46C3.56,13.37 3,14.62 3,16C3,18.186 4.405,20.046 6.361,20.725C6.182,21.382 6.09,22.059 6.086,22.736ZM7.133,18.873C5.897,18.502 5,17.358 5,16C5,14.34 6.34,13 8,13C9.505,13 10.747,14.102 10.966,15.545C10.021,15.925 9.136,16.499 8.371,17.264C7.879,17.756 7.466,18.298 7.133,18.873ZM20.35,8.046L15.859,3.553L12.431,7.001L16.901,11.484L20.35,8.046ZM8,15C7.45,15 7,15.45 7,16C7,16.55 7.45,17 8,17C8.55,17 9,16.55 9,16C9,15.45 8.55,15 8,15Z"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.832215,6.28971e-17,-6.28971e-17,0.832215,6.96368,6.76821)">
|
||||||
|
<path d="M17.65,6.35C16.2,4.9 14.21,4 12,4C7.611,4 4,7.611 4,12C4,16.389 7.611,20 12,20C15.73,20 18.84,17.45 19.73,14L17.65,14C16.83,16.33 14.61,18 12,18C8.708,18 6,15.292 6,12C6,8.708 8.708,6 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11L20,11L20,4L17.65,6.35Z" style="fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
@ -87,6 +87,7 @@
|
|||||||
<file>application/scalable/actions/username-copy.svg</file>
|
<file>application/scalable/actions/username-copy.svg</file>
|
||||||
<file>application/scalable/actions/view-history.svg</file>
|
<file>application/scalable/actions/view-history.svg</file>
|
||||||
<file>application/scalable/actions/web.svg</file>
|
<file>application/scalable/actions/web.svg</file>
|
||||||
|
<file>application/scalable/actions/yubikey-refresh.svg</file>
|
||||||
<file>application/scalable/apps/freedesktop.svg</file>
|
<file>application/scalable/apps/freedesktop.svg</file>
|
||||||
<file>application/scalable/apps/internet-web-browser.svg</file>
|
<file>application/scalable/apps/internet-web-browser.svg</file>
|
||||||
<file>application/scalable/apps/keepassxc.svg</file>
|
<file>application/scalable/apps/keepassxc.svg</file>
|
||||||
|
@ -1500,39 +1500,10 @@ Backup database located at %2</source>
|
|||||||
<source>Password field</source>
|
<source>Password field</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>Enter Additional Credentials (if any):</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Key File:</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source><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></source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Key file help</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>Hardware key slot selection</source>
|
<source>Hardware key slot selection</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>Hardware Key:</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source><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></source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Hardware key help</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>Key file to unlock the database</source>
|
<source>Key file to unlock the database</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
@ -1545,14 +1516,6 @@ Backup database located at %2</source>
|
|||||||
<source>Browse…</source>
|
<source>Browse…</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>Refresh hardware tokens</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Refresh</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>Unlock Database</source>
|
<source>Unlock Database</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
@ -1638,23 +1601,6 @@ To prevent this error from appearing, you must go to "Database Settings / S
|
|||||||
<source>Cannot use database file as key file</source>
|
<source>Cannot use database file as key file</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>You cannot use your database file as a key file.
|
|
||||||
If you do not have a key file, please leave the field empty.</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Detecting hardware keys…</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>No hardware keys detected</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Select hardware key…</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>authenticate to access the database</source>
|
<source>authenticate to access the database</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
@ -1663,6 +1609,54 @@ If you do not have a key file, please leave the field empty.</source>
|
|||||||
<source>Failed to authenticate with Quick Unlock: %1</source>
|
<source>Failed to authenticate with Quick Unlock: %1</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Select Key File:</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source><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!</p></source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Click to add a key file.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source><a href="#" style="text-decoration: underline">I have a key file</a></source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Use hardware key [Serial: %1]</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Use hardware key</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Your database file is NOT a key file!
|
||||||
|
If you don't have a key file or don't know what that is, you don't have to select one.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>KeePassXC database file selected</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>The file you selected looks like a database file.
|
||||||
|
A database file is NOT a key file!
|
||||||
|
|
||||||
|
Are you sure you want to continue with this file?.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>No hardware keys found.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Refresh Hardware Keys</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>DatabaseSettingWidgetMetaData</name>
|
<name>DatabaseSettingWidgetMetaData</name>
|
||||||
@ -9633,10 +9627,6 @@ Example: JBSWY3DPEHPK3PXP</source>
|
|||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>YubiKey</name>
|
<name>YubiKey</name>
|
||||||
<message>
|
|
||||||
<source>%1 No interface, slot %2</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>General: </source>
|
<source>General: </source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
@ -9648,14 +9638,6 @@ Example: JBSWY3DPEHPK3PXP</source>
|
|||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>YubiKeyEditWidget</name>
|
<name>YubiKeyEditWidget</name>
|
||||||
<message>
|
|
||||||
<source>Refresh hardware tokens</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Refresh</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>Hardware key slot selection</source>
|
<source>Hardware key slot selection</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
@ -9700,28 +9682,17 @@ Example: JBSWY3DPEHPK3PXP</source>
|
|||||||
<source><p>If you own a <a href="https://www.yubico.com/">YubiKey</a> or <a href="https://onlykey.io">OnlyKey</a>, you can use it for additional security.</p><p>The key requires one of its slots to be programmed as <a href="https://docs.yubico.com/yesdk/users-manual/application-otp/challenge-response.html">HMAC-SHA1 Challenge-Response</a>.</p></source>
|
<source><p>If you own a <a href="https://www.yubico.com/">YubiKey</a> or <a href="https://onlykey.io">OnlyKey</a>, you can use it for additional security.</p><p>The key requires one of its slots to be programmed as <a href="https://docs.yubico.com/yesdk/users-manual/application-otp/challenge-response.html">HMAC-SHA1 Challenge-Response</a>.</p></source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
|
||||||
<context>
|
|
||||||
<name>YubiKeyInterface</name>
|
|
||||||
<message>
|
<message>
|
||||||
<source>%1 Invalid slot specified - %2</source>
|
<source>Refresh hardware keys</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>YubiKeyInterfacePCSC</name>
|
<name>YubiKeyInterfacePCSC</name>
|
||||||
<message>
|
|
||||||
<source>(PCSC) %1 [%2] Challenge-Response - Slot %3</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>The YubiKey PCSC interface has not been initialized.</source>
|
<source>The YubiKey PCSC interface has not been initialized.</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>Hardware key is currently in use.</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>Could not find or access hardware key with serial number %1. Please present it to continue. </source>
|
<source>Could not find or access hardware key with serial number %1. Please present it to continue. </source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
@ -9738,6 +9709,21 @@ Example: JBSWY3DPEHPK3PXP</source>
|
|||||||
<source>Failed to complete a challenge-response, the PCSC error code was: %1</source>
|
<source>Failed to complete a challenge-response, the PCSC error code was: %1</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>(NFC) %1 [%2] - Slot %3, %4</source>
|
||||||
|
<comment>YubiKey display fields</comment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Press</source>
|
||||||
|
<comment>USB Challenge-Response Key interaction request</comment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Passive</source>
|
||||||
|
<comment>USB Challenge-Response Key no interaction required</comment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>YubiKeyInterfaceUSB</name>
|
<name>YubiKeyInterfaceUSB</name>
|
||||||
@ -9745,14 +9731,6 @@ Example: JBSWY3DPEHPK3PXP</source>
|
|||||||
<source>Unknown</source>
|
<source>Unknown</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>(USB) %1 [%2] Configured Slot - %3</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>(USB) %1 [%2] Challenge-Response - Slot %3 - %4</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>Press</source>
|
<source>Press</source>
|
||||||
<comment>USB Challenge-Response Key interaction request</comment>
|
<comment>USB Challenge-Response Key interaction request</comment>
|
||||||
@ -9767,10 +9745,6 @@ Example: JBSWY3DPEHPK3PXP</source>
|
|||||||
<source>The YubiKey USB interface has not been initialized.</source>
|
<source>The YubiKey USB interface has not been initialized.</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>Hardware key is currently in use.</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>Could not find hardware key with serial number %1. Please plug it in to continue.</source>
|
<source>Could not find hardware key with serial number %1. Please plug it in to continue.</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
@ -9787,5 +9761,15 @@ Example: JBSWY3DPEHPK3PXP</source>
|
|||||||
<source>Failed to complete a challenge-response, the specific error was: %1</source>
|
<source>Failed to complete a challenge-response, the specific error was: %1</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>%1 [%2] - Slot %3</source>
|
||||||
|
<comment>YubiKey NEO display fields</comment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>%1 [%2] - Slot %3, %4</source>
|
||||||
|
<comment>YubiKey display fields</comment>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
</TS>
|
</TS>
|
||||||
|
@ -253,6 +253,17 @@ if(WIN32)
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(WITH_XC_YUBIKEY)
|
||||||
|
set(keepassx_SOURCES ${keepassx_SOURCES} gui/osutils/DeviceListener.cpp)
|
||||||
|
if(APPLE)
|
||||||
|
set(keepassx_SOURCES ${keepassx_SOURCES} gui/osutils/macutils/DeviceListenerMac.cpp)
|
||||||
|
elseif(UNIX)
|
||||||
|
set(keepassx_SOURCES ${keepassx_SOURCES} gui/osutils/nixutils/DeviceListenerLibUsb.cpp)
|
||||||
|
elseif(WIN32)
|
||||||
|
set(keepassx_SOURCES ${keepassx_SOURCES} gui/osutils/winutils/DeviceListenerWin.cpp)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
set(keepassx_SOURCES ${keepassx_SOURCES}
|
set(keepassx_SOURCES ${keepassx_SOURCES}
|
||||||
../share/icons/icons.qrc
|
../share/icons/icons.qrc
|
||||||
../share/wizard/wizard.qrc)
|
../share/wizard/wizard.qrc)
|
||||||
@ -401,7 +412,7 @@ if(HAIKU)
|
|||||||
target_link_libraries(keepassx_core network)
|
target_link_libraries(keepassx_core network)
|
||||||
endif()
|
endif()
|
||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
target_link_libraries(keepassx_core Qt5::DBus)
|
target_link_libraries(keepassx_core Qt5::DBus ${LIBUSB_LIBRARIES})
|
||||||
if(WITH_XC_X11)
|
if(WITH_XC_X11)
|
||||||
target_link_libraries(keepassx_core Qt5::X11Extras X11)
|
target_link_libraries(keepassx_core Qt5::X11Extras X11)
|
||||||
endif()
|
endif()
|
||||||
|
@ -18,6 +18,8 @@
|
|||||||
#ifndef KEEPASSXC_BOOTSTRAP_H
|
#ifndef KEEPASSXC_BOOTSTRAP_H
|
||||||
#define KEEPASSXC_BOOTSTRAP_H
|
#define KEEPASSXC_BOOTSTRAP_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
namespace Bootstrap
|
namespace Bootstrap
|
||||||
{
|
{
|
||||||
void bootstrap();
|
void bootstrap();
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
|
|
||||||
#include "config-keepassx.h"
|
|
||||||
#include "core/Config.h"
|
#include "core/Config.h"
|
||||||
#include "core/Resources.h"
|
#include "core/Resources.h"
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#define KEEPASSX_TRANSLATOR_H
|
#define KEEPASSX_TRANSLATOR_H
|
||||||
|
|
||||||
#include <QMetaType>
|
#include <QMetaType>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
class Translator
|
class Translator
|
||||||
{
|
{
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#define KEEPASSX_APPLICATION_H
|
#define KEEPASSX_APPLICATION_H
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QString>
|
||||||
#include <QtNetwork/qlocalserver.h>
|
#include <QtNetwork/qlocalserver.h>
|
||||||
|
|
||||||
#if defined(Q_OS_WIN) || (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
|
#if defined(Q_OS_WIN) || (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
|
||||||
|
@ -19,13 +19,15 @@
|
|||||||
#include "DatabaseOpenWidget.h"
|
#include "DatabaseOpenWidget.h"
|
||||||
#include "ui_DatabaseOpenWidget.h"
|
#include "ui_DatabaseOpenWidget.h"
|
||||||
|
|
||||||
#include "config-keepassx.h"
|
|
||||||
#include "gui/FileDialog.h"
|
#include "gui/FileDialog.h"
|
||||||
#include "gui/Icons.h"
|
#include "gui/Icons.h"
|
||||||
#include "gui/MainWindow.h"
|
#include "gui/MainWindow.h"
|
||||||
#include "gui/MessageBox.h"
|
#include "gui/MessageBox.h"
|
||||||
#include "keys/ChallengeResponseKey.h"
|
#include "keys/ChallengeResponseKey.h"
|
||||||
#include "keys/FileKey.h"
|
#include "keys/FileKey.h"
|
||||||
|
#ifdef WITH_XC_YUBIKEY
|
||||||
|
#include "keys/drivers/YubiKeyInterfaceUSB.h"
|
||||||
|
#endif
|
||||||
#include "quickunlock/QuickUnlockInterface.h"
|
#include "quickunlock/QuickUnlockInterface.h"
|
||||||
|
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
@ -58,6 +60,9 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
|
|||||||
: DialogyWidget(parent)
|
: DialogyWidget(parent)
|
||||||
, m_ui(new Ui::DatabaseOpenWidget())
|
, m_ui(new Ui::DatabaseOpenWidget())
|
||||||
, m_db(nullptr)
|
, m_db(nullptr)
|
||||||
|
#ifdef WITH_XC_YUBIKEY
|
||||||
|
, m_deviceListener(new DeviceListener(this))
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
|
|
||||||
@ -90,18 +95,27 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
|
|||||||
connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(openDatabase()));
|
connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(openDatabase()));
|
||||||
connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject()));
|
connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject()));
|
||||||
|
|
||||||
m_ui->hardwareKeyLabelHelp->setIcon(icons()->icon("system-help").pixmap(QSize(12, 12)));
|
connect(m_ui->addKeyFileLinkLabel, &QLabel::linkActivated, this, [&](const QString&) {
|
||||||
connect(m_ui->hardwareKeyLabelHelp, SIGNAL(clicked(bool)), SLOT(openHardwareKeyHelp()));
|
if (browseKeyFile()) {
|
||||||
m_ui->keyFileLabelHelp->setIcon(icons()->icon("system-help").pixmap(QSize(12, 12)));
|
toggleKeyFileComponent(true);
|
||||||
connect(m_ui->keyFileLabelHelp, SIGNAL(clicked(bool)), SLOT(openKeyFileHelp()));
|
}
|
||||||
|
});
|
||||||
|
connect(m_ui->keyFileLineEdit, &PasswordWidget::textChanged, this, [&](const QString& text) {
|
||||||
|
if (text.isEmpty() && m_ui->keyFileLineEdit->isVisible()) {
|
||||||
|
toggleKeyFileComponent(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(m_ui->useHardwareKeyCheckBox, &QCheckBox::toggled, m_ui->hardwareKeyCombo, &QComboBox::setEnabled);
|
||||||
|
|
||||||
|
toggleKeyFileComponent(false);
|
||||||
|
toggleHardwareKeyComponent(false);
|
||||||
|
|
||||||
#ifdef WITH_XC_YUBIKEY
|
|
||||||
m_ui->hardwareKeyProgress->setVisible(false);
|
|
||||||
QSizePolicy sp = m_ui->hardwareKeyProgress->sizePolicy();
|
QSizePolicy sp = m_ui->hardwareKeyProgress->sizePolicy();
|
||||||
sp.setRetainSizeWhenHidden(true);
|
sp.setRetainSizeWhenHidden(true);
|
||||||
m_ui->hardwareKeyProgress->setSizePolicy(sp);
|
m_ui->hardwareKeyProgress->setSizePolicy(sp);
|
||||||
|
|
||||||
connect(m_ui->buttonRedetectYubikey, SIGNAL(clicked()), SLOT(pollHardwareKey()));
|
#ifdef WITH_XC_YUBIKEY
|
||||||
|
connect(m_deviceListener, SIGNAL(devicePlugged(bool, void*, void*)), this, SLOT(pollHardwareKey()));
|
||||||
connect(YubiKey::instance(), SIGNAL(detectComplete(bool)), SLOT(hardwareKeyResponse(bool)), Qt::QueuedConnection);
|
connect(YubiKey::instance(), SIGNAL(detectComplete(bool)), SLOT(hardwareKeyResponse(bool)), Qt::QueuedConnection);
|
||||||
|
|
||||||
connect(YubiKey::instance(), &YubiKey::userInteractionRequest, this, [this] {
|
connect(YubiKey::instance(), &YubiKey::userInteractionRequest, this, [this] {
|
||||||
@ -113,12 +127,17 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(YubiKey::instance(), &YubiKey::challengeCompleted, this, [this] { m_ui->messageWidget->hide(); });
|
connect(YubiKey::instance(), &YubiKey::challengeCompleted, this, [this] { m_ui->messageWidget->hide(); });
|
||||||
|
|
||||||
|
m_ui->noHardwareKeysFoundLabel->setVisible(false);
|
||||||
|
m_ui->refreshHardwareKeys->setIcon(icons()->icon("yubikey-refresh", true));
|
||||||
|
connect(m_ui->refreshHardwareKeys, &QPushButton::clicked, this, [this] { pollHardwareKey(true); });
|
||||||
|
m_hideNoHardwareKeysFoundTimer.setInterval(2000);
|
||||||
|
connect(&m_hideNoHardwareKeysFoundTimer, &QTimer::timeout, this, [this] {
|
||||||
|
m_ui->noHardwareKeysFoundLabel->setVisible(false);
|
||||||
|
});
|
||||||
#else
|
#else
|
||||||
m_ui->hardwareKeyLabel->setVisible(false);
|
m_ui->noHardwareKeysFoundLabel->setVisible(false);
|
||||||
m_ui->hardwareKeyLabelHelp->setVisible(false);
|
m_ui->refreshHardwareKeys->setVisible(false);
|
||||||
m_ui->buttonRedetectYubikey->setVisible(false);
|
|
||||||
m_ui->challengeResponseCombo->setVisible(false);
|
|
||||||
m_ui->hardwareKeyProgress->setVisible(false);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// QuickUnlock actions
|
// QuickUnlock actions
|
||||||
@ -129,6 +148,32 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
|
|||||||
|
|
||||||
DatabaseOpenWidget::~DatabaseOpenWidget() = default;
|
DatabaseOpenWidget::~DatabaseOpenWidget() = default;
|
||||||
|
|
||||||
|
void DatabaseOpenWidget::toggleKeyFileComponent(bool state)
|
||||||
|
{
|
||||||
|
m_ui->addKeyFileLinkLabel->setVisible(!state);
|
||||||
|
m_ui->selectKeyFileComponent->setVisible(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseOpenWidget::toggleHardwareKeyComponent(bool state)
|
||||||
|
{
|
||||||
|
m_ui->hardwareKeyProgress->setVisible(false);
|
||||||
|
m_ui->hardwareKeyComponent->setVisible(state);
|
||||||
|
m_ui->hardwareKeyCombo->setVisible(state && m_ui->hardwareKeyCombo->count() != 1);
|
||||||
|
m_ui->noHardwareKeysFoundLabel->setVisible(!state && m_manualHardwareKeyRefresh);
|
||||||
|
if (!state) {
|
||||||
|
m_ui->useHardwareKeyCheckBox->setChecked(false);
|
||||||
|
}
|
||||||
|
if (m_ui->hardwareKeyCombo->count() == 1) {
|
||||||
|
m_ui->useHardwareKeyCheckBox->setText(
|
||||||
|
tr("Use hardware key [Serial: %1]")
|
||||||
|
.arg(m_ui->hardwareKeyCombo->itemData(m_ui->hardwareKeyCombo->currentIndex())
|
||||||
|
.value<YubiKeySlot>()
|
||||||
|
.first));
|
||||||
|
} else {
|
||||||
|
m_ui->useHardwareKeyCheckBox->setText(tr("Use hardware key"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DatabaseOpenWidget::showEvent(QShowEvent* event)
|
void DatabaseOpenWidget::showEvent(QShowEvent* event)
|
||||||
{
|
{
|
||||||
DialogyWidget::showEvent(event);
|
DialogyWidget::showEvent(event);
|
||||||
@ -141,6 +186,24 @@ void DatabaseOpenWidget::showEvent(QShowEvent* event)
|
|||||||
m_ui->editPassword->setFocus();
|
m_ui->editPassword->setFocus();
|
||||||
}
|
}
|
||||||
m_hideTimer.stop();
|
m_hideTimer.stop();
|
||||||
|
|
||||||
|
#ifdef WITH_XC_YUBIKEY
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
m_deviceListener->registerHotplugCallback(true,
|
||||||
|
true,
|
||||||
|
YubiKeyInterfaceUSB::YUBICO_USB_VID,
|
||||||
|
DeviceListener::MATCH_ANY,
|
||||||
|
&DeviceListenerWin::DEV_CLS_KEYBOARD);
|
||||||
|
m_deviceListener->registerHotplugCallback(true,
|
||||||
|
true,
|
||||||
|
YubiKeyInterfaceUSB::ONLYKEY_USB_VID,
|
||||||
|
DeviceListener::MATCH_ANY,
|
||||||
|
&DeviceListenerWin::DEV_CLS_KEYBOARD);
|
||||||
|
#else
|
||||||
|
m_deviceListener->registerHotplugCallback(true, true, YubiKeyInterfaceUSB::YUBICO_USB_VID);
|
||||||
|
m_deviceListener->registerHotplugCallback(true, true, YubiKeyInterfaceUSB::ONLYKEY_USB_VID);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseOpenWidget::hideEvent(QHideEvent* event)
|
void DatabaseOpenWidget::hideEvent(QHideEvent* event)
|
||||||
@ -151,6 +214,10 @@ void DatabaseOpenWidget::hideEvent(QHideEvent* event)
|
|||||||
if (!isVisible()) {
|
if (!isVisible()) {
|
||||||
m_hideTimer.start();
|
m_hideTimer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef WITH_XC_YUBIKEY
|
||||||
|
m_deviceListener->deregisterAllHotplugCallbacks();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DatabaseOpenWidget::unlockingDatabase()
|
bool DatabaseOpenWidget::unlockingDatabase()
|
||||||
@ -175,6 +242,7 @@ void DatabaseOpenWidget::load(const QString& filename)
|
|||||||
auto lastKeyFiles = config()->get(Config::LastKeyFiles).toHash();
|
auto lastKeyFiles = config()->get(Config::LastKeyFiles).toHash();
|
||||||
if (lastKeyFiles.contains(m_filename)) {
|
if (lastKeyFiles.contains(m_filename)) {
|
||||||
m_ui->keyFileLineEdit->setText(lastKeyFiles[m_filename].toString());
|
m_ui->keyFileLineEdit->setText(lastKeyFiles[m_filename].toString());
|
||||||
|
toggleKeyFileComponent(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,13 +254,8 @@ void DatabaseOpenWidget::load(const QString& filename)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef WITH_XC_YUBIKEY
|
#ifdef WITH_XC_YUBIKEY
|
||||||
// Only auto-poll for hardware keys if we previously used one with this database file
|
// Do initial auto-poll
|
||||||
if (config()->get(Config::RememberLastKeyFiles).toBool()) {
|
pollHardwareKey();
|
||||||
auto lastChallengeResponse = config()->get(Config::LastChallengeResponse).toHash();
|
|
||||||
if (lastChallengeResponse.contains(m_filename)) {
|
|
||||||
pollHardwareKey();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,7 +267,7 @@ void DatabaseOpenWidget::clearForms()
|
|||||||
m_ui->keyFileLineEdit->clear();
|
m_ui->keyFileLineEdit->clear();
|
||||||
m_ui->keyFileLineEdit->setShowPassword(false);
|
m_ui->keyFileLineEdit->setShowPassword(false);
|
||||||
m_ui->keyFileLineEdit->setClearButtonEnabled(true);
|
m_ui->keyFileLineEdit->setClearButtonEnabled(true);
|
||||||
m_ui->challengeResponseCombo->clear();
|
m_ui->hardwareKeyCombo->clear();
|
||||||
m_ui->centralStack->setCurrentIndex(0);
|
m_ui->centralStack->setCurrentIndex(0);
|
||||||
|
|
||||||
QString error;
|
QString error;
|
||||||
@ -383,9 +446,9 @@ QSharedPointer<CompositeKey> DatabaseOpenWidget::buildDatabaseKey()
|
|||||||
auto lastChallengeResponse = config()->get(Config::LastChallengeResponse).toHash();
|
auto lastChallengeResponse = config()->get(Config::LastChallengeResponse).toHash();
|
||||||
lastChallengeResponse.remove(m_filename);
|
lastChallengeResponse.remove(m_filename);
|
||||||
|
|
||||||
int selectionIndex = m_ui->challengeResponseCombo->currentIndex();
|
int selectionIndex = m_ui->hardwareKeyCombo->currentIndex();
|
||||||
if (selectionIndex > 0) {
|
if (m_ui->useHardwareKeyCheckBox->isChecked()) {
|
||||||
auto slot = m_ui->challengeResponseCombo->itemData(selectionIndex).value<YubiKeySlot>();
|
auto slot = m_ui->hardwareKeyCombo->itemData(selectionIndex).value<YubiKeySlot>();
|
||||||
auto crKey = QSharedPointer<ChallengeResponseKey>(new ChallengeResponseKey(slot));
|
auto crKey = QSharedPointer<ChallengeResponseKey>(new ChallengeResponseKey(slot));
|
||||||
databaseKey->addChallengeResponseKey(crKey);
|
databaseKey->addChallengeResponseKey(crKey);
|
||||||
|
|
||||||
@ -406,55 +469,65 @@ void DatabaseOpenWidget::reject()
|
|||||||
emit dialogFinished(false);
|
emit dialogFinished(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseOpenWidget::browseKeyFile()
|
bool DatabaseOpenWidget::browseKeyFile()
|
||||||
{
|
{
|
||||||
QString filters = QString("%1 (*);;%2 (*.keyx; *.key)").arg(tr("All files"), tr("Key files"));
|
QString filters = QString("%1 (*);;%2 (*.keyx; *.key)").arg(tr("All files"), tr("Key files"));
|
||||||
QString filename = fileDialog()->getOpenFileName(this, tr("Select key file"), QString(), filters);
|
QString filename =
|
||||||
|
fileDialog()->getOpenFileName(this, tr("Select key file"), FileDialog::getLastDir("keyfile"), filters);
|
||||||
|
if (filename.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
FileDialog::saveLastDir("keyfile", filename, true);
|
||||||
|
|
||||||
if (QFileInfo(filename).canonicalFilePath() == QFileInfo(m_filename).canonicalFilePath()) {
|
if (QFileInfo(filename).canonicalFilePath() == QFileInfo(m_filename).canonicalFilePath()) {
|
||||||
MessageBox::warning(this,
|
MessageBox::warning(this,
|
||||||
tr("Cannot use database file as key file"),
|
tr("Cannot use database file as key file"),
|
||||||
tr("You cannot use your database file as a key file.\nIf you do not have a key file, "
|
tr("Your database file is NOT a key file!\nIf you don't have a key file or don't know what "
|
||||||
"please leave the field empty."),
|
"that is, you don't have to select one."),
|
||||||
MessageBox::Button::Ok);
|
MessageBox::Button::Ok);
|
||||||
filename = "";
|
return false;
|
||||||
|
}
|
||||||
|
if (filename.endsWith(".kdbx")
|
||||||
|
&& MessageBox::warning(this,
|
||||||
|
tr("KeePassXC database file selected"),
|
||||||
|
tr("The file you selected looks like a database file.\nA database file is NOT a key "
|
||||||
|
"file!\n\nAre you sure you want to continue with this file?."),
|
||||||
|
MessageBox::Button::Yes | MessageBox::Button::Cancel,
|
||||||
|
MessageBox::Button::Cancel)
|
||||||
|
!= MessageBox::Yes) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!filename.isEmpty()) {
|
m_ui->keyFileLineEdit->setText(filename);
|
||||||
m_ui->keyFileLineEdit->setText(filename);
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseOpenWidget::pollHardwareKey()
|
void DatabaseOpenWidget::pollHardwareKey(bool manualTrigger)
|
||||||
{
|
{
|
||||||
if (m_pollingHardwareKey) {
|
if (m_pollingHardwareKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_ui->challengeResponseCombo->clear();
|
m_ui->hardwareKeyCombo->setEnabled(false);
|
||||||
m_ui->challengeResponseCombo->addItem(tr("Detecting hardware keys…"));
|
|
||||||
|
|
||||||
m_ui->buttonRedetectYubikey->setEnabled(false);
|
|
||||||
m_ui->challengeResponseCombo->setEnabled(false);
|
|
||||||
m_ui->hardwareKeyProgress->setVisible(true);
|
m_ui->hardwareKeyProgress->setVisible(true);
|
||||||
|
m_ui->refreshHardwareKeys->setEnabled(false);
|
||||||
|
m_ui->noHardwareKeysFoundLabel->setVisible(false);
|
||||||
m_pollingHardwareKey = true;
|
m_pollingHardwareKey = true;
|
||||||
|
m_manualHardwareKeyRefresh = manualTrigger;
|
||||||
|
|
||||||
YubiKey::instance()->findValidKeysAsync();
|
YubiKey::instance()->findValidKeysAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseOpenWidget::hardwareKeyResponse(bool found)
|
void DatabaseOpenWidget::hardwareKeyResponse(bool found)
|
||||||
{
|
{
|
||||||
m_ui->challengeResponseCombo->clear();
|
|
||||||
m_ui->buttonRedetectYubikey->setEnabled(true);
|
|
||||||
m_ui->hardwareKeyProgress->setVisible(false);
|
m_ui->hardwareKeyProgress->setVisible(false);
|
||||||
|
m_ui->refreshHardwareKeys->setEnabled(true);
|
||||||
|
m_ui->hardwareKeyCombo->clear();
|
||||||
m_pollingHardwareKey = false;
|
m_pollingHardwareKey = false;
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
m_ui->challengeResponseCombo->addItem(tr("No hardware keys detected"));
|
toggleHardwareKeyComponent(false);
|
||||||
m_ui->challengeResponseCombo->setEnabled(false);
|
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
m_ui->challengeResponseCombo->addItem(tr("Select hardware key…"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
YubiKeySlot lastUsedSlot;
|
YubiKeySlot lastUsedSlot;
|
||||||
@ -466,31 +539,24 @@ void DatabaseOpenWidget::hardwareKeyResponse(bool found)
|
|||||||
if (split.size() > 1) {
|
if (split.size() > 1) {
|
||||||
lastUsedSlot = YubiKeySlot(split[0].toUInt(), split[1].toInt());
|
lastUsedSlot = YubiKeySlot(split[0].toUInt(), split[1].toInt());
|
||||||
}
|
}
|
||||||
|
m_ui->useHardwareKeyCheckBox->setChecked(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int selectedIndex = 0;
|
int selectedIndex = 0;
|
||||||
for (auto& slot : YubiKey::instance()->foundKeys()) {
|
const auto foundKeys = YubiKey::instance()->foundKeys();
|
||||||
|
for (auto i = foundKeys.cbegin(); i != foundKeys.cend(); ++i) {
|
||||||
// add detected YubiKey to combo box
|
// add detected YubiKey to combo box
|
||||||
m_ui->challengeResponseCombo->addItem(YubiKey::instance()->getDisplayName(slot), QVariant::fromValue(slot));
|
m_ui->hardwareKeyCombo->addItem(i.value(), QVariant::fromValue(i.key()));
|
||||||
// Select this YubiKey + Slot if we used it in the past
|
// Select this YubiKey + Slot if we used it in the past
|
||||||
if (lastUsedSlot == slot) {
|
if (lastUsedSlot == i.key()) {
|
||||||
selectedIndex = m_ui->challengeResponseCombo->count() - 1;
|
selectedIndex = m_ui->hardwareKeyCombo->count() - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_ui->challengeResponseCombo->setCurrentIndex(selectedIndex);
|
toggleHardwareKeyComponent(true);
|
||||||
m_ui->challengeResponseCombo->setEnabled(true);
|
m_ui->hardwareKeyCombo->setEnabled(m_ui->useHardwareKeyCheckBox->isChecked());
|
||||||
}
|
m_ui->hardwareKeyCombo->setCurrentIndex(selectedIndex);
|
||||||
|
|
||||||
void DatabaseOpenWidget::openHardwareKeyHelp()
|
|
||||||
{
|
|
||||||
QDesktopServices::openUrl(QUrl("https://keepassxc.org/docs/#faq-yubikey-2fa"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void DatabaseOpenWidget::openKeyFileHelp()
|
|
||||||
{
|
|
||||||
QDesktopServices::openUrl(QUrl("https://keepassxc.org/docs/#faq-keyfile-howto"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DatabaseOpenWidget::setUserInteractionLock(bool state)
|
void DatabaseOpenWidget::setUserInteractionLock(bool state)
|
||||||
|
@ -19,10 +19,15 @@
|
|||||||
#ifndef KEEPASSX_DATABASEOPENWIDGET_H
|
#ifndef KEEPASSX_DATABASEOPENWIDGET_H
|
||||||
#define KEEPASSX_DATABASEOPENWIDGET_H
|
#define KEEPASSX_DATABASEOPENWIDGET_H
|
||||||
|
|
||||||
|
#include <QPointer>
|
||||||
#include <QScopedPointer>
|
#include <QScopedPointer>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include "config-keepassx.h"
|
||||||
#include "gui/DialogyWidget.h"
|
#include "gui/DialogyWidget.h"
|
||||||
|
#ifdef WITH_XC_YUBIKEY
|
||||||
|
#include "osutils/DeviceListener.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
class CompositeKey;
|
class CompositeKey;
|
||||||
class Database;
|
class Database;
|
||||||
@ -71,17 +76,22 @@ protected slots:
|
|||||||
void reject();
|
void reject();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void browseKeyFile();
|
bool browseKeyFile();
|
||||||
void pollHardwareKey();
|
void toggleKeyFileComponent(bool state);
|
||||||
|
void toggleHardwareKeyComponent(bool state);
|
||||||
|
void pollHardwareKey(bool manualTrigger = false);
|
||||||
void hardwareKeyResponse(bool found);
|
void hardwareKeyResponse(bool found);
|
||||||
void openHardwareKeyHelp();
|
|
||||||
void openKeyFileHelp();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
#ifdef WITH_XC_YUBIKEY
|
||||||
|
QPointer<DeviceListener> m_deviceListener;
|
||||||
|
#endif
|
||||||
bool m_pollingHardwareKey = false;
|
bool m_pollingHardwareKey = false;
|
||||||
|
bool m_manualHardwareKeyRefresh = false;
|
||||||
bool m_blockQuickUnlock = false;
|
bool m_blockQuickUnlock = false;
|
||||||
bool m_unlockingDatabase = false;
|
bool m_unlockingDatabase = false;
|
||||||
QTimer m_hideTimer;
|
QTimer m_hideTimer;
|
||||||
|
QTimer m_hideNoHardwareKeysFoundTimer;
|
||||||
|
|
||||||
Q_DISABLE_COPY(DatabaseOpenWidget)
|
Q_DISABLE_COPY(DatabaseOpenWidget)
|
||||||
};
|
};
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>520</width>
|
<width>745</width>
|
||||||
<height>436</height>
|
<height>544</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="accessibleName">
|
<property name="accessibleName">
|
||||||
@ -18,7 +18,7 @@
|
|||||||
<widget class="MessageWidget" name="messageWidget" native="true"/>
|
<widget class="MessageWidget" name="messageWidget" native="true"/>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="0,1,0">
|
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="1,0,1">
|
||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
@ -40,18 +40,6 @@
|
|||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QWidget" name="formContainer" native="true">
|
<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" stretch="1,0,0,0,0,2">
|
<layout class="QVBoxLayout" name="verticalLayout_4" stretch="1,0,0,0,0,2">
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalSpacer_2">
|
<spacer name="verticalSpacer_2">
|
||||||
@ -71,7 +59,6 @@
|
|||||||
<property name="font">
|
<property name="font">
|
||||||
<font>
|
<font>
|
||||||
<pointsize>12</pointsize>
|
<pointsize>12</pointsize>
|
||||||
<weight>75</weight>
|
|
||||||
<bold>true</bold>
|
<bold>true</bold>
|
||||||
</font>
|
</font>
|
||||||
</property>
|
</property>
|
||||||
@ -107,8 +94,8 @@
|
|||||||
<widget class="QStackedWidget" name="centralStack">
|
<widget class="QStackedWidget" name="centralStack">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>0</width>
|
<width>650</width>
|
||||||
<height>250</height>
|
<height>0</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="frameShape">
|
<property name="frameShape">
|
||||||
@ -122,172 +109,223 @@
|
|||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="mainPage">
|
<widget class="QWidget" name="mainPage">
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
<property name="leftMargin">
|
<property name="leftMargin">
|
||||||
<number>20</number>
|
<number>30</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="topMargin">
|
<property name="topMargin">
|
||||||
<number>15</number>
|
<number>25</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="rightMargin">
|
<property name="rightMargin">
|
||||||
<number>20</number>
|
<number>30</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="bottomMargin">
|
<property name="bottomMargin">
|
||||||
<number>15</number>
|
<number>25</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label">
|
<widget class="QFrame" name="enterPasswordComponent">
|
||||||
<property name="text">
|
<property name="lineWidth">
|
||||||
<string>Enter Password:</string>
|
<number>0</number>
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>editPassword</cstring>
|
|
||||||
</property>
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="enterPasswordComponentLayout">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>10</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>
|
||||||
|
<widget class="QLabel" name="passwordLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Enter Password:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="PasswordWidget" name="editPassword" native="true">
|
||||||
|
<property name="focusPolicy">
|
||||||
|
<enum>Qt::StrongFocus</enum>
|
||||||
|
</property>
|
||||||
|
<property name="accessibleName">
|
||||||
|
<string>Password field</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QProgressBar" name="hardwareKeyProgress">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>4</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>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="PasswordWidget" name="editPassword" native="true">
|
<widget class="QFrame" name="selectKeyFileComponent">
|
||||||
<property name="focusPolicy">
|
<property name="frameShape">
|
||||||
<enum>Qt::StrongFocus</enum>
|
<enum>QFrame::NoFrame</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="accessibleName">
|
<property name="frameShadow">
|
||||||
<string>Password field</string>
|
<enum>QFrame::Plain</enum>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="lineWidth">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="selectKeyFileComponentLayout">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>15</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="selectKeyFileLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Select Key File:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_7" stretch="1,0">
|
||||||
|
<item>
|
||||||
|
<widget class="PasswordWidget" name="keyFileLineEdit" native="true">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="focusPolicy">
|
||||||
|
<enum>Qt::StrongFocus</enum>
|
||||||
|
</property>
|
||||||
|
<property name="accessibleName">
|
||||||
|
<string>Key file to unlock the database</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<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>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<spacer name="verticalSpacer_4">
|
<widget class="QFrame" name="addAdditionalKeysComponent">
|
||||||
<property name="orientation">
|
<property name="lineWidth">
|
||||||
<enum>Qt::Vertical</enum>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeType">
|
<layout class="QHBoxLayout" name="addAdditionalKeysComponentLayout">
|
||||||
<enum>QSizePolicy::Fixed</enum>
|
<property name="sizeConstraint">
|
||||||
</property>
|
<enum>QLayout::SetMinimumSize</enum>
|
||||||
<property name="sizeHint" stdset="0">
|
</property>
|
||||||
<size>
|
<property name="leftMargin">
|
||||||
<width>20</width>
|
<number>0</number>
|
||||||
<height>5</height>
|
</property>
|
||||||
</size>
|
<property name="topMargin">
|
||||||
</property>
|
<number>0</number>
|
||||||
</spacer>
|
</property>
|
||||||
</item>
|
<property name="rightMargin">
|
||||||
<item>
|
<number>0</number>
|
||||||
<widget class="QLabel" name="label_2">
|
</property>
|
||||||
<property name="text">
|
<property name="bottomMargin">
|
||||||
<string>Enter Additional Credentials (if any):</string>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
<item>
|
||||||
</item>
|
<widget class="QFrame" name="hardwareKeyComponent">
|
||||||
<item>
|
<layout class="QHBoxLayout" name="hardwareKeyComponentLayout">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
|
||||||
<item>
|
|
||||||
<spacer name="horizontalSpacer_2">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</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>
|
|
||||||
<item>
|
|
||||||
<layout class="QGridLayout" name="gridLayout_3">
|
|
||||||
<property name="topMargin">
|
|
||||||
<number>3</number>
|
|
||||||
</property>
|
|
||||||
<item row="0" column="0">
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
|
||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
<number>5</number>
|
<number>5</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<property name="leftMargin">
|
||||||
<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 row="1" column="3">
|
|
||||||
<layout class="QGridLayout" name="gridLayout">
|
|
||||||
<property name="spacing">
|
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item row="1" column="2">
|
<property name="topMargin">
|
||||||
<widget class="QProgressBar" name="hardwareKeyProgress">
|
<number>0</number>
|
||||||
<property name="maximumSize">
|
</property>
|
||||||
<size>
|
<property name="rightMargin">
|
||||||
<width>16777215</width>
|
<number>0</number>
|
||||||
<height>2</height>
|
</property>
|
||||||
</size>
|
<property name="bottomMargin">
|
||||||
</property>
|
<number>0</number>
|
||||||
<property name="minimum">
|
</property>
|
||||||
<number>0</number>
|
<item>
|
||||||
</property>
|
<widget class="QCheckBox" name="useHardwareKeyCheckBox">
|
||||||
<property name="maximum">
|
<property name="text">
|
||||||
<number>0</number>
|
<string notr="true">Use Hardware Security Key [Serial: 11111111]</string>
|
||||||
</property>
|
|
||||||
<property name="value">
|
|
||||||
<number>-1</number>
|
|
||||||
</property>
|
|
||||||
<property name="textVisible">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="2">
|
<item>
|
||||||
<widget class="QComboBox" name="challengeResponseCombo">
|
<widget class="QComboBox" name="hardwareKeyCombo">
|
||||||
<property name="enabled">
|
<property name="enabled">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizePolicy">
|
<property name="minimumSize">
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
<size>
|
||||||
<horstretch>0</horstretch>
|
<width>200</width>
|
||||||
<verstretch>0</verstretch>
|
<height>0</height>
|
||||||
</sizepolicy>
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>300</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="accessibleName">
|
<property name="accessibleName">
|
||||||
<string>Hardware key slot selection</string>
|
<string>Hardware key slot selection</string>
|
||||||
@ -298,165 +336,110 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</widget>
|
||||||
<item row="1" column="0">
|
</item>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
<item>
|
||||||
<property name="spacing">
|
<widget class="QLabel" name="noHardwareKeysFoundLabel">
|
||||||
<number>2</number>
|
<property name="sizePolicy">
|
||||||
</property>
|
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||||
<item>
|
<horstretch>0</horstretch>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
<verstretch>0</verstretch>
|
||||||
<property name="spacing">
|
</sizepolicy>
|
||||||
<number>5</number>
|
</property>
|
||||||
</property>
|
<property name="text">
|
||||||
<item>
|
<string>No hardware keys found.</string>
|
||||||
<widget class="QLabel" name="hardwareKeyLabel">
|
</property>
|
||||||
<property name="text">
|
<property name="margin">
|
||||||
<string>Hardware Key:</string>
|
<number>1</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="buddy">
|
</widget>
|
||||||
<cstring>challengeResponseCombo</cstring>
|
</item>
|
||||||
</property>
|
<item>
|
||||||
</widget>
|
<spacer name="horizontalSpacer">
|
||||||
</item>
|
<property name="orientation">
|
||||||
<item>
|
<enum>Qt::Horizontal</enum>
|
||||||
<widget class="QToolButton" name="hardwareKeyLabelHelp">
|
</property>
|
||||||
<property name="cursor">
|
<property name="sizeHint" stdset="0">
|
||||||
<cursorShape>PointingHandCursor</cursorShape>
|
<size>
|
||||||
</property>
|
<width>40</width>
|
||||||
<property name="focusPolicy">
|
<height>0</height>
|
||||||
<enum>Qt::ClickFocus</enum>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="toolTip">
|
</spacer>
|
||||||
<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>
|
</item>
|
||||||
<p>Click for more information…</p></string>
|
<item>
|
||||||
</property>
|
<widget class="QPushButton" name="refreshHardwareKeys">
|
||||||
<property name="accessibleName">
|
<property name="cursor">
|
||||||
<string>Hardware key help</string>
|
<cursorShape>PointingHandCursor</cursorShape>
|
||||||
</property>
|
</property>
|
||||||
<property name="styleSheet">
|
<property name="toolTip">
|
||||||
<string notr="true">QToolButton {
|
<string>Refresh Hardware Keys</string>
|
||||||
border: none;
|
</property>
|
||||||
background: none;
|
<property name="accessibleName">
|
||||||
}</string>
|
<string>Refresh Hardware Keys</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="styleSheet">
|
||||||
<string notr="true">?</string>
|
<string notr="true">QPushButton { background-color: transparent; border: none; } </string>
|
||||||
</property>
|
</property>
|
||||||
<property name="iconSize">
|
<property name="text">
|
||||||
<size>
|
<string notr="true"/>
|
||||||
<width>12</width>
|
</property>
|
||||||
<height>12</height>
|
</widget>
|
||||||
</size>
|
</item>
|
||||||
</property>
|
<item>
|
||||||
<property name="popupMode">
|
<widget class="QLabel" name="addKeyFileLinkLabel">
|
||||||
<enum>QToolButton::InstantPopup</enum>
|
<property name="cursor">
|
||||||
</property>
|
<cursorShape>PointingHandCursor</cursorShape>
|
||||||
</widget>
|
</property>
|
||||||
</item>
|
<property name="focusPolicy">
|
||||||
</layout>
|
<enum>Qt::TabFocus</enum>
|
||||||
</item>
|
</property>
|
||||||
<item>
|
<property name="toolTip">
|
||||||
<spacer name="verticalSpacer_6">
|
<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!</p></string>
|
||||||
<property name="orientation">
|
</property>
|
||||||
<enum>Qt::Vertical</enum>
|
<property name="accessibleDescription">
|
||||||
</property>
|
<string>Click to add a key file.</string>
|
||||||
<property name="sizeType">
|
</property>
|
||||||
<enum>QSizePolicy::Fixed</enum>
|
<property name="text">
|
||||||
</property>
|
<string><a href="#" style="text-decoration: underline">I have a key file</a></string>
|
||||||
<property name="sizeHint" stdset="0">
|
</property>
|
||||||
<size>
|
<property name="alignment">
|
||||||
<width>0</width>
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||||
<height>2</height>
|
</property>
|
||||||
</size>
|
<property name="margin">
|
||||||
</property>
|
<number>1</number>
|
||||||
</spacer>
|
</property>
|
||||||
</item>
|
<property name="textInteractionFlags">
|
||||||
</layout>
|
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse</set>
|
||||||
</item>
|
</property>
|
||||||
<item row="0" column="3">
|
</widget>
|
||||||
<layout class="QGridLayout" name="gridLayout_2">
|
</item>
|
||||||
<property name="verticalSpacing">
|
</layout>
|
||||||
<number>0</number>
|
</widget>
|
||||||
</property>
|
</item>
|
||||||
<item row="0" column="1">
|
<item>
|
||||||
<widget class="PasswordWidget" name="keyFileLineEdit" native="true">
|
<spacer name="verticalSpacer_4">
|
||||||
<property name="sizePolicy">
|
<property name="orientation">
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
<enum>Qt::Vertical</enum>
|
||||||
<horstretch>0</horstretch>
|
</property>
|
||||||
<verstretch>0</verstretch>
|
<property name="sizeHint" stdset="0">
|
||||||
</sizepolicy>
|
<size>
|
||||||
</property>
|
<width>0</width>
|
||||||
<property name="focusPolicy">
|
<height>5</height>
|
||||||
<enum>Qt::StrongFocus</enum>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="accessibleName">
|
</spacer>
|
||||||
<string>Key file to unlock the database</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</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>
|
|
||||||
<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>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="dialogButtonsLayout">
|
<layout class="QHBoxLayout" name="dialogButtonsLayout">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
<property name="topMargin">
|
<property name="topMargin">
|
||||||
<number>15</number>
|
<number>25</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>5</number>
|
||||||
</property>
|
</property>
|
||||||
<item alignment="Qt::AlignRight">
|
<item alignment="Qt::AlignRight">
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
@ -474,17 +457,20 @@
|
|||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="quickUnlockPage">
|
<widget class="QWidget" name="quickUnlockPage">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
<property name="leftMargin">
|
<property name="leftMargin">
|
||||||
<number>20</number>
|
<number>10</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="topMargin">
|
<property name="topMargin">
|
||||||
<number>15</number>
|
<number>10</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="rightMargin">
|
<property name="rightMargin">
|
||||||
<number>20</number>
|
<number>10</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="bottomMargin">
|
<property name="bottomMargin">
|
||||||
<number>15</number>
|
<number>10</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<spacer name="horizontalSpacer_5">
|
<spacer name="horizontalSpacer_5">
|
||||||
@ -493,8 +479,8 @@
|
|||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
<width>40</width>
|
<width>30</width>
|
||||||
<height>20</height>
|
<height>0</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
@ -508,8 +494,8 @@
|
|||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
<width>20</width>
|
<width>0</width>
|
||||||
<height>40</height>
|
<height>10</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
@ -525,7 +511,6 @@
|
|||||||
<property name="font">
|
<property name="font">
|
||||||
<font>
|
<font>
|
||||||
<pointsize>10</pointsize>
|
<pointsize>10</pointsize>
|
||||||
<weight>75</weight>
|
|
||||||
<bold>true</bold>
|
<bold>true</bold>
|
||||||
</font>
|
</font>
|
||||||
</property>
|
</property>
|
||||||
@ -551,8 +536,8 @@
|
|||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
<width>20</width>
|
<width>0</width>
|
||||||
<height>40</height>
|
<height>10</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
@ -566,8 +551,8 @@
|
|||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
<width>40</width>
|
<width>30</width>
|
||||||
<height>20</height>
|
<height>0</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
@ -635,13 +620,16 @@
|
|||||||
</customwidget>
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
|
<tabstop>quickUnlockButton</tabstop>
|
||||||
|
<tabstop>resetQuickUnlockButton</tabstop>
|
||||||
<tabstop>editPassword</tabstop>
|
<tabstop>editPassword</tabstop>
|
||||||
<tabstop>keyFileLineEdit</tabstop>
|
<tabstop>keyFileLineEdit</tabstop>
|
||||||
<tabstop>buttonBrowseFile</tabstop>
|
<tabstop>buttonBrowseFile</tabstop>
|
||||||
<tabstop>challengeResponseCombo</tabstop>
|
<tabstop>useHardwareKeyCheckBox</tabstop>
|
||||||
<tabstop>buttonRedetectYubikey</tabstop>
|
<tabstop>hardwareKeyCombo</tabstop>
|
||||||
<tabstop>quickUnlockButton</tabstop>
|
<tabstop>refreshHardwareKeys</tabstop>
|
||||||
<tabstop>resetQuickUnlockButton</tabstop>
|
<tabstop>addKeyFileLinkLabel</tabstop>
|
||||||
|
<tabstop>buttonBox</tabstop>
|
||||||
</tabstops>
|
</tabstops>
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections/>
|
<connections/>
|
||||||
|
@ -16,21 +16,30 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "YubiKeyEditWidget.h"
|
#include "YubiKeyEditWidget.h"
|
||||||
|
|
||||||
#include "ui_KeyComponentWidget.h"
|
#include "ui_KeyComponentWidget.h"
|
||||||
#include "ui_YubiKeyEditWidget.h"
|
#include "ui_YubiKeyEditWidget.h"
|
||||||
|
|
||||||
#include "config-keepassx.h"
|
|
||||||
#include "core/AsyncTask.h"
|
#include "core/AsyncTask.h"
|
||||||
|
#include "gui/Icons.h"
|
||||||
#include "keys/ChallengeResponseKey.h"
|
#include "keys/ChallengeResponseKey.h"
|
||||||
#include "keys/CompositeKey.h"
|
#include "keys/CompositeKey.h"
|
||||||
|
#ifdef WITH_XC_YUBIKEY
|
||||||
|
#include "keys/drivers/YubiKeyInterfaceUSB.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
YubiKeyEditWidget::YubiKeyEditWidget(QWidget* parent)
|
YubiKeyEditWidget::YubiKeyEditWidget(QWidget* parent)
|
||||||
: KeyComponentWidget(parent)
|
: KeyComponentWidget(parent)
|
||||||
, m_compUi(new Ui::YubiKeyEditWidget())
|
, m_compUi(new Ui::YubiKeyEditWidget())
|
||||||
|
#ifdef WITH_XC_YUBIKEY
|
||||||
|
, m_deviceListener(new DeviceListener(this))
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
initComponent();
|
initComponent();
|
||||||
|
#ifdef WITH_XC_YUBIKEY
|
||||||
connect(YubiKey::instance(), SIGNAL(detectComplete(bool)), SLOT(hardwareKeyResponse(bool)), Qt::QueuedConnection);
|
connect(YubiKey::instance(), SIGNAL(detectComplete(bool)), SLOT(hardwareKeyResponse(bool)), Qt::QueuedConnection);
|
||||||
|
connect(m_deviceListener, &DeviceListener::devicePlugged, this, [&](bool, void*, void*) { pollYubikey(); });
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
YubiKeyEditWidget::~YubiKeyEditWidget() = default;
|
YubiKeyEditWidget::~YubiKeyEditWidget() = default;
|
||||||
@ -74,19 +83,48 @@ QWidget* YubiKeyEditWidget::componentEditWidget()
|
|||||||
m_compUi->yubikeyProgress->setSizePolicy(sp);
|
m_compUi->yubikeyProgress->setSizePolicy(sp);
|
||||||
m_compUi->yubikeyProgress->setVisible(false);
|
m_compUi->yubikeyProgress->setVisible(false);
|
||||||
|
|
||||||
#ifdef WITH_XC_YUBIKEY
|
|
||||||
connect(m_compUi->buttonRedetectYubikey, SIGNAL(clicked()), SLOT(pollYubikey()));
|
|
||||||
pollYubikey();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return m_compEditWidget;
|
return m_compEditWidget;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void YubiKeyEditWidget::showEvent(QShowEvent* event)
|
||||||
|
{
|
||||||
|
KeyComponentWidget::showEvent(event);
|
||||||
|
|
||||||
|
#ifdef WITH_XC_YUBIKEY
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
m_deviceListener->registerHotplugCallback(true,
|
||||||
|
true,
|
||||||
|
YubiKeyInterfaceUSB::YUBICO_USB_VID,
|
||||||
|
DeviceListener::MATCH_ANY,
|
||||||
|
&DeviceListenerWin::DEV_CLS_KEYBOARD);
|
||||||
|
m_deviceListener->registerHotplugCallback(true,
|
||||||
|
true,
|
||||||
|
YubiKeyInterfaceUSB::ONLYKEY_USB_VID,
|
||||||
|
DeviceListener::MATCH_ANY,
|
||||||
|
&DeviceListenerWin::DEV_CLS_KEYBOARD);
|
||||||
|
#else
|
||||||
|
m_deviceListener->registerHotplugCallback(true, true, YubiKeyInterfaceUSB::YUBICO_USB_VID);
|
||||||
|
m_deviceListener->registerHotplugCallback(true, true, YubiKeyInterfaceUSB::ONLYKEY_USB_VID);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void YubiKeyEditWidget::hideEvent(QHideEvent* event)
|
||||||
|
{
|
||||||
|
KeyComponentWidget::hideEvent(event);
|
||||||
|
#ifdef WITH_XC_YUBIKEY
|
||||||
|
m_deviceListener->deregisterAllHotplugCallbacks();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void YubiKeyEditWidget::initComponentEditWidget(QWidget* widget)
|
void YubiKeyEditWidget::initComponentEditWidget(QWidget* widget)
|
||||||
{
|
{
|
||||||
Q_UNUSED(widget);
|
Q_UNUSED(widget);
|
||||||
Q_ASSERT(m_compEditWidget);
|
Q_ASSERT(m_compEditWidget);
|
||||||
m_compUi->comboChallengeResponse->setFocus();
|
m_compUi->comboChallengeResponse->setFocus();
|
||||||
|
m_compUi->refreshHardwareKeys->setIcon(icons()->icon("yubikey-refresh", true));
|
||||||
|
connect(m_compUi->refreshHardwareKeys, &QPushButton::clicked, this, &YubiKeyEditWidget::pollYubikey);
|
||||||
|
pollYubikey();
|
||||||
}
|
}
|
||||||
|
|
||||||
void YubiKeyEditWidget::initComponent()
|
void YubiKeyEditWidget::initComponent()
|
||||||
@ -116,9 +154,9 @@ void YubiKeyEditWidget::pollYubikey()
|
|||||||
m_isDetected = false;
|
m_isDetected = false;
|
||||||
m_compUi->comboChallengeResponse->clear();
|
m_compUi->comboChallengeResponse->clear();
|
||||||
m_compUi->comboChallengeResponse->addItem(tr("Detecting hardware keys…"));
|
m_compUi->comboChallengeResponse->addItem(tr("Detecting hardware keys…"));
|
||||||
m_compUi->buttonRedetectYubikey->setEnabled(false);
|
|
||||||
m_compUi->comboChallengeResponse->setEnabled(false);
|
m_compUi->comboChallengeResponse->setEnabled(false);
|
||||||
m_compUi->yubikeyProgress->setVisible(true);
|
m_compUi->yubikeyProgress->setVisible(true);
|
||||||
|
m_compUi->refreshHardwareKeys->setEnabled(false);
|
||||||
|
|
||||||
YubiKey::instance()->findValidKeysAsync();
|
YubiKey::instance()->findValidKeysAsync();
|
||||||
#endif
|
#endif
|
||||||
@ -131,20 +169,22 @@ void YubiKeyEditWidget::hardwareKeyResponse(bool found)
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_compUi->comboChallengeResponse->clear();
|
m_compUi->comboChallengeResponse->clear();
|
||||||
m_compUi->buttonRedetectYubikey->setEnabled(true);
|
m_compUi->refreshHardwareKeys->setEnabled(true);
|
||||||
m_compUi->yubikeyProgress->setVisible(false);
|
|
||||||
|
|
||||||
if (!found) {
|
if (!found) {
|
||||||
|
m_compUi->yubikeyProgress->setVisible(false);
|
||||||
m_compUi->comboChallengeResponse->addItem(tr("No hardware keys detected"));
|
m_compUi->comboChallengeResponse->addItem(tr("No hardware keys detected"));
|
||||||
m_isDetected = false;
|
m_isDetected = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& slot : YubiKey::instance()->foundKeys()) {
|
const auto foundKeys = YubiKey::instance()->foundKeys();
|
||||||
|
for (auto i = foundKeys.cbegin(); i != foundKeys.cend(); ++i) {
|
||||||
// add detected YubiKey to combo box and encode blocking mode in LSB, slot number in second LSB
|
// add detected YubiKey to combo box and encode blocking mode in LSB, slot number in second LSB
|
||||||
m_compUi->comboChallengeResponse->addItem(YubiKey::instance()->getDisplayName(slot), QVariant::fromValue(slot));
|
m_compUi->comboChallengeResponse->addItem(i.value(), QVariant::fromValue(i.key()));
|
||||||
}
|
}
|
||||||
|
|
||||||
m_isDetected = true;
|
m_isDetected = true;
|
||||||
|
m_compUi->yubikeyProgress->setVisible(false);
|
||||||
m_compUi->comboChallengeResponse->setEnabled(true);
|
m_compUi->comboChallengeResponse->setEnabled(true);
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,10 @@
|
|||||||
#define KEEPASSXC_YUBIKEYEDITWIDGET_H
|
#define KEEPASSXC_YUBIKEYEDITWIDGET_H
|
||||||
|
|
||||||
#include "KeyComponentWidget.h"
|
#include "KeyComponentWidget.h"
|
||||||
|
#include "config-keepassx.h"
|
||||||
|
#ifdef WITH_XC_YUBIKEY
|
||||||
|
#include "gui/osutils/DeviceListener.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Ui
|
namespace Ui
|
||||||
{
|
{
|
||||||
@ -43,6 +47,8 @@ protected:
|
|||||||
QWidget* componentEditWidget() override;
|
QWidget* componentEditWidget() override;
|
||||||
void initComponentEditWidget(QWidget* widget) override;
|
void initComponentEditWidget(QWidget* widget) override;
|
||||||
void initComponent() override;
|
void initComponent() override;
|
||||||
|
void showEvent(QShowEvent* event) override;
|
||||||
|
void hideEvent(QHideEvent* event) override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void hardwareKeyResponse(bool found);
|
void hardwareKeyResponse(bool found);
|
||||||
@ -51,6 +57,9 @@ private slots:
|
|||||||
private:
|
private:
|
||||||
const QScopedPointer<Ui::YubiKeyEditWidget> m_compUi;
|
const QScopedPointer<Ui::YubiKeyEditWidget> m_compUi;
|
||||||
QPointer<QWidget> m_compEditWidget;
|
QPointer<QWidget> m_compEditWidget;
|
||||||
|
#ifdef WITH_XC_YUBIKEY
|
||||||
|
QPointer<DeviceListener> m_deviceListener;
|
||||||
|
#endif
|
||||||
bool m_isDetected = false;
|
bool m_isDetected = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,51 +24,84 @@
|
|||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QGridLayout" name="gridLayout_4">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
<property name="verticalSpacing">
|
<property name="spacing">
|
||||||
<number>0</number>
|
<number>6</number>
|
||||||
</property>
|
</property>
|
||||||
<item row="0" column="1">
|
<item>
|
||||||
<widget class="QPushButton" name="buttonRedetectYubikey">
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
<property name="accessibleName">
|
<property name="spacing">
|
||||||
<string>Refresh hardware tokens</string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Refresh</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QComboBox" name="comboChallengeResponse">
|
|
||||||
<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>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QProgressBar" name="yubikeyProgress">
|
|
||||||
<property name="maximumSize">
|
|
||||||
<size>
|
|
||||||
<width>16777215</width>
|
|
||||||
<height>2</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="maximum">
|
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="value">
|
<item>
|
||||||
<number>-1</number>
|
<widget class="QComboBox" name="comboChallengeResponse">
|
||||||
|
<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>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QProgressBar" name="yubikeyProgress">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>16777215</width>
|
||||||
|
<height>2</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>-1</number>
|
||||||
|
</property>
|
||||||
|
<property name="textVisible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="textVisible">
|
<item>
|
||||||
<bool>false</bool>
|
<widget class="QPushButton" name="refreshHardwareKeys">
|
||||||
</property>
|
<property name="toolTip">
|
||||||
</widget>
|
<string>Refresh hardware keys</string>
|
||||||
|
</property>
|
||||||
|
<property name="accessibleName">
|
||||||
|
<string>Refresh hardware keys</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer_2">
|
||||||
|
<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>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
@ -87,10 +120,6 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<tabstops>
|
|
||||||
<tabstop>comboChallengeResponse</tabstop>
|
|
||||||
<tabstop>buttonRedetectYubikey</tabstop>
|
|
||||||
</tabstops>
|
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections/>
|
<connections/>
|
||||||
</ui>
|
</ui>
|
||||||
|
78
src/gui/osutils/DeviceListener.cpp
Normal file
78
src/gui/osutils/DeviceListener.cpp
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 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 "DeviceListener.h"
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
DeviceListener::DeviceListener(QWidget* parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
{
|
||||||
|
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
|
||||||
|
m_listeners[0] = new DEVICELISTENER_IMPL(this);
|
||||||
|
connectSignals(m_listeners[0]);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceListener::~DeviceListener()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeviceListener::connectSignals(DEVICELISTENER_IMPL* listener)
|
||||||
|
{
|
||||||
|
connect(listener, &DEVICELISTENER_IMPL::devicePlugged, this, [&](bool state, void* ctx, void* device) {
|
||||||
|
// Wait a few ms to prevent USB device access conflicts
|
||||||
|
QTimer::singleShot(50, [&] { emit devicePlugged(state, ctx, device); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceListener::Handle
|
||||||
|
DeviceListener::registerHotplugCallback(bool arrived, bool left, int vendorId, int productId, const QUuid* deviceClass)
|
||||||
|
{
|
||||||
|
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
|
||||||
|
const Handle handle = m_listeners[0]->registerHotplugCallback(arrived, left, vendorId, productId, deviceClass);
|
||||||
|
#else
|
||||||
|
auto* listener = new DEVICELISTENER_IMPL(this);
|
||||||
|
const auto handle = reinterpret_cast<Handle>(listener);
|
||||||
|
m_listeners[handle] = listener;
|
||||||
|
m_listeners[handle]->registerHotplugCallback(arrived, left, vendorId, productId, deviceClass);
|
||||||
|
connectSignals(m_listeners[handle]);
|
||||||
|
#endif
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeviceListener::deregisterHotplugCallback(Handle handle)
|
||||||
|
{
|
||||||
|
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
|
||||||
|
m_listeners[0]->deregisterHotplugCallback(static_cast<int>(handle));
|
||||||
|
#else
|
||||||
|
if (m_listeners.contains(handle)) {
|
||||||
|
m_listeners[handle]->deregisterHotplugCallback();
|
||||||
|
m_listeners.remove(handle);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeviceListener::deregisterAllHotplugCallbacks()
|
||||||
|
{
|
||||||
|
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
|
||||||
|
m_listeners[0]->deregisterAllHotplugCallbacks();
|
||||||
|
#else
|
||||||
|
while (!m_listeners.isEmpty()) {
|
||||||
|
deregisterHotplugCallback(m_listeners.constBegin().key());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
78
src/gui/osutils/DeviceListener.h
Normal file
78
src/gui/osutils/DeviceListener.h
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 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 DEVICELISTENER_H
|
||||||
|
#define DEVICELISTENER_H
|
||||||
|
|
||||||
|
#include <QHash>
|
||||||
|
#include <QPair>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
#include "winutils/DeviceListenerWin.h"
|
||||||
|
#elif defined(Q_OS_MACOS)
|
||||||
|
#include "macutils/DeviceListenerMac.h"
|
||||||
|
#elif defined(Q_OS_UNIX)
|
||||||
|
#include "nixutils/DeviceListenerLibUsb.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class QUuid;
|
||||||
|
|
||||||
|
class DeviceListener : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
typedef qintptr Handle;
|
||||||
|
static constexpr int MATCH_ANY = -1;
|
||||||
|
|
||||||
|
explicit DeviceListener(QWidget* parent);
|
||||||
|
DeviceListener(const DeviceListener&) = delete;
|
||||||
|
~DeviceListener() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a hotplug notification callback.
|
||||||
|
*
|
||||||
|
* Fires devicePlugged() or deviceUnplugged() when the state of a matching device changes.
|
||||||
|
* The signals are supplied with the platform-specific context and ID of the firing device.
|
||||||
|
* Registering a new callback with the same DeviceListener will unregister any previous callbacks.
|
||||||
|
*
|
||||||
|
* @param arrived listen for new devices
|
||||||
|
* @param left listen for device unplug
|
||||||
|
* @param vendorId vendor ID to listen for or DeviceListener::MATCH_ANY
|
||||||
|
* @param productId product ID to listen for or DeviceListener::MATCH_ANY
|
||||||
|
* @param deviceClass device class GUID (Windows only)
|
||||||
|
* @return callback handle
|
||||||
|
*/
|
||||||
|
Handle registerHotplugCallback(bool arrived,
|
||||||
|
bool left,
|
||||||
|
int vendorId = MATCH_ANY,
|
||||||
|
int productId = MATCH_ANY,
|
||||||
|
const QUuid* deviceClass = nullptr);
|
||||||
|
void deregisterHotplugCallback(Handle handle);
|
||||||
|
void deregisterAllHotplugCallbacks();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void devicePlugged(bool state, void* ctx, void* device);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QHash<Handle, QPointer<DEVICELISTENER_IMPL>> m_listeners;
|
||||||
|
void connectSignals(DEVICELISTENER_IMPL* listener);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DEVICELISTENER_H
|
@ -26,7 +26,7 @@ class ScreenLockListener : public QObject
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ScreenLockListener(QWidget* parent = nullptr);
|
explicit ScreenLockListener(QWidget* parent);
|
||||||
~ScreenLockListener() override;
|
~ScreenLockListener() override;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
#ifndef SCREENLOCKLISTENERPRIVATE_H
|
#ifndef SCREENLOCKLISTENERPRIVATE_H
|
||||||
#define SCREENLOCKLISTENERPRIVATE_H
|
#define SCREENLOCKLISTENERPRIVATE_H
|
||||||
#include <QObject>
|
#include <QWidget>
|
||||||
|
|
||||||
class ScreenLockListenerPrivate : public QObject
|
class ScreenLockListenerPrivate : public QObject
|
||||||
{
|
{
|
||||||
@ -26,7 +26,7 @@ public:
|
|||||||
static ScreenLockListenerPrivate* instance(QWidget* parent = nullptr);
|
static ScreenLockListenerPrivate* instance(QWidget* parent = nullptr);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
ScreenLockListenerPrivate(QWidget* parent = nullptr);
|
explicit ScreenLockListenerPrivate(QWidget* parent = nullptr);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void screenLocked();
|
void screenLocked();
|
||||||
|
95
src/gui/osutils/macutils/DeviceListenerMac.cpp
Normal file
95
src/gui/osutils/macutils/DeviceListenerMac.cpp
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 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 "DeviceListenerMac.h"
|
||||||
|
|
||||||
|
#include <QPointer>
|
||||||
|
#include <IOKit/IOKitLib.h>
|
||||||
|
|
||||||
|
DeviceListenerMac::DeviceListenerMac(QObject* parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_mgr(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceListenerMac::~DeviceListenerMac()
|
||||||
|
{
|
||||||
|
if (m_mgr) {
|
||||||
|
IOHIDManagerUnscheduleFromRunLoop(m_mgr, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||||
|
IOHIDManagerClose(m_mgr, kIOHIDOptionsTypeNone);
|
||||||
|
CFRelease(m_mgr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeviceListenerMac::registerHotplugCallback(bool arrived, bool left, int vendorId, int productId, const QUuid*)
|
||||||
|
{
|
||||||
|
if (!m_mgr) {
|
||||||
|
m_mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone);
|
||||||
|
if (!m_mgr) {
|
||||||
|
qWarning("Failed to create IOHIDManager.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IOHIDManagerScheduleWithRunLoop(m_mgr, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vendorId > 0 || productId > 0) {
|
||||||
|
CFMutableDictionaryRef matchingDict = IOServiceMatching(kIOHIDDeviceKey);
|
||||||
|
if (vendorId > 0) {
|
||||||
|
auto vid = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vendorId);
|
||||||
|
CFDictionaryAddValue(matchingDict, CFSTR(kIOHIDVendorIDKey), vid);
|
||||||
|
CFRelease(vid);
|
||||||
|
}
|
||||||
|
if (productId > 0) {
|
||||||
|
auto pid = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vendorId);
|
||||||
|
CFDictionaryAddValue(matchingDict, CFSTR(kIOHIDProductIDKey), pid);
|
||||||
|
CFRelease(pid);
|
||||||
|
}
|
||||||
|
IOHIDManagerSetDeviceMatching(m_mgr, matchingDict);
|
||||||
|
CFRelease(matchingDict);
|
||||||
|
} else {
|
||||||
|
IOHIDManagerSetDeviceMatching(m_mgr, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
QPointer that = this;
|
||||||
|
if (arrived) {
|
||||||
|
IOHIDManagerRegisterDeviceMatchingCallback(m_mgr, [](void* ctx, IOReturn, void*, IOHIDDeviceRef device) {
|
||||||
|
static_cast<DeviceListenerMac*>(ctx)->onDeviceStateChanged(true, device);
|
||||||
|
}, that);
|
||||||
|
}
|
||||||
|
if (left) {
|
||||||
|
IOHIDManagerRegisterDeviceRemovalCallback(m_mgr, [](void* ctx, IOReturn, void*, IOHIDDeviceRef device) {
|
||||||
|
static_cast<DeviceListenerMac*>(ctx)->onDeviceStateChanged(true, device);
|
||||||
|
}, that);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IOHIDManagerOpen(m_mgr, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
|
||||||
|
qWarning("Could not open enumerated devices.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeviceListenerMac::deregisterHotplugCallback()
|
||||||
|
{
|
||||||
|
if (m_mgr) {
|
||||||
|
IOHIDManagerRegisterDeviceMatchingCallback(m_mgr, nullptr, this);
|
||||||
|
IOHIDManagerRegisterDeviceRemovalCallback(m_mgr, nullptr, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeviceListenerMac::onDeviceStateChanged(bool state, void* device)
|
||||||
|
{
|
||||||
|
emit devicePlugged(state, m_mgr, device);
|
||||||
|
}
|
51
src/gui/osutils/macutils/DeviceListenerMac.h
Normal file
51
src/gui/osutils/macutils/DeviceListenerMac.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 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 DEVICELISTENER_MAC_H
|
||||||
|
#define DEVICELISTENER_MAC_H
|
||||||
|
|
||||||
|
#define DEVICELISTENER_IMPL DeviceListenerMac
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <IOKit/hid/IOHIDManager.h>
|
||||||
|
|
||||||
|
class QUuid;
|
||||||
|
|
||||||
|
class DeviceListenerMac : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit DeviceListenerMac(QObject* parent);
|
||||||
|
DeviceListenerMac(const DeviceListenerMac&) = delete;
|
||||||
|
~DeviceListenerMac() override;
|
||||||
|
|
||||||
|
void registerHotplugCallback(bool arrived,
|
||||||
|
bool left,
|
||||||
|
int vendorId = -1,
|
||||||
|
int productId = -1, const QUuid* = nullptr);
|
||||||
|
void deregisterHotplugCallback();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void devicePlugged(bool state, void* ctx, void* device);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onDeviceStateChanged(bool state, void* device);
|
||||||
|
IOHIDManagerRef m_mgr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DEVICELISTENER_MAC_H
|
124
src/gui/osutils/nixutils/DeviceListenerLibUsb.cpp
Normal file
124
src/gui/osutils/nixutils/DeviceListenerLibUsb.cpp
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 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 "DeviceListenerLibUsb.h"
|
||||||
|
#include "core/Tools.h"
|
||||||
|
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QtConcurrent>
|
||||||
|
#include <libusb.h>
|
||||||
|
|
||||||
|
DeviceListenerLibUsb::DeviceListenerLibUsb(QWidget* parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_ctx(nullptr)
|
||||||
|
, m_completed(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceListenerLibUsb::~DeviceListenerLibUsb()
|
||||||
|
{
|
||||||
|
if (m_ctx) {
|
||||||
|
deregisterAllHotplugCallbacks();
|
||||||
|
libusb_exit(static_cast<libusb_context*>(m_ctx));
|
||||||
|
m_ctx = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
void handleUsbEvents(libusb_context* ctx, QAtomicInt* completed)
|
||||||
|
{
|
||||||
|
while (!*completed) {
|
||||||
|
libusb_handle_events_completed(ctx, reinterpret_cast<int*>(completed));
|
||||||
|
Tools::sleep(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int DeviceListenerLibUsb::registerHotplugCallback(bool arrived, bool left, int vendorId, int productId, const QUuid*)
|
||||||
|
{
|
||||||
|
if (!m_ctx) {
|
||||||
|
if (libusb_init(reinterpret_cast<libusb_context**>(&m_ctx)) != LIBUSB_SUCCESS) {
|
||||||
|
qWarning("Unable to initialize libusb. USB devices may not be detected properly.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int events = 0;
|
||||||
|
if (arrived) {
|
||||||
|
events |= LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED;
|
||||||
|
}
|
||||||
|
if (left) {
|
||||||
|
events |= LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
int handle = 0;
|
||||||
|
const QPointer that = this;
|
||||||
|
const int ret = libusb_hotplug_register_callback(
|
||||||
|
static_cast<libusb_context*>(m_ctx),
|
||||||
|
static_cast<libusb_hotplug_event>(events),
|
||||||
|
static_cast<libusb_hotplug_flag>(0),
|
||||||
|
vendorId,
|
||||||
|
productId,
|
||||||
|
LIBUSB_HOTPLUG_MATCH_ANY,
|
||||||
|
[](libusb_context* ctx, libusb_device* device, libusb_hotplug_event event, void* userData) -> int {
|
||||||
|
if (!ctx) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
emit static_cast<DeviceListenerLibUsb*>(userData)->devicePlugged(
|
||||||
|
event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, ctx, device);
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
that,
|
||||||
|
&handle);
|
||||||
|
if (ret != LIBUSB_SUCCESS) {
|
||||||
|
qWarning("Failed to register USB listener callback.");
|
||||||
|
handle = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_completed && m_usbEvents.isRunning()) {
|
||||||
|
// Avoid race conditions
|
||||||
|
m_usbEvents.waitForFinished();
|
||||||
|
}
|
||||||
|
if (!m_usbEvents.isRunning()) {
|
||||||
|
m_completed = false;
|
||||||
|
m_usbEvents = QtConcurrent::run(handleUsbEvents, static_cast<libusb_context*>(m_ctx), &m_completed);
|
||||||
|
}
|
||||||
|
m_callbackHandles.insert(handle);
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeviceListenerLibUsb::deregisterHotplugCallback(int handle)
|
||||||
|
{
|
||||||
|
if (!m_ctx || !m_callbackHandles.contains(handle)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
libusb_hotplug_deregister_callback(static_cast<libusb_context*>(m_ctx), handle);
|
||||||
|
m_callbackHandles.remove(handle);
|
||||||
|
|
||||||
|
if (m_callbackHandles.isEmpty() && m_usbEvents.isRunning()) {
|
||||||
|
m_completed = true;
|
||||||
|
m_usbEvents.waitForFinished();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeviceListenerLibUsb::deregisterAllHotplugCallbacks()
|
||||||
|
{
|
||||||
|
while (!m_callbackHandles.isEmpty()) {
|
||||||
|
deregisterHotplugCallback(*m_callbackHandles.constBegin());
|
||||||
|
}
|
||||||
|
}
|
53
src/gui/osutils/nixutils/DeviceListenerLibUsb.h
Normal file
53
src/gui/osutils/nixutils/DeviceListenerLibUsb.h
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 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 DEVICELISTENER_LIBUSB_H
|
||||||
|
#define DEVICELISTENER_LIBUSB_H
|
||||||
|
|
||||||
|
#define DEVICELISTENER_IMPL DeviceListenerLibUsb
|
||||||
|
|
||||||
|
#include <QAtomicInt>
|
||||||
|
#include <QFuture>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
class QUuid;
|
||||||
|
|
||||||
|
class DeviceListenerLibUsb : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit DeviceListenerLibUsb(QWidget* parent);
|
||||||
|
DeviceListenerLibUsb(const DeviceListenerLibUsb&) = delete;
|
||||||
|
~DeviceListenerLibUsb() override;
|
||||||
|
|
||||||
|
int registerHotplugCallback(bool arrived, bool left, int vendorId = -1, int productId = -1, const QUuid* = nullptr);
|
||||||
|
void deregisterHotplugCallback(int handle);
|
||||||
|
void deregisterAllHotplugCallbacks();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void devicePlugged(bool state, void* ctx, void* device);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void* m_ctx;
|
||||||
|
QSet<int> m_callbackHandles;
|
||||||
|
QFuture<void> m_usbEvents;
|
||||||
|
QAtomicInt m_completed;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DEVICELISTENER_LIBUSB_H
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
#ifndef SCREENLOCKLISTENERDBUS_H
|
#ifndef SCREENLOCKLISTENERDBUS_H
|
||||||
#define SCREENLOCKLISTENERDBUS_H
|
#define SCREENLOCKLISTENERDBUS_H
|
||||||
|
|
||||||
#include "gui/osutils/ScreenLockListenerPrivate.h"
|
#include "gui/osutils/ScreenLockListenerPrivate.h"
|
||||||
#include <QDBusMessage>
|
#include <QDBusMessage>
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ class ScreenLockListenerDBus : public ScreenLockListenerPrivate
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit ScreenLockListenerDBus(QWidget* parent = nullptr);
|
explicit ScreenLockListenerDBus(QWidget* parent);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void gnomeSessionStatusChanged(uint status);
|
void gnomeSessionStatusChanged(uint status);
|
||||||
|
105
src/gui/osutils/winutils/DeviceListenerWin.cpp
Normal file
105
src/gui/osutils/winutils/DeviceListenerWin.cpp
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 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 "DeviceListenerWin.h"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <winuser.h>
|
||||||
|
|
||||||
|
#include <dbt.h>
|
||||||
|
|
||||||
|
DeviceListenerWin::DeviceListenerWin(QWidget* parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
// Event listeners need a valid window reference
|
||||||
|
Q_ASSERT(parent);
|
||||||
|
QCoreApplication::instance()->installNativeEventFilter(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceListenerWin::~DeviceListenerWin()
|
||||||
|
{
|
||||||
|
deregisterHotplugCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeviceListenerWin::registerHotplugCallback(bool arrived,
|
||||||
|
bool left,
|
||||||
|
int vendorId,
|
||||||
|
int productId,
|
||||||
|
const QUuid* deviceClass)
|
||||||
|
{
|
||||||
|
Q_ASSERT(deviceClass);
|
||||||
|
|
||||||
|
if (m_deviceNotifyHandle) {
|
||||||
|
deregisterHotplugCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString regex = R"(^\\{2}\?\\[A-Z]+#)";
|
||||||
|
if (vendorId > 0) {
|
||||||
|
regex += QString("VID_%1&").arg(vendorId, 0, 16).toUpper();
|
||||||
|
if (productId > 0) {
|
||||||
|
regex += QString("PID_%1&").arg(productId, 0, 16).toUpper();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_deviceIdMatch = QRegularExpression(regex);
|
||||||
|
|
||||||
|
DEV_BROADCAST_DEVICEINTERFACE_W notificationFilter{
|
||||||
|
sizeof(DEV_BROADCAST_DEVICEINTERFACE_W), DBT_DEVTYP_DEVICEINTERFACE, 0u, *deviceClass, {0x00}};
|
||||||
|
auto w = reinterpret_cast<HWND>(qobject_cast<QWidget*>(parent())->winId());
|
||||||
|
m_deviceNotifyHandle = RegisterDeviceNotificationW(w, ¬ificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE);
|
||||||
|
if (!m_deviceNotifyHandle) {
|
||||||
|
qWarning("Failed to register device notification handle.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_handleArrival = arrived;
|
||||||
|
m_handleRemoval = left;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeviceListenerWin::deregisterHotplugCallback()
|
||||||
|
{
|
||||||
|
if (m_deviceNotifyHandle) {
|
||||||
|
UnregisterDeviceNotification(m_deviceNotifyHandle);
|
||||||
|
m_deviceNotifyHandle = nullptr;
|
||||||
|
m_handleArrival = false;
|
||||||
|
m_handleRemoval = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeviceListenerWin::nativeEventFilter(const QByteArray& eventType, void* message, long*)
|
||||||
|
{
|
||||||
|
if (eventType != "windows_generic_MSG") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto* m = static_cast<MSG*>(message);
|
||||||
|
if (m->message != WM_DEVICECHANGE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((m_handleArrival && m->wParam == DBT_DEVICEARRIVAL)
|
||||||
|
|| (m_handleRemoval && m->wParam == DBT_DEVICEREMOVECOMPLETE)) {
|
||||||
|
const auto pBrHdr = reinterpret_cast<PDEV_BROADCAST_HDR>(m->lParam);
|
||||||
|
const auto pDevIface = reinterpret_cast<PDEV_BROADCAST_DEVICEINTERFACE_W>(pBrHdr);
|
||||||
|
const auto name = QString::fromWCharArray(pDevIface->dbcc_name, pDevIface->dbcc_size);
|
||||||
|
if (m_deviceIdMatch.match(name).hasMatch()) {
|
||||||
|
emit devicePlugged(m->wParam == DBT_DEVICEARRIVAL, nullptr, pDevIface);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
63
src/gui/osutils/winutils/DeviceListenerWin.h
Normal file
63
src/gui/osutils/winutils/DeviceListenerWin.h
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 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 DEVICELISTENER_WIN_H
|
||||||
|
#define DEVICELISTENER_WIN_H
|
||||||
|
|
||||||
|
#define DEVICELISTENER_IMPL DeviceListenerWin
|
||||||
|
|
||||||
|
#include <QAbstractNativeEventFilter>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QUuid>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
class DeviceListenerWin : public QObject, public QAbstractNativeEventFilter
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr QUuid DEV_CLS_USB =
|
||||||
|
QUuid(0xa5dcbf10L, 0x6530, 0x11d2, 0x90, 0x1f, 0x00, 0xc0, 0x4f, 0xB9, 0x51, 0xed);
|
||||||
|
static constexpr QUuid DEV_CLS_KEYBOARD =
|
||||||
|
QUuid(0x884b96c3L, 0x56ef, 0x11d1, 0xbc, 0x8c, 0x00, 0xa0, 0xc9, 0x14, 0x05, 0xdd);
|
||||||
|
static constexpr QUuid DEV_CLS_CCID =
|
||||||
|
QUuid(0x50dd5230L, 0xba8a, 0x11d1, 0xbf, 0x5d, 0x00, 0x00, 0xf8, 0x05, 0xf5, 0x30);
|
||||||
|
|
||||||
|
explicit DeviceListenerWin(QWidget* parent);
|
||||||
|
DeviceListenerWin(const DeviceListenerWin&) = delete;
|
||||||
|
~DeviceListenerWin() override;
|
||||||
|
|
||||||
|
void registerHotplugCallback(bool arrived,
|
||||||
|
bool left,
|
||||||
|
int vendorId = -1,
|
||||||
|
int productId = -1,
|
||||||
|
const QUuid* deviceClass = nullptr);
|
||||||
|
void deregisterHotplugCallback();
|
||||||
|
|
||||||
|
bool nativeEventFilter(const QByteArray& eventType, void* message, long*) override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void devicePlugged(bool state, void* ctx, void* device);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void* m_deviceNotifyHandle = nullptr;
|
||||||
|
bool m_handleArrival = false;
|
||||||
|
bool m_handleRemoval = false;
|
||||||
|
QRegularExpression m_deviceIdMatch;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DEVICELISTENER_WIN_H
|
@ -17,8 +17,8 @@
|
|||||||
|
|
||||||
#ifndef SCREENLOCKLISTENERWIN_H
|
#ifndef SCREENLOCKLISTENERWIN_H
|
||||||
#define SCREENLOCKLISTENERWIN_H
|
#define SCREENLOCKLISTENERWIN_H
|
||||||
|
|
||||||
#include <QAbstractNativeEventFilter>
|
#include <QAbstractNativeEventFilter>
|
||||||
#include <QObject>
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
#include "gui/osutils/ScreenLockListenerPrivate.h"
|
#include "gui/osutils/ScreenLockListenerPrivate.h"
|
||||||
@ -27,9 +27,9 @@ class ScreenLockListenerWin : public ScreenLockListenerPrivate, public QAbstract
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit ScreenLockListenerWin(QWidget* parent = nullptr);
|
explicit ScreenLockListenerWin(QWidget* parent);
|
||||||
~ScreenLockListenerWin();
|
~ScreenLockListenerWin();
|
||||||
bool nativeEventFilter(const QByteArray& eventType, void* message, long*) override;
|
virtual bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void* m_powerNotificationHandle;
|
void* m_powerNotificationHandle;
|
||||||
|
@ -20,10 +20,13 @@
|
|||||||
#include "YubiKeyInterfacePCSC.h"
|
#include "YubiKeyInterfacePCSC.h"
|
||||||
#include "YubiKeyInterfaceUSB.h"
|
#include "YubiKeyInterfaceUSB.h"
|
||||||
|
|
||||||
|
#include <QMutexLocker>
|
||||||
|
#include <QSet>
|
||||||
#include <QtConcurrent>
|
#include <QtConcurrent>
|
||||||
|
|
||||||
|
QMutex YubiKey::s_interfaceMutex(QMutex::Recursive);
|
||||||
|
|
||||||
YubiKey::YubiKey()
|
YubiKey::YubiKey()
|
||||||
: m_interfaces_detect_mutex(QMutex::Recursive)
|
|
||||||
{
|
{
|
||||||
int num_interfaces = 0;
|
int num_interfaces = 0;
|
||||||
|
|
||||||
@ -70,78 +73,39 @@ bool YubiKey::isInitialized()
|
|||||||
|
|
||||||
bool YubiKey::findValidKeys()
|
bool YubiKey::findValidKeys()
|
||||||
{
|
{
|
||||||
bool found = false;
|
QMutexLocker lock(&s_interfaceMutex);
|
||||||
if (m_interfaces_detect_mutex.tryLock(1000)) {
|
|
||||||
found |= YubiKeyInterfaceUSB::instance()->findValidKeys();
|
m_usbKeys = YubiKeyInterfaceUSB::instance()->findValidKeys();
|
||||||
found |= YubiKeyInterfacePCSC::instance()->findValidKeys();
|
m_pcscKeys = YubiKeyInterfacePCSC::instance()->findValidKeys();
|
||||||
m_interfaces_detect_mutex.unlock();
|
|
||||||
}
|
return !m_usbKeys.isEmpty() || !m_pcscKeys.isEmpty();
|
||||||
return found;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void YubiKey::findValidKeysAsync()
|
void YubiKey::findValidKeysAsync()
|
||||||
{
|
{
|
||||||
QtConcurrent::run([this] {
|
QtConcurrent::run([this] { emit detectComplete(findValidKeys()); });
|
||||||
bool found = findValidKeys();
|
|
||||||
emit detectComplete(found);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<YubiKeySlot> YubiKey::foundKeys()
|
YubiKey::KeyMap YubiKey::foundKeys()
|
||||||
{
|
{
|
||||||
QList<YubiKeySlot> foundKeys;
|
QMutexLocker lock(&s_interfaceMutex);
|
||||||
|
KeyMap foundKeys;
|
||||||
|
|
||||||
auto keys = YubiKeyInterfaceUSB::instance()->foundKeys();
|
for (auto i = m_usbKeys.cbegin(); i != m_usbKeys.cend(); ++i) {
|
||||||
QList<unsigned int> handledSerials = keys.uniqueKeys();
|
foundKeys.insert(i.key(), i.value());
|
||||||
for (auto serial : handledSerials) {
|
|
||||||
for (const auto& key : keys.values(serial)) {
|
|
||||||
foundKeys.append({serial, key.first});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
keys = YubiKeyInterfacePCSC::instance()->foundKeys();
|
for (auto i = m_pcscKeys.cbegin(); i != m_pcscKeys.cend(); ++i) {
|
||||||
for (auto serial : keys.uniqueKeys()) {
|
foundKeys.insert(i.key(), i.value());
|
||||||
// Ignore keys that were detected on USB interface already
|
|
||||||
if (handledSerials.contains(serial)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& key : keys.values(serial)) {
|
|
||||||
foundKeys.append({serial, key.first});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return foundKeys;
|
return foundKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString YubiKey::getDisplayName(YubiKeySlot slot)
|
|
||||||
{
|
|
||||||
QString name;
|
|
||||||
name.clear();
|
|
||||||
|
|
||||||
if (YubiKeyInterfaceUSB::instance()->hasFoundKey(slot)) {
|
|
||||||
name += YubiKeyInterfaceUSB::instance()->getDisplayName(slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (YubiKeyInterfacePCSC::instance()->hasFoundKey(slot)) {
|
|
||||||
// In some cases, the key might present on two interfaces
|
|
||||||
// This should usually never happen, because the PCSC interface
|
|
||||||
// filters the "virtual yubikey reader device".
|
|
||||||
if (!name.isNull()) {
|
|
||||||
name += " = ";
|
|
||||||
}
|
|
||||||
name += YubiKeyInterfacePCSC::instance()->getDisplayName(slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!name.isNull()) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tr("%1 No interface, slot %2").arg(QString::number(slot.first), QString::number(slot.second));
|
|
||||||
}
|
|
||||||
|
|
||||||
QString YubiKey::errorMessage()
|
QString YubiKey::errorMessage()
|
||||||
{
|
{
|
||||||
|
QMutexLocker lock(&s_interfaceMutex);
|
||||||
|
|
||||||
QString error;
|
QString error;
|
||||||
error.clear();
|
error.clear();
|
||||||
if (!m_error.isNull()) {
|
if (!m_error.isNull()) {
|
||||||
@ -177,11 +141,13 @@ QString YubiKey::errorMessage()
|
|||||||
*/
|
*/
|
||||||
bool YubiKey::testChallenge(YubiKeySlot slot, bool* wouldBlock)
|
bool YubiKey::testChallenge(YubiKeySlot slot, bool* wouldBlock)
|
||||||
{
|
{
|
||||||
if (YubiKeyInterfaceUSB::instance()->hasFoundKey(slot)) {
|
QMutexLocker lock(&s_interfaceMutex);
|
||||||
|
|
||||||
|
if (m_usbKeys.contains(slot)) {
|
||||||
return YubiKeyInterfaceUSB::instance()->testChallenge(slot, wouldBlock);
|
return YubiKeyInterfaceUSB::instance()->testChallenge(slot, wouldBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (YubiKeyInterfacePCSC::instance()->hasFoundKey(slot)) {
|
if (m_pcscKeys.contains(slot)) {
|
||||||
return YubiKeyInterfacePCSC::instance()->testChallenge(slot, wouldBlock);
|
return YubiKeyInterfacePCSC::instance()->testChallenge(slot, wouldBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,23 +166,25 @@ bool YubiKey::testChallenge(YubiKeySlot slot, bool* wouldBlock)
|
|||||||
YubiKey::ChallengeResult
|
YubiKey::ChallengeResult
|
||||||
YubiKey::challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response)
|
YubiKey::challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response)
|
||||||
{
|
{
|
||||||
|
QMutexLocker lock(&s_interfaceMutex);
|
||||||
|
|
||||||
m_error.clear();
|
m_error.clear();
|
||||||
|
|
||||||
// Make sure we tried to find available keys
|
// Make sure we tried to find available keys
|
||||||
if (foundKeys().isEmpty()) {
|
if (m_usbKeys.isEmpty() && m_pcscKeys.isEmpty()) {
|
||||||
findValidKeys();
|
findValidKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (YubiKeyInterfaceUSB::instance()->hasFoundKey(slot)) {
|
if (m_usbKeys.contains(slot)) {
|
||||||
return YubiKeyInterfaceUSB::instance()->challenge(slot, challenge, response);
|
return YubiKeyInterfaceUSB::instance()->challenge(slot, challenge, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (YubiKeyInterfacePCSC::instance()->hasFoundKey(slot)) {
|
if (m_pcscKeys.contains(slot)) {
|
||||||
return YubiKeyInterfacePCSC::instance()->challenge(slot, challenge, response);
|
return YubiKeyInterfacePCSC::instance()->challenge(slot, challenge, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_error = tr("Could not find interface for hardware key with serial number %1. Please connect it to continue.")
|
m_error = tr("Could not find interface for hardware key with serial number %1. Please connect it to continue.")
|
||||||
.arg(slot.first);
|
.arg(slot.first);
|
||||||
|
|
||||||
return YubiKey::ChallengeResult::YCR_ERROR;
|
return ChallengeResult::YCR_ERROR;
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#define KEEPASSX_YUBIKEY_H
|
#define KEEPASSX_YUBIKEY_H
|
||||||
|
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
#include <QMultiMap>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
@ -36,6 +37,7 @@ class YubiKey : public QObject
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
typedef QMap<YubiKeySlot, QString> KeyMap;
|
||||||
enum class ChallengeResult : int
|
enum class ChallengeResult : int
|
||||||
{
|
{
|
||||||
YCR_ERROR = 0,
|
YCR_ERROR = 0,
|
||||||
@ -49,8 +51,7 @@ public:
|
|||||||
bool findValidKeys();
|
bool findValidKeys();
|
||||||
void findValidKeysAsync();
|
void findValidKeysAsync();
|
||||||
|
|
||||||
QList<YubiKeySlot> foundKeys();
|
KeyMap foundKeys();
|
||||||
QString getDisplayName(YubiKeySlot slot);
|
|
||||||
|
|
||||||
ChallengeResult challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response);
|
ChallengeResult challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response);
|
||||||
bool testChallenge(YubiKeySlot slot, bool* wouldBlock = nullptr);
|
bool testChallenge(YubiKeySlot slot, bool* wouldBlock = nullptr);
|
||||||
@ -85,7 +86,11 @@ private:
|
|||||||
QTimer m_interactionTimer;
|
QTimer m_interactionTimer;
|
||||||
bool m_initialized = false;
|
bool m_initialized = false;
|
||||||
QString m_error;
|
QString m_error;
|
||||||
QMutex m_interfaces_detect_mutex;
|
|
||||||
|
static QMutex s_interfaceMutex;
|
||||||
|
|
||||||
|
KeyMap m_usbKeys;
|
||||||
|
KeyMap m_pcscKeys;
|
||||||
|
|
||||||
Q_DISABLE_COPY(YubiKey)
|
Q_DISABLE_COPY(YubiKey)
|
||||||
};
|
};
|
||||||
|
@ -19,10 +19,7 @@
|
|||||||
#include "YubiKeyInterface.h"
|
#include "YubiKeyInterface.h"
|
||||||
|
|
||||||
YubiKeyInterface::YubiKeyInterface()
|
YubiKeyInterface::YubiKeyInterface()
|
||||||
: m_mutex(QMutex::Recursive)
|
|
||||||
{
|
{
|
||||||
m_interactionTimer.setSingleShot(true);
|
|
||||||
m_interactionTimer.setInterval(300);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool YubiKeyInterface::isInitialized() const
|
bool YubiKeyInterface::isInitialized() const
|
||||||
@ -30,36 +27,6 @@ bool YubiKeyInterface::isInitialized() const
|
|||||||
return m_initialized;
|
return m_initialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
QMultiMap<unsigned int, QPair<int, QString>> YubiKeyInterface::foundKeys()
|
|
||||||
{
|
|
||||||
return m_foundKeys;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool YubiKeyInterface::hasFoundKey(YubiKeySlot slot)
|
|
||||||
{
|
|
||||||
// A serial number of 0 implies use the first key
|
|
||||||
if (slot.first == 0 && !m_foundKeys.isEmpty()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& key : m_foundKeys.values(slot.first)) {
|
|
||||||
if (slot.second == key.first) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString YubiKeyInterface::getDisplayName(YubiKeySlot slot)
|
|
||||||
{
|
|
||||||
for (const auto& key : m_foundKeys.values(slot.first)) {
|
|
||||||
if (slot.second == key.first) {
|
|
||||||
return key.second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tr("%1 Invalid slot specified - %2").arg(QString::number(slot.first), QString::number(slot.second));
|
|
||||||
}
|
|
||||||
|
|
||||||
QString YubiKeyInterface::errorMessage()
|
QString YubiKeyInterface::errorMessage()
|
||||||
{
|
{
|
||||||
return m_error;
|
return m_error;
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
#define KEEPASSX_YUBIKEY_INTERFACE_H
|
#define KEEPASSX_YUBIKEY_INTERFACE_H
|
||||||
|
|
||||||
#include "YubiKey.h"
|
#include "YubiKey.h"
|
||||||
|
|
||||||
#include <QMultiMap>
|
#include <QMultiMap>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,11 +31,8 @@ class YubiKeyInterface : public QObject
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
bool isInitialized() const;
|
bool isInitialized() const;
|
||||||
QMultiMap<unsigned int, QPair<int, QString>> foundKeys();
|
|
||||||
bool hasFoundKey(YubiKeySlot slot);
|
|
||||||
QString getDisplayName(YubiKeySlot slot);
|
|
||||||
|
|
||||||
virtual bool findValidKeys() = 0;
|
virtual YubiKey::KeyMap findValidKeys() = 0;
|
||||||
virtual YubiKey::ChallengeResult
|
virtual YubiKey::ChallengeResult
|
||||||
challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response) = 0;
|
challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response) = 0;
|
||||||
virtual bool testChallenge(YubiKeySlot slot, bool* wouldBlock) = 0;
|
virtual bool testChallenge(YubiKeySlot slot, bool* wouldBlock) = 0;
|
||||||
@ -60,7 +56,6 @@ signals:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
explicit YubiKeyInterface();
|
explicit YubiKeyInterface();
|
||||||
|
|
||||||
virtual YubiKey::ChallengeResult performChallenge(void* key,
|
virtual YubiKey::ChallengeResult performChallenge(void* key,
|
||||||
int slot,
|
int slot,
|
||||||
bool mayBlock,
|
bool mayBlock,
|
||||||
@ -68,10 +63,6 @@ protected:
|
|||||||
Botan::secure_vector<char>& response) = 0;
|
Botan::secure_vector<char>& response) = 0;
|
||||||
virtual bool performTestChallenge(void* key, int slot, bool* wouldBlock) = 0;
|
virtual bool performTestChallenge(void* key, int slot, bool* wouldBlock) = 0;
|
||||||
|
|
||||||
QMultiMap<unsigned int, QPair<int, QString>> m_foundKeys;
|
|
||||||
|
|
||||||
QMutex m_mutex;
|
|
||||||
QTimer m_interactionTimer;
|
|
||||||
bool m_initialized = false;
|
bool m_initialized = false;
|
||||||
QString m_error;
|
QString m_error;
|
||||||
|
|
||||||
|
@ -466,40 +466,6 @@ namespace
|
|||||||
return SCARD_E_NO_SMARTCARD;
|
return SCARD_E_NO_SMARTCARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
/***
|
|
||||||
* @brief Reads the status of a key
|
|
||||||
*
|
|
||||||
* The status is used for the firmware version only atm.
|
|
||||||
*
|
|
||||||
* @param handle Smartcard handle and applet ID bytestring pair
|
|
||||||
* @param version The firmware version in [major, minor, patch] format
|
|
||||||
*
|
|
||||||
* @return SCARD_S_SUCCESS on success
|
|
||||||
*/
|
|
||||||
RETVAL getStatus(const SCardAID& handle, uint8_t version[3])
|
|
||||||
{
|
|
||||||
// Ensure the transmission is retransmitted after card resets
|
|
||||||
return transactRetry(handle.first, [&handle, &version]() {
|
|
||||||
auto rv = selectApplet(handle);
|
|
||||||
|
|
||||||
// Ensure that the card is always selected before sending the command
|
|
||||||
if (rv != SCARD_S_SUCCESS) {
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t pbSendBuffer[5] = {CLA_ISO, INS_STATUS, 0, 0, 6};
|
|
||||||
uint8_t pbRecvBuffer[8] = {0}; // 4 bytes serial, 2 bytes other stuff, 2 bytes status
|
|
||||||
SCUINT dwRecvLength = 8;
|
|
||||||
|
|
||||||
rv = transmit(handle.first, pbSendBuffer, 5, pbRecvBuffer, dwRecvLength);
|
|
||||||
if (rv == SCARD_S_SUCCESS && dwRecvLength >= 3) {
|
|
||||||
memcpy(version, pbRecvBuffer, 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rv;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* @brief Performs a challenge-response transmission
|
* @brief Performs a challenge-response transmission
|
||||||
*
|
*
|
||||||
@ -575,19 +541,17 @@ YubiKeyInterfacePCSC* YubiKeyInterfacePCSC::instance()
|
|||||||
return m_instance;
|
return m_instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool YubiKeyInterfacePCSC::findValidKeys()
|
YubiKey::KeyMap YubiKeyInterfacePCSC::findValidKeys()
|
||||||
{
|
{
|
||||||
m_error.clear();
|
m_error.clear();
|
||||||
if (!isInitialized()) {
|
if (!isInitialized()) {
|
||||||
return false;
|
return {};
|
||||||
}
|
}
|
||||||
// Remove all known keys
|
|
||||||
m_foundKeys.clear();
|
YubiKey::KeyMap foundKeys;
|
||||||
|
|
||||||
// Connect to each reader and look for cards
|
// Connect to each reader and look for cards
|
||||||
auto readers_list = getReaders(m_sc_context);
|
for (const auto& reader_name : getReaders(m_sc_context)) {
|
||||||
foreach (const QString& reader_name, readers_list) {
|
|
||||||
|
|
||||||
/* Some Yubikeys present their PCSC interface via USB as well
|
/* Some Yubikeys present their PCSC interface via USB as well
|
||||||
Although this would not be a problem in itself,
|
Although this would not be a problem in itself,
|
||||||
we filter these connections because in USB mode,
|
we filter these connections because in USB mode,
|
||||||
@ -608,65 +572,70 @@ bool YubiKeyInterfacePCSC::findValidKeys()
|
|||||||
&hCard,
|
&hCard,
|
||||||
&dwActiveProtocol);
|
&dwActiveProtocol);
|
||||||
|
|
||||||
if (rv == SCARD_S_SUCCESS) {
|
if (rv != SCARD_S_SUCCESS) {
|
||||||
// Read the protocol and the ATR record
|
// Cannot connect to the reader
|
||||||
char pbReader[MAX_READERNAME] = {0};
|
continue;
|
||||||
SCUINT dwReaderLen = sizeof(pbReader);
|
}
|
||||||
SCUINT dwState = 0;
|
|
||||||
SCUINT dwProt = SCARD_PROTOCOL_UNDEFINED;
|
|
||||||
uint8_t pbAtr[MAX_ATR_SIZE] = {0};
|
|
||||||
SCUINT dwAtrLen = sizeof(pbAtr);
|
|
||||||
|
|
||||||
rv = SCardStatus(hCard, pbReader, &dwReaderLen, &dwState, &dwProt, pbAtr, &dwAtrLen);
|
// Read the protocol and the ATR record
|
||||||
if (rv == SCARD_S_SUCCESS && (dwProt == SCARD_PROTOCOL_T0 || dwProt == SCARD_PROTOCOL_T1)) {
|
char pbReader[MAX_READERNAME] = {0};
|
||||||
// Find which AID to use
|
SCUINT dwReaderLen = sizeof(pbReader);
|
||||||
SCardAID satr;
|
SCUINT dwState = 0;
|
||||||
if (findAID(hCard, m_aid_codes, satr)) {
|
SCUINT dwProt = SCARD_PROTOCOL_UNDEFINED;
|
||||||
// Build the UI name using the display name found in the ATR map
|
uint8_t pbAtr[MAX_ATR_SIZE] = {0};
|
||||||
QByteArray atr(reinterpret_cast<char*>(pbAtr), dwAtrLen);
|
SCUINT dwAtrLen = sizeof(pbAtr);
|
||||||
QString name("Unknown Key");
|
|
||||||
if (m_atr_names.contains(atr)) {
|
|
||||||
name = m_atr_names.value(atr);
|
|
||||||
}
|
|
||||||
// Add the firmware version and the serial number
|
|
||||||
uint8_t version[3] = {0};
|
|
||||||
getStatus(satr, version);
|
|
||||||
name +=
|
|
||||||
QString(" v%1.%2.%3")
|
|
||||||
.arg(QString::number(version[0]), QString::number(version[1]), QString::number(version[2]));
|
|
||||||
|
|
||||||
unsigned int serial = 0;
|
rv = SCardStatus(hCard, pbReader, &dwReaderLen, &dwState, &dwProt, pbAtr, &dwAtrLen);
|
||||||
getSerial(satr, serial);
|
if (rv != SCARD_S_SUCCESS || (dwProt != SCARD_PROTOCOL_T0 && dwProt != SCARD_PROTOCOL_T1)) {
|
||||||
|
// Could not read the ATR record or the protocol is not supported
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
/* This variable indicates that the key is locked / timed out.
|
// Find which AID to use
|
||||||
When using the key via NFC, the user has to re-present the key to clear the timeout.
|
SCardAID satr;
|
||||||
Also, the key can be programmatically reset (see below).
|
if (findAID(hCard, m_aid_codes, satr)) {
|
||||||
When using the key via USB (where the Yubikey presents as a PCSC reader in itself),
|
// Build the UI name using the display name found in the ATR map
|
||||||
the non-HMAC-SHA1 slots (eg. OTP) are incorrectly recognized as locked HMAC-SHA1 slots.
|
QByteArray atr(reinterpret_cast<char*>(pbAtr), dwAtrLen);
|
||||||
Due to this conundrum, we exclude "locked" keys from the key enumeration,
|
QString name("Unknown Key");
|
||||||
but only if the reader is the "virtual yubikey reader device".
|
if (m_atr_names.contains(atr)) {
|
||||||
This also has the nice side effect of de-duplicating interfaces when a key
|
name = m_atr_names.value(atr);
|
||||||
Is connected via USB and also accessible via PCSC */
|
}
|
||||||
bool wouldBlock = false;
|
|
||||||
/* When the key is used via NFC, the lock state / time-out is cleared when
|
unsigned int serial = 0;
|
||||||
the smartcard connection is re-established / the applet is selected
|
getSerial(satr, serial);
|
||||||
so the next call to performTestChallenge actually clears the lock.
|
|
||||||
Due to this the key is unlocked, and we display it as such.
|
/* This variable indicates that the key is locked / timed out.
|
||||||
When the key times out in the time between the key listing and
|
When using the key via NFC, the user has to re-present the key to clear the timeout.
|
||||||
the database unlock /save, an interaction request will be displayed. */
|
Also, the key can be programmatically reset (see below).
|
||||||
for (int slot = 1; slot <= 2; ++slot) {
|
When using the key via USB (where the Yubikey presents as a PCSC reader in itself),
|
||||||
if (performTestChallenge(&satr, slot, &wouldBlock)) {
|
the non-HMAC-SHA1 slots (eg. OTP) are incorrectly recognized as locked HMAC-SHA1 slots.
|
||||||
auto display = tr("(PCSC) %1 [%2] Challenge-Response - Slot %3")
|
Due to this conundrum, we exclude "locked" keys from the key enumeration,
|
||||||
.arg(name, QString::number(serial), QString::number(slot));
|
but only if the reader is the "virtual yubikey reader device".
|
||||||
m_foundKeys.insert(serial, {slot, display});
|
This also has the nice side effect of de-duplicating interfaces when a key
|
||||||
}
|
Is connected via USB and also accessible via PCSC */
|
||||||
}
|
bool wouldBlock = false;
|
||||||
|
/* When the key is used via NFC, the lock state / time-out is cleared when
|
||||||
|
the smartcard connection is re-established / the applet is selected
|
||||||
|
so the next call to performTestChallenge actually clears the lock.
|
||||||
|
Due to this the key is unlocked, and we display it as such.
|
||||||
|
When the key times out in the time between the key listing and
|
||||||
|
the database unlock /save, an interaction request will be displayed. */
|
||||||
|
for (int slot = 1; slot <= 2; ++slot) {
|
||||||
|
if (performTestChallenge(&satr, slot, &wouldBlock)) {
|
||||||
|
auto display =
|
||||||
|
tr("(NFC) %1 [%2] - Slot %3, %4", "YubiKey display fields")
|
||||||
|
.arg(name,
|
||||||
|
QString::number(serial),
|
||||||
|
QString::number(slot),
|
||||||
|
wouldBlock ? tr("Press", "USB Challenge-Response Key interaction request")
|
||||||
|
: tr("Passive", "USB Challenge-Response Key no interaction required"));
|
||||||
|
foundKeys.insert({serial, slot}, display);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return !m_foundKeys.isEmpty();
|
return foundKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool YubiKeyInterfacePCSC::testChallenge(YubiKeySlot slot, bool* wouldBlock)
|
bool YubiKeyInterfacePCSC::testChallenge(YubiKeySlot slot, bool* wouldBlock)
|
||||||
@ -707,12 +676,6 @@ YubiKeyInterfacePCSC::challenge(YubiKeySlot slot, const QByteArray& challenge, B
|
|||||||
return YubiKey::ChallengeResult::YCR_ERROR;
|
return YubiKey::ChallengeResult::YCR_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to grab a lock for 1 second, fail out if not possible
|
|
||||||
if (!m_mutex.tryLock(1000)) {
|
|
||||||
m_error = tr("Hardware key is currently in use.");
|
|
||||||
return YubiKey::ChallengeResult::YCR_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try for a few seconds to find the key
|
// Try for a few seconds to find the key
|
||||||
emit challengeStarted();
|
emit challengeStarted();
|
||||||
|
|
||||||
@ -732,7 +695,6 @@ YubiKeyInterfacePCSC::challenge(YubiKeySlot slot, const QByteArray& challenge, B
|
|||||||
resets the key (see comment above) */
|
resets the key (see comment above) */
|
||||||
if (ret == YubiKey::ChallengeResult::YCR_SUCCESS) {
|
if (ret == YubiKey::ChallengeResult::YCR_SUCCESS) {
|
||||||
emit challengeCompleted();
|
emit challengeCompleted();
|
||||||
m_mutex.unlock();
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -746,7 +708,6 @@ YubiKeyInterfacePCSC::challenge(YubiKeySlot slot, const QByteArray& challenge, B
|
|||||||
.arg(slot.first)
|
.arg(slot.first)
|
||||||
+ m_error;
|
+ m_error;
|
||||||
emit challengeCompleted();
|
emit challengeCompleted();
|
||||||
m_mutex.unlock();
|
|
||||||
return YubiKey::ChallengeResult::YCR_ERROR;
|
return YubiKey::ChallengeResult::YCR_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ class YubiKeyInterfacePCSC : public YubiKeyInterface
|
|||||||
public:
|
public:
|
||||||
static YubiKeyInterfacePCSC* instance();
|
static YubiKeyInterfacePCSC* instance();
|
||||||
|
|
||||||
bool findValidKeys() override;
|
YubiKey::KeyMap findValidKeys() override;
|
||||||
|
|
||||||
YubiKey::ChallengeResult
|
YubiKey::ChallengeResult
|
||||||
challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response) override;
|
challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response) override;
|
||||||
@ -71,7 +71,7 @@ private:
|
|||||||
Botan::secure_vector<char>& response) override;
|
Botan::secure_vector<char>& response) override;
|
||||||
bool performTestChallenge(void* key, int slot, bool* wouldBlock) override;
|
bool performTestChallenge(void* key, int slot, bool* wouldBlock) override;
|
||||||
|
|
||||||
SCARDCONTEXT m_sc_context;
|
SCARDCONTEXT m_sc_context{};
|
||||||
|
|
||||||
// This list contains all the AID (application identifier) codes for the Yubikey HMAC-SHA1 applet
|
// This list contains all the AID (application identifier) codes for the Yubikey HMAC-SHA1 applet
|
||||||
// and also for compatible third-party ones. They will be tried one by one.
|
// and also for compatible third-party ones. They will be tried one by one.
|
||||||
@ -86,16 +86,13 @@ private:
|
|||||||
const QHash<QByteArray, QString> m_atr_names = {
|
const QHash<QByteArray, QString> m_atr_names = {
|
||||||
// Yubico Yubikeys
|
// Yubico Yubikeys
|
||||||
{QByteArrayLiteral("\x3B\x8C\x80\x01\x59\x75\x62\x69\x6B\x65\x79\x4E\x45\x4F\x72\x33\x58"), "YubiKey NEO"},
|
{QByteArrayLiteral("\x3B\x8C\x80\x01\x59\x75\x62\x69\x6B\x65\x79\x4E\x45\x4F\x72\x33\x58"), "YubiKey NEO"},
|
||||||
{QByteArrayLiteral("\x3B\x8C\x80\x01\x59\x75\x62\x69\x6B\x65\x79\x4E\x45\x4F\x72\xFF\x94"),
|
{QByteArrayLiteral("\x3B\x8C\x80\x01\x59\x75\x62\x69\x6B\x65\x79\x4E\x45\x4F\x72\xFF\x94"), "YubiKey NEO"},
|
||||||
"YubiKey NEO via NFC"},
|
{QByteArrayLiteral("\x3B\x8D\x80\x01\x80\x73\xC0\x21\xC0\x57\x59\x75\x62\x69\x4B\x65\x79\xF9"), "YubiKey 5"},
|
||||||
{QByteArrayLiteral("\x3B\x8D\x80\x01\x80\x73\xC0\x21\xC0\x57\x59\x75\x62\x69\x4B\x65\x79\xF9"),
|
{QByteArrayLiteral("\x3B\x8D\x80\x01\x80\x73\xC0\x21\xC0\x57\x59\x75\x62\x69\x4B\x65\xFF\x7F"), "YubiKey 5"},
|
||||||
"YubiKey 5 NFC via NFC"},
|
|
||||||
{QByteArrayLiteral("\x3B\x8D\x80\x01\x80\x73\xC0\x21\xC0\x57\x59\x75\x62\x69\x4B\x65\xFF\x7F"),
|
|
||||||
"YubiKey 5 NFC via ACR122U"},
|
|
||||||
{QByteArrayLiteral("\x3B\xF8\x13\x00\x00\x81\x31\xFE\x15\x59\x75\x62\x69\x6B\x65\x79\x34\xD4"),
|
{QByteArrayLiteral("\x3B\xF8\x13\x00\x00\x81\x31\xFE\x15\x59\x75\x62\x69\x6B\x65\x79\x34\xD4"),
|
||||||
"YubiKey 4 OTP+CCID"},
|
"YubiKey 4 - OTP+CCID"},
|
||||||
{QByteArrayLiteral("\x3B\xF9\x18\x00\xFF\x81\x31\xFE\x45\x50\x56\x5F\x4A\x33\x41\x30\x34\x30\x40"),
|
{QByteArrayLiteral("\x3B\xF9\x18\x00\xFF\x81\x31\xFE\x45\x50\x56\x5F\x4A\x33\x41\x30\x34\x30\x40"),
|
||||||
"YubiKey NEO OTP+U2F+CCID (PKI)"},
|
"YubiKey NEO - OTP+U2F+CCID (PKI)"},
|
||||||
{QByteArrayLiteral("\x3B\xFA\x13\x00\x00\x81\x31\xFE\x15\x59\x75\x62\x69\x6B\x65\x79\x4E\x45\x4F\xA6"),
|
{QByteArrayLiteral("\x3B\xFA\x13\x00\x00\x81\x31\xFE\x15\x59\x75\x62\x69\x6B\x65\x79\x4E\x45\x4F\xA6"),
|
||||||
"YubiKey NEO"},
|
"YubiKey NEO"},
|
||||||
{QByteArrayLiteral("\x3B\xFC\x13\x00\x00\x81\x31\xFE\x15\x59\x75\x62\x69\x6B\x65\x79\x4E\x45\x4F\x72\x33\xE1"),
|
{QByteArrayLiteral("\x3B\xFC\x13\x00\x00\x81\x31\xFE\x15\x59\x75\x62\x69\x6B\x65\x79\x4E\x45\x4F\x72\x33\xE1"),
|
||||||
@ -104,7 +101,7 @@ private:
|
|||||||
"YubiKey NEO"},
|
"YubiKey NEO"},
|
||||||
{QByteArrayLiteral(
|
{QByteArrayLiteral(
|
||||||
"\x3B\xFD\x13\x00\x00\x81\x31\xFE\x15\x80\x73\xC0\x21\xC0\x57\x59\x75\x62\x69\x4B\x65\x79\x40"),
|
"\x3B\xFD\x13\x00\x00\x81\x31\xFE\x15\x80\x73\xC0\x21\xC0\x57\x59\x75\x62\x69\x4B\x65\x79\x40"),
|
||||||
"YubiKey 5 NFC (PKI)"},
|
"YubiKey 5 (PKI)"},
|
||||||
{QByteArrayLiteral(
|
{QByteArrayLiteral(
|
||||||
"\x3B\xFD\x13\x00\x00\x81\x31\xFE\x45\x41\x37\x30\x30\x36\x43\x47\x20\x32\x34\x32\x52\x31\xD6"),
|
"\x3B\xFD\x13\x00\x00\x81\x31\xFE\x45\x41\x37\x30\x30\x36\x43\x47\x20\x32\x34\x32\x52\x31\xD6"),
|
||||||
"YubiKey NEO (token)"},
|
"YubiKey NEO (token)"},
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
#include "core/Tools.h"
|
#include "core/Tools.h"
|
||||||
#include "crypto/Random.h"
|
#include "crypto/Random.h"
|
||||||
#include "thirdparty/ykcore/ykcore.h"
|
#include "thirdparty/ykcore/ykcore.h"
|
||||||
#include "thirdparty/ykcore/ykdef.h"
|
|
||||||
#include "thirdparty/ykcore/ykstatus.h"
|
#include "thirdparty/ykcore/ykstatus.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@ -82,7 +81,6 @@ namespace
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
YubiKeyInterfaceUSB::YubiKeyInterfaceUSB()
|
YubiKeyInterfaceUSB::YubiKeyInterfaceUSB()
|
||||||
: YubiKeyInterface()
|
|
||||||
{
|
{
|
||||||
if (!yk_init()) {
|
if (!yk_init()) {
|
||||||
qDebug("YubiKey: Failed to initialize USB interface.");
|
qDebug("YubiKey: Failed to initialize USB interface.");
|
||||||
@ -107,15 +105,14 @@ YubiKeyInterfaceUSB* YubiKeyInterfaceUSB::instance()
|
|||||||
return m_instance;
|
return m_instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool YubiKeyInterfaceUSB::findValidKeys()
|
YubiKey::KeyMap YubiKeyInterfaceUSB::findValidKeys()
|
||||||
{
|
{
|
||||||
m_error.clear();
|
m_error.clear();
|
||||||
if (!isInitialized()) {
|
if (!isInitialized()) {
|
||||||
return false;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove all known keys
|
YubiKey::KeyMap keyMap;
|
||||||
m_foundKeys.clear();
|
|
||||||
|
|
||||||
// Try to detect up to 4 connected hardware keys
|
// Try to detect up to 4 connected hardware keys
|
||||||
for (int i = 0; i < MAX_KEYS; ++i) {
|
for (int i = 0; i < MAX_KEYS; ++i) {
|
||||||
@ -133,13 +130,12 @@ bool YubiKeyInterfaceUSB::findValidKeys()
|
|||||||
yk_get_key_vid_pid(yk_key, &vid, &pid);
|
yk_get_key_vid_pid(yk_key, &vid, &pid);
|
||||||
|
|
||||||
QString name = m_pid_names.value(pid, tr("Unknown"));
|
QString name = m_pid_names.value(pid, tr("Unknown"));
|
||||||
if (vid == 0x1d50) {
|
if (vid == ONLYKEY_VID) {
|
||||||
name = QStringLiteral("OnlyKey");
|
name = QStringLiteral("OnlyKey %ver");
|
||||||
|
}
|
||||||
|
if (name.contains("%ver")) {
|
||||||
|
name = name.replace("%ver", QString::number(ykds_version_major(st)));
|
||||||
}
|
}
|
||||||
name += QString(" v%1.%2.%3")
|
|
||||||
.arg(QString::number(ykds_version_major(st)),
|
|
||||||
QString::number(ykds_version_minor(st)),
|
|
||||||
QString::number(ykds_version_build(st)));
|
|
||||||
|
|
||||||
bool wouldBlock;
|
bool wouldBlock;
|
||||||
for (int slot = 1; slot <= 2; ++slot) {
|
for (int slot = 1; slot <= 2; ++slot) {
|
||||||
@ -151,25 +147,23 @@ bool YubiKeyInterfaceUSB::findValidKeys()
|
|||||||
// Don't actually challenge a YubiKey Neo or below, they always require button press
|
// Don't actually challenge a YubiKey Neo or below, they always require button press
|
||||||
// if it is enabled for the slot resulting in failed detection
|
// if it is enabled for the slot resulting in failed detection
|
||||||
if (pid <= NEO_OTP_U2F_CCID_PID) {
|
if (pid <= NEO_OTP_U2F_CCID_PID) {
|
||||||
auto display = tr("(USB) %1 [%2] Configured Slot - %3")
|
auto display = tr("%1 [%2] - Slot %3", "YubiKey NEO display fields")
|
||||||
.arg(name, QString::number(serial), QString::number(slot));
|
.arg(name, QString::number(serial), QString::number(slot));
|
||||||
m_foundKeys.insert(serial, {slot, display});
|
keyMap.insert({serial, slot}, display);
|
||||||
} else if (performTestChallenge(yk_key, slot, &wouldBlock)) {
|
} else if (performTestChallenge(yk_key, slot, &wouldBlock)) {
|
||||||
auto display =
|
auto display =
|
||||||
tr("(USB) %1 [%2] Challenge-Response - Slot %3 - %4")
|
tr("%1 [%2] - Slot %3, %4", "YubiKey display fields")
|
||||||
.arg(name,
|
.arg(name,
|
||||||
QString::number(serial),
|
QString::number(serial),
|
||||||
QString::number(slot),
|
QString::number(slot),
|
||||||
wouldBlock ? tr("Press", "USB Challenge-Response Key interaction request")
|
wouldBlock ? tr("Press", "USB Challenge-Response Key interaction request")
|
||||||
: tr("Passive", "USB Challenge-Response Key no interaction required"));
|
: tr("Passive", "USB Challenge-Response Key no interaction required"));
|
||||||
m_foundKeys.insert(serial, {slot, display});
|
keyMap.insert({serial, slot}, display);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ykds_free(st);
|
ykds_free(st);
|
||||||
closeKey(yk_key);
|
closeKey(yk_key);
|
||||||
|
|
||||||
Tools::wait(100);
|
|
||||||
} else if (yk_errno == YK_ENOKEY) {
|
} else if (yk_errno == YK_ENOKEY) {
|
||||||
// No more keys are connected
|
// No more keys are connected
|
||||||
break;
|
break;
|
||||||
@ -180,7 +174,7 @@ bool YubiKeyInterfaceUSB::findValidKeys()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return !m_foundKeys.isEmpty();
|
return keyMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -198,6 +192,7 @@ bool YubiKeyInterfaceUSB::testChallenge(YubiKeySlot slot, bool* wouldBlock)
|
|||||||
if (yk_key) {
|
if (yk_key) {
|
||||||
ret = performTestChallenge(yk_key, slot.second, wouldBlock);
|
ret = performTestChallenge(yk_key, slot.second, wouldBlock);
|
||||||
}
|
}
|
||||||
|
closeKey(yk_key);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,18 +228,11 @@ YubiKeyInterfaceUSB::challenge(YubiKeySlot slot, const QByteArray& challenge, Bo
|
|||||||
return YubiKey::ChallengeResult::YCR_ERROR;
|
return YubiKey::ChallengeResult::YCR_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to grab a lock for 1 second, fail out if not possible
|
|
||||||
if (!m_mutex.tryLock(1000)) {
|
|
||||||
m_error = tr("Hardware key is currently in use.");
|
|
||||||
return YubiKey::ChallengeResult::YCR_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* yk_key = openKeySerial(slot.first);
|
auto* yk_key = openKeySerial(slot.first);
|
||||||
if (!yk_key) {
|
if (!yk_key) {
|
||||||
// Key with specified serial number is not connected
|
// Key with specified serial number is not connected
|
||||||
m_error =
|
m_error =
|
||||||
tr("Could not find hardware key with serial number %1. Please plug it in to continue.").arg(slot.first);
|
tr("Could not find hardware key with serial number %1. Please plug it in to continue.").arg(slot.first);
|
||||||
m_mutex.unlock();
|
|
||||||
return YubiKey::ChallengeResult::YCR_ERROR;
|
return YubiKey::ChallengeResult::YCR_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,7 +241,6 @@ YubiKeyInterfaceUSB::challenge(YubiKeySlot slot, const QByteArray& challenge, Bo
|
|||||||
|
|
||||||
closeKey(yk_key);
|
closeKey(yk_key);
|
||||||
emit challengeCompleted();
|
emit challengeCompleted();
|
||||||
m_mutex.unlock();
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -32,8 +32,10 @@ class YubiKeyInterfaceUSB : public YubiKeyInterface
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
static YubiKeyInterfaceUSB* instance();
|
static YubiKeyInterfaceUSB* instance();
|
||||||
|
static constexpr int YUBICO_USB_VID = YUBICO_VID;
|
||||||
|
static constexpr int ONLYKEY_USB_VID = ONLYKEY_VID;
|
||||||
|
|
||||||
bool findValidKeys() override;
|
YubiKey::KeyMap findValidKeys() override;
|
||||||
|
|
||||||
YubiKey::ChallengeResult
|
YubiKey::ChallengeResult
|
||||||
challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response) override;
|
challenge(YubiKeySlot slot, const QByteArray& challenge, Botan::secure_vector<char>& response) override;
|
||||||
@ -53,22 +55,22 @@ private:
|
|||||||
bool performTestChallenge(void* key, int slot, bool* wouldBlock) override;
|
bool performTestChallenge(void* key, int slot, bool* wouldBlock) override;
|
||||||
|
|
||||||
// This map provides display names for the various USB PIDs of the Yubikeys
|
// This map provides display names for the various USB PIDs of the Yubikeys
|
||||||
const QHash<int, QString> m_pid_names = {{YUBIKEY_PID, "YubiKey 1/2"},
|
const QHash<int, QString> m_pid_names = {{YUBIKEY_PID, "YubiKey %ver"},
|
||||||
{NEO_OTP_PID, "YubiKey NEO - OTP only"},
|
{NEO_OTP_PID, "YubiKey NEO - OTP"},
|
||||||
{NEO_OTP_CCID_PID, "YubiKey NEO - OTP and CCID"},
|
{NEO_OTP_CCID_PID, "YubiKey NEO - OTP+CCID"},
|
||||||
{NEO_CCID_PID, "YubiKey NEO - CCID only"},
|
{NEO_CCID_PID, "YubiKey NEO - CCID"},
|
||||||
{NEO_U2F_PID, "YubiKey NEO - U2F only"},
|
{NEO_U2F_PID, "YubiKey NEO - FIDO"},
|
||||||
{NEO_OTP_U2F_PID, "YubiKey NEO - OTP and U2F"},
|
{NEO_OTP_U2F_PID, "YubiKey NEO - OTP+FIDO"},
|
||||||
{NEO_U2F_CCID_PID, "YubiKey NEO - U2F and CCID"},
|
{NEO_U2F_CCID_PID, "YubiKey NEO - FIDO+CCID"},
|
||||||
{NEO_OTP_U2F_CCID_PID, "YubiKey NEO - OTP, U2F and CCID"},
|
{NEO_OTP_U2F_CCID_PID, "YubiKey NEO - OTP+FIDO+CCID"},
|
||||||
{YK4_OTP_PID, "YubiKey 4/5 - OTP only"},
|
{YK4_OTP_PID, "YubiKey %ver - OTP"},
|
||||||
{YK4_U2F_PID, "YubiKey 4/5 - U2F only"},
|
{YK4_U2F_PID, "YubiKey %ver - U2F"},
|
||||||
{YK4_OTP_U2F_PID, "YubiKey 4/5 - OTP and U2F"},
|
{YK4_OTP_U2F_PID, "YubiKey %ver - OTP+FIDO"},
|
||||||
{YK4_CCID_PID, "YubiKey 4/5 - CCID only"},
|
{YK4_CCID_PID, "YubiKey %ver - CCID"},
|
||||||
{YK4_OTP_CCID_PID, "YubiKey 4/5 - OTP and CCID"},
|
{YK4_OTP_CCID_PID, "YubiKey %ver - OTP+CCID"},
|
||||||
{YK4_U2F_CCID_PID, "YubiKey 4/5 - U2F and CCID"},
|
{YK4_U2F_CCID_PID, "YubiKey %ver - FIDO+CCID"},
|
||||||
{YK4_OTP_U2F_CCID_PID, "YubiKey 4/5 - OTP, U2F and CCID"},
|
{YK4_OTP_U2F_CCID_PID, "YubiKey %ver - OTP+FIDO+CCID"},
|
||||||
{PLUS_U2F_OTP_PID, "YubiKey plus - OTP+U2F"}};
|
{PLUS_U2F_OTP_PID, "YubiKey plus - OTP+FIDO"}};
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSX_YUBIKEY_INTERFACE_USB_H
|
#endif // KEEPASSX_YUBIKEY_INTERFACE_USB_H
|
||||||
|
@ -45,17 +45,11 @@ void YubiKey::findValidKeysAsync()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<YubiKeySlot> YubiKey::foundKeys()
|
YubiKey::KeyMap YubiKey::foundKeys()
|
||||||
{
|
{
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
QString YubiKey::getDisplayName(YubiKeySlot slot)
|
|
||||||
{
|
|
||||||
Q_UNUSED(slot);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
QString YubiKey::errorMessage()
|
QString YubiKey::errorMessage()
|
||||||
{
|
{
|
||||||
return {};
|
return {};
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
#ifndef KEEPASSXC_WINDOWSHELLO_H
|
#ifndef KEEPASSXC_WINDOWSHELLO_H
|
||||||
#define KEEPASSXC_WINDOWSHELLO_H
|
#define KEEPASSXC_WINDOWSHELLO_H
|
||||||
|
|
||||||
#include "QuickUnlockInterface.h";
|
#include "QuickUnlockInterface.h"
|
||||||
|
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
@ -2253,7 +2253,7 @@ void TestCli::testYubiKeyOption()
|
|||||||
|
|
||||||
YubiKey::instance()->findValidKeys();
|
YubiKey::instance()->findValidKeys();
|
||||||
|
|
||||||
auto keys = YubiKey::instance()->foundKeys();
|
const auto keys = YubiKey::instance()->foundKeys().keys();
|
||||||
if (keys.isEmpty()) {
|
if (keys.isEmpty()) {
|
||||||
QSKIP("No YubiKey devices were detected.");
|
QSKIP("No YubiKey devices were detected.");
|
||||||
}
|
}
|
||||||
|
@ -44,11 +44,12 @@ void TestYubiKeyChallengeResponse::testDetectDevices()
|
|||||||
YubiKey::instance()->findValidKeys();
|
YubiKey::instance()->findValidKeys();
|
||||||
|
|
||||||
// Look at the information retrieved from the key(s)
|
// Look at the information retrieved from the key(s)
|
||||||
for (auto key : YubiKey::instance()->foundKeys()) {
|
const auto foundKeys = YubiKey::instance()->foundKeys();
|
||||||
auto displayName = YubiKey::instance()->getDisplayName(key);
|
for (auto i = foundKeys.cbegin(); i != foundKeys.cend(); ++i) {
|
||||||
|
const auto& displayName = i.value();
|
||||||
QVERIFY(displayName.contains("Challenge-Response - Slot") || displayName.contains("Configured Slot -"));
|
QVERIFY(displayName.contains("Challenge-Response - Slot") || displayName.contains("Configured Slot -"));
|
||||||
QVERIFY(displayName.contains(QString::number(key.first)));
|
QVERIFY(displayName.contains(QString::number(i.key().first)));
|
||||||
QVERIFY(displayName.contains(QString::number(key.second)));
|
QVERIFY(displayName.contains(QString::number(i.key().second)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +60,7 @@ void TestYubiKeyChallengeResponse::testDetectDevices()
|
|||||||
*/
|
*/
|
||||||
void TestYubiKeyChallengeResponse::testKeyChallenge()
|
void TestYubiKeyChallengeResponse::testKeyChallenge()
|
||||||
{
|
{
|
||||||
auto keys = YubiKey::instance()->foundKeys();
|
auto keys = YubiKey::instance()->foundKeys().keys();
|
||||||
if (keys.isEmpty()) {
|
if (keys.isEmpty()) {
|
||||||
QSKIP("No YubiKey devices were detected.");
|
QSKIP("No YubiKey devices were detected.");
|
||||||
}
|
}
|
||||||
|
@ -141,6 +141,7 @@ void TestGui::init()
|
|||||||
databaseOpenWidget->findChild<PasswordWidget*>("editPassword")->findChild<QLineEdit*>("passwordEdit");
|
databaseOpenWidget->findChild<PasswordWidget*>("editPassword")->findChild<QLineEdit*>("passwordEdit");
|
||||||
QVERIFY(editPassword);
|
QVERIFY(editPassword);
|
||||||
editPassword->setFocus();
|
editPassword->setFocus();
|
||||||
|
QTRY_VERIFY(editPassword->hasFocus());
|
||||||
|
|
||||||
QTest::keyClicks(editPassword, "a");
|
QTest::keyClicks(editPassword, "a");
|
||||||
QTest::keyClick(editPassword, Qt::Key_Enter);
|
QTest::keyClick(editPassword, Qt::Key_Enter);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user