Release 2.6.3

Added

- Support Argon2id KDF [#5778]
- Support XMLv2 key files [#5798]

Changed

- Improve CSV Import/Export, include time fields and TOTP [#5346]
- Support empty area dragging of the application window [#5860]
- Display default Auto-Type sequence in preview pane [#5654]
- Remove strict length limit on generated passwords [#5748]
- Hide key file path by default when unlocking database [#5779]
- Document browser extension use with Edge in managed mode [#5692]
- Windows: Prevent clipboard history and cloud sync [#5853]
- macOS: Update the application icon to Big Sur styling [#5851]

Fixed

- Re-select previously selected entry on database unlock [#5559]
- Properly save special character choice in password generator [#5610]
- Fix crash in browser integration with multiple similar entries [#5653]
- Remove offset on username field in classic theme [#5788]
- Ensure entry history is copied when drag/dropping entries and groups [#5817]
- Close modal dialogs when database is locked [#5820]
- Prevent crash when KeeShare modifies an entry that is currently being edited [#5827]
- Improve preview of entry attributes [#5834]
- Always activate/focus database open dialog preventing mistype [#5878]
- Reports: fix calculation of average password length [#5862]
- Linux: Delay startup on login to correct tray icon issues [#5724]
This commit is contained in:
Janek Bevendorff 2021-01-12 17:49:19 +01:00
commit beae1869a3
No known key found for this signature in database
GPG Key ID: 2FDEB0D40BCA5E11
88 changed files with 9127 additions and 543 deletions

View File

@ -1,5 +1,37 @@
# Changelog
## 2.6.3 (2020-01-12)
### Added
- Support Argon2id KDF [#5778]
- Support XMLv2 key files [#5798]
### Changed
- Improve CSV Import/Export, include time fields and TOTP [#5346]
- Support empty area dragging of the application window [#5860]
- Display default Auto-Type sequence in preview pane [#5654]
- Remove strict length limit on generated passwords [#5748]
- Hide key file path by default when unlocking database [#5779]
- Document browser extension use with Edge in managed mode [#5692]
- Windows: Prevent clipboard history and cloud sync [#5853]
- macOS: Update the application icon to Big Sur styling [#5851]
### Fixed
- Re-select previously selected entry on database unlock [#5559]
- Properly save special character choice in password generator [#5610]
- Fix crash in browser integration with multiple similar entries [#5653]
- Remove offset on username field in classic theme [#5788]
- Ensure entry history is copied when drag/dropping entries and groups [#5817]
- Close modal dialogs when database is locked [#5820]
- Prevent crash when KeeShare modifies an entry that is currently being edited [#5827]
- Improve preview of entry attributes [#5834]
- Always activate/focus database open dialog preventing mistype [#5878]
- Reports: fix calculation of average password length [#5862]
- Linux: Delay startup on login to correct tray icon issues [#5724]
## 2.6.2 (2020-10-21)
### Added

View File

@ -101,7 +101,7 @@ endif()
set(KEEPASSXC_VERSION_MAJOR "2")
set(KEEPASSXC_VERSION_MINOR "6")
set(KEEPASSXC_VERSION_PATCH "2")
set(KEEPASSXC_VERSION_PATCH "3")
set(KEEPASSXC_VERSION "${KEEPASSXC_VERSION_MAJOR}.${KEEPASSXC_VERSION_MINOR}.${KEEPASSXC_VERSION_PATCH}")
set(OVERRIDE_VERSION "" CACHE STRING "Override the KeePassXC Version for Snapshot builds")

View File

@ -23,6 +23,10 @@ You can download the KeePassXC-Browser extension from your web browser. To downl
2. Click the button to install/add the extension to the browser. Accept any confirmation dialogs.
// tag::advanced[]
NOTE: When Microsoft Edge is installed as a managed application, system administrators are required to deploy a custom native messaging configuration. Instructions for this are found in the advanced section below.
// end::advanced[]
=== Configure KeePassXC-Browser
To start using KeePassXC-Browser, you must configure it so that it can communicate with the KeePassXC application on your desktop.
@ -104,5 +108,38 @@ WARNING: We do not recommend changing any of these settings as they may break th
.Advanced browser settings
image::browser_advanced_settings.png[]
=== Advanced Setup
==== Managed Microsoft Edge on Windows
1. Deploy *org.keepassxc.keepassxc_browser_edge.json* to, for example, `C:\ProgramData\KeepassXC` on all managed platforms.
+
----
{
"allowed_origins": [
"chrome-extension://pdffhmdngciaglkoonimfcmckehcpafo/"
],
"description": "KeePassXC integration with native messaging support",
"name": "org.keepassxc.keepassxc_browser",
"path": "C:\\Program Files\\KeePassXC\\keepassxc-proxy.exe",
"type": "stdio"
}
----
2. Configure GPO options (registry result):
+
----
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Edge\NativeMessagingHosts\org.keepassxc.keepassxc_browser]
@="C:\ProgramData\KeepassXC\org.keepassxc.keepassxc_browser_edge.json"
[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge]
"NativeMessagingUserLevelHosts"=dword:00000000
[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge\ExtensionInstallAllowlist]
"1"="pdffhmdngciaglkoonimfcmckehcpafo"
[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge\NativeMessagingAllowlist]
"1"="org.keepassxc.keepassxc_browser"
----
// end::advanced[]
// end::content[]

View File

@ -50,6 +50,33 @@
</screenshots>
<releases>
<release version="2.6.3" date="2021-01-12">
<description>
<ul>
<li>Support Argon2id KDF [#5778]</li>
<li>Support XMLv2 key files [#5798]</li>
<li>Improve CSV Import/Export, include time fields and TOTP [#5346]</li>
<li>Support empty area dragging of the application window [#5860]</li>
<li>Display default Auto-Type sequence in preview pane [#5654]</li>
<li>Remove strict length limit on generated passwords [#5748]</li>
<li>Hide key file path by default when unlocking database [#5779]</li>
<li>Document browser extension use with Edge in managed mode [#5692]</li>
<li>Windows: Prevent clipboard history and cloud sync [#5853]</li>
<li>macOS: Update the application icon to Big Sur styling [#5851]</li>
<li>Re-select previously selected entry on database unlock [#5559]</li>
<li>Properly save special character choice in password generator [#5610]</li>
<li>Fix crash in browser integration with multiple similar entries [#5653]</li>
<li>Remove offset on username field in classic theme [#5788]</li>
<li>Ensure entry history is copied when drag/dropping entries and groups [#5817]</li>
<li>Close modal dialogs when database is locked [#5820]</li>
<li>Prevent crash when KeeShare modifies an entry that is currently being edited [#5827]</li>
<li>Improve preview of entry attributes [#5834]</li>
<li>Always activate/focus database open dialog preventing mistype [#5878]</li>
<li>Reports: fix calculation of average password length [#5862]</li>
<li>Linux: Delay startup on login to correct tray icon issues [#5724]</li>
</ul>
</description>
</release>
<release version="2.6.2" date="2020-10-21">
<description>
<ul>

BIN
share/macosx/keepassxc.ai Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

View File

@ -245,7 +245,7 @@
</message>
<message>
<source>(restart program to activate)</source>
<translation type="unfinished"/>
<translation>(genstart program for at aktivere)</translation>
</message>
<message>
<source>Minimize window after unlocking database</source>

View File

@ -233,7 +233,7 @@
</message>
<message>
<source>Check for updates at application startup once per week</source>
<translation>Bei Programmstartstart wöchentlich auf Updates prüfen</translation>
<translation>Bei Programmstart wöchentlich auf Updates prüfen</translation>
</message>
<message>
<source>Include beta releases when checking for updates</source>

View File

@ -1114,6 +1114,14 @@ chrome-laptop.</source>
<source>Column %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>TOTP</source>
<translation type="unfinished">TOTP</translation>
</message>
<message>
<source>Icon</source>
<translation type="unfinished">Icon</translation>
</message>
</context>
<context>
<name>CsvParserModel</name>
@ -1218,20 +1226,6 @@ Backup database located at %2</source>
<source>Refresh</source>
<translation>Refresh</translation>
</message>
<message>
<source>Legacy key file format</source>
<translation>Legacy key file format</translation>
</message>
<message>
<source>You are using a legacy key file format which may become
unsupported in the future.
Please consider generating a new key file.</source>
<translation>You are using a legacy key file format which may become
unsupported in the future.
Please consider generating a new key file.</translation>
</message>
<message>
<source>Don&apos;t show this warning again</source>
<translation>Don&apos;t show this warning again</translation>
@ -1357,6 +1351,14 @@ If you do not have a key file, please leave the field empty.</source>
<source>Select hardware key</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Old key file format</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>You are using an old key file format which KeePassXC may&lt;br&gt;stop supporting in the future.&lt;br&gt;&lt;br&gt;Please consider generating a new key file by going to:&lt;br&gt;&lt;strong&gt;Database / Database Security / Change Key File.&lt;/strong&gt;&lt;br&gt;</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>DatabaseSettingWidgetMetaData</name>
@ -2324,6 +2326,15 @@ Disable safe saves and try again?</translation>
<source>[PROTECTED] Press Reveal to view or edit</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Invalid Entry</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>An external merge operation has invalidated this entry.
Unfortunately, any changes made have been lost.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>EditEntryWidgetAdvanced</name>
@ -3464,11 +3475,6 @@ Are you sure to add this file?</source>
<source>[PROTECTED]</source>
<translation>[PROTECTED]</translation>
</message>
<message>
<source>&lt;b&gt;%1&lt;/b&gt;: %2</source>
<comment>attributes line</comment>
<translation>&lt;b&gt;%1&lt;/b&gt;: %2</translation>
</message>
<message>
<source>Enabled</source>
<translation>Enabled</translation>
@ -3489,6 +3495,15 @@ Are you sure to add this file?</source>
<source>Advanced</source>
<translation type="unfinished">Advanced</translation>
</message>
<message>
<source>Default Sequence</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>&lt;tr&gt;&lt;td&gt;&lt;b&gt;%1&lt;/b&gt;:&lt;/td&gt;&lt;td&gt;%2&lt;/td&gt;&lt;/tr&gt;</source>
<comment>attributes line</comment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>EntryURLModel</name>
@ -3522,23 +3537,8 @@ Are you sure to add this file?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>FdoSecrets::Item</name>
<message>
<source>Entry &quot;%1&quot; from database &quot;%2&quot; was used by %3</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>FdoSecrets::Service</name>
<message numerus="yes">
<source>%n Entry(s) was used by %1</source>
<comment>%1 is the name of an application</comment>
<translation type="unfinished">
<numerusform></numerusform>
<numerusform></numerusform>
</translation>
</message>
<message>
<source>Failed to register DBus service at %1.&lt;br/&gt;</source>
<translation type="unfinished"></translation>
@ -4362,10 +4362,6 @@ If this reoccurs, then your database file may be corrupt.</source>
<source>&lt;p&gt;You can add a key file containing random bytes for additional security.&lt;/p&gt;&lt;p&gt;You must keep it secret and never lose it or you will be locked out!&lt;/p&gt;</source>
<translation>&lt;p&gt;You can add a key file containing random bytes for additional security.&lt;/p&gt;&lt;p&gt;You must keep it secret and never lose it or you will be locked out!&lt;/p&gt;</translation>
</message>
<message>
<source>Legacy key file format</source>
<translation>Legacy key file format</translation>
</message>
<message>
<source>Error loading the key file &apos;%1&apos;
Message: %2</source>
@ -4434,10 +4430,11 @@ Are you sure you want to continue with this file?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>You are using a legacy key file format which may become
unsupported in the future.
Generate a new key file in the database security settings.</source>
<source>Old key file format</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>You selected a key file in an old format which KeePassXC&lt;br&gt;may stop supporting in the future.&lt;br&gt;&lt;br&gt;Please consider generating a new key file instead.</source>
<translation type="unfinished"></translation>
</message>
</context>
@ -5780,16 +5777,6 @@ Expect some bugs and minor issues, this version is not meant for production use.
<source>Perform advanced analysis on the password.</source>
<translation>Perform advanced analysis on the password.</translation>
</message>
<message>
<source>WARNING: You are using a legacy key file format which may become
unsupported in the future.
Please consider generating a new key file.</source>
<translation>WARNING: You are using a legacy key file format which may become
unsupported in the future.
Please consider generating a new key file.</translation>
</message>
<message>
<source>
@ -6180,10 +6167,6 @@ Available commands:
<source>%1: (row, col) %2,%3</source>
<translation>%1: (row, col) %2,%3</translation>
</message>
<message>
<source>Argon2 (KDBX 4 recommended)</source>
<translation>Argon2 (KDBX 4 recommended)</translation>
</message>
<message>
<source>AES-KDF (KDBX 4)</source>
<translation>AES-KDF (KDBX 4)</translation>
@ -6760,10 +6743,6 @@ Kernel: %3 %4</source>
<source>AES (%1 rounds)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Argon2 (%1 rounds, %2 KB)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>AES 256-bit</source>
<translation type="unfinished"></translation>
@ -6800,6 +6779,45 @@ Kernel: %3 %4</source>
<source>path to a custom local config file</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>WARNING: You are using an old key file format which KeePassXC may
stop supporting in the future.
Please consider generating a new key file.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Argon2%1 (%2 rounds, %3 KB)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Argon2d (KDBX 4 recommended)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Argon2id (KDBX 4)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>TOTP</source>
<translation type="unfinished">TOTP</translation>
</message>
<message>
<source>Icon</source>
<translation type="unfinished">Icon</translation>
</message>
<message>
<source>Unsupported key file version: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Checksum mismatch! Key file may be corrupt.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Unexpected key file data! Key file may be corrupt.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QtIOCompressor</name>

View File

@ -2454,7 +2454,7 @@ Disable safe saves and try again?</translation>
<name>EditEntryWidgetBrowser</name>
<message>
<source>These settings affect to the entry&apos;s behaviour with the browser extension.</source>
<translation>These settings affect to the entry&apos;s behavior with the browser extension.</translation>
<translation>These settings affect the browser extensions behavior with regard to this database entry.</translation>
</message>
<message>
<source>General</source>
@ -2470,7 +2470,7 @@ Disable safe saves and try again?</translation>
</message>
<message>
<source>Additional URL&apos;s</source>
<translation>Additional URL&apos;s</translation>
<translation>Additional URLs</translation>
</message>
<message>
<source>Add</source>

View File

@ -229,7 +229,7 @@
</message>
<message>
<source>Remember database key files and security dongles</source>
<translation>Recordar los últimos ficheros claves y los «dongle» de seguridad</translation>
<translation>Recuerde los archivos de clave de la base de datos y los «dongle» de seguridad</translation>
</message>
<message>
<source>Check for updates at application startup once per week</source>
@ -1177,7 +1177,7 @@ Copia de seguridad de base de datos ubicada en %2</translation>
</message>
<message>
<source>Recycle Bin</source>
<translation>Papelera</translation>
<translation>Papelera de reciclaje</translation>
</message>
<message>
<source>Passwords</source>
@ -1204,7 +1204,7 @@ Copia de seguridad de base de datos ubicada en %2</translation>
<name>DatabaseOpenWidget</name>
<message>
<source>Key File:</source>
<translation>Fichero clave:</translation>
<translation>Fichero Clave:</translation>
</message>
<message>
<source>Refresh</source>
@ -1258,7 +1258,7 @@ Considere generar un nuevo fichero clave.</translation>
</message>
<message>
<source>Hardware key slot selection</source>
<translation>Selección de ranura de clave hardware</translation>
<translation>Selección de ranura de llave por hardware</translation>
</message>
<message>
<source>Browse for key file</source>
@ -1274,11 +1274,11 @@ Considere generar un nuevo fichero clave.</translation>
</message>
<message>
<source>Hardware Key:</source>
<translation>Clave hardware:</translation>
<translation>Llave por hardware:</translation>
</message>
<message>
<source>Hardware key help</source>
<translation>Ayuda de clave hardware</translation>
<translation>Ayuda de la llave por hardware</translation>
</message>
<message>
<source>TouchID for Quick Unlock</source>
@ -1317,7 +1317,7 @@ Para prevenir que aparezca este error, debe ir a «Configuración de base de dat
<message>
<source>&lt;p&gt;You can use a hardware security key such as a &lt;strong&gt;YubiKey&lt;/strong&gt; or &lt;strong&gt;OnlyKey&lt;/strong&gt; with slots configured for HMAC-SHA1.&lt;/p&gt;
&lt;p&gt;Click for more information...&lt;/p&gt;</source>
<translation>&lt;p&gt;Puede usar una clave de seguridad hardware como &lt;strong&gt;YubiKey&lt;/strong&gt; o &lt;strong&gt;OnlyKey&lt;/strong&gt; con ranuras configuradas para HMAC-SHA1.&lt;/p&gt;
<translation>&lt;p&gt;Puede usar una llave de seguridad por hardware como &lt;strong&gt;YubiKey&lt;/strong&gt; o &lt;strong&gt;OnlyKey&lt;/strong&gt; con ranuras configuradas para HMAC-SHA1.&lt;/p&gt;
&lt;p&gt;Clic para más información...&lt;/p&gt;</translation>
</message>
<message>
@ -1352,15 +1352,15 @@ Si no tiene un fichero clave, deje el campo vacío.</translation>
</message>
<message>
<source>Detecting hardware keys</source>
<translation>Detectando claves hardware...</translation>
<translation>Detectando llaves por hardware...</translation>
</message>
<message>
<source>No hardware keys detected</source>
<translation>No se detectaron claves hardware</translation>
<translation>No se detectaron llaves por hardware</translation>
</message>
<message>
<source>Select hardware key</source>
<translation>Seleccionar clave hardware...</translation>
<translation>Seleccionar llave por hardware...</translation>
</message>
</context>
<context>
@ -1589,7 +1589,7 @@ Are you sure you want to continue without a password?</source>
</message>
<message>
<source>Key Derivation Function:</source>
<translation>Función de derivación de la llave:</translation>
<translation>Función de derivación de la clave:</translation>
</message>
<message>
<source>Transform rounds:</source>
@ -1670,7 +1670,7 @@ Si conserva este número, ¡su base de datos puede tardar horas o días (o inclu
<source>You are using a very low number of key transform rounds with AES-KDF.
If you keep this number, your database may be too easy to crack!</source>
<translation>Está utilizando una cantidad muy baja de rondas de transformación de llave con AES-KDF.
<translation>Está utilizando un número muy bajo de rondas de transformación de clave con AES-KDF.
Si conserva este número, ¡su base de datos puede ser muy fácil de descifrar!</translation>
</message>
@ -1680,7 +1680,7 @@ Si conserva este número, ¡su base de datos puede ser muy fácil de descifrar!<
</message>
<message>
<source>Failed to transform key with new KDF parameters; KDF unchanged.</source>
<translation>Error al transformar la llave con nuevos parámetros KDF; KDF sin cambios.</translation>
<translation>Error al transformar la clave con nuevos parámetros KDF; KDF sin cambios.</translation>
</message>
<message numerus="yes">
<source> MiB</source>
@ -1710,7 +1710,7 @@ Si conserva este número, ¡su base de datos puede ser muy fácil de descifrar!<
</message>
<message>
<source>Key derivation function</source>
<translation>Función de derivación de la llave</translation>
<translation>Función de derivación de la clave</translation>
</message>
<message>
<source>Transform rounds</source>
@ -2000,7 +2000,7 @@ Esto es definitivamente un error, por favor repórtelo a los desarrolladores.</t
</message>
<message numerus="yes">
<source>Do you really want to move %n entry(s) to the recycle bin?</source>
<translation><numerusform>¿Desea mover %n apunte a la papelera?</numerusform><numerusform>¿Desea mover %n apuntes a la papelera?</numerusform></translation>
<translation><numerusform>¿Desea mover %n apunte a la papelera?</numerusform><numerusform>¿Desea mover %n apuntes a la papelera de reciclaje?</numerusform></translation>
</message>
<message>
<source>Execute command?</source>
@ -2137,11 +2137,11 @@ Disable safe saves and try again?</source>
</message>
<message>
<source>Move group to recycle bin?</source>
<translation>¿Mover grupo a la papelera?</translation>
<translation>¿Mover grupo a la papelera de reciclaje?</translation>
</message>
<message>
<source>Do you really want to move the group &quot;%1&quot; to the recycle bin?</source>
<translation>¿Desea mover el grupo «%1» a la papelera?</translation>
<translation>¿Desea mover el grupo «%1» a la papelera de reciclaje?</translation>
</message>
<message>
<source>Successfully merged the database files.</source>
@ -2642,7 +2642,7 @@ Disable safe saves and try again?</source>
</message>
<message>
<source>Add key to agent when database is opened/unlocked</source>
<translation>Añadir llave al agente cuando la base de datos se abre/desbloquea</translation>
<translation>Añadir clave al agente cuando la base de datos se abre/desbloquea</translation>
</message>
<message>
<source>Comment</source>
@ -2687,7 +2687,7 @@ Disable safe saves and try again?</source>
</message>
<message>
<source>Require user confirmation when this key is used</source>
<translation>Requiere confirmación del usuario cuando se usa esta llave</translation>
<translation>Requiere confirmación del usuario cuando se usa esta clave</translation>
</message>
<message>
<source>Remove key from agent after specified seconds</source>
@ -2958,7 +2958,7 @@ Las extensiones soportadas son: %1.</translation>
</message>
<message numerus="yes">
<source>%n icon(s) already exist in the database</source>
<translation><numerusform>El icono %n ya existe en la base de datos</numerusform><numerusform>Los %n iconos ya existen en la base de datos</numerusform></translation>
<translation><numerusform>El icono %n ya existe en la base de datos</numerusform><numerusform>Los %n icono(s) ya existe(n) en la base de datos</numerusform></translation>
</message>
<message numerus="yes">
<source>The following icon(s) failed:</source>
@ -2966,7 +2966,7 @@ Las extensiones soportadas son: %1.</translation>
</message>
<message numerus="yes">
<source>This icon is used by %n entry(s), and will be replaced by the default icon. Are you sure you want to delete it?</source>
<translation><numerusform>Este icono es usado en %1 apunte, y será remplazado por el icono por defecto. ¿Está seguro que desea eliminarlo?</numerusform><numerusform>Este icono es usado en %1 apuntes, y será remplazado por el icono predeterminado. ¿Está seguro que desea eliminarlo?</numerusform></translation>
<translation><numerusform>Este icono es usado en %1 apunte, y será remplazado por el icono por defecto. ¿Está seguro que desea eliminarlo?</numerusform><numerusform>Este icono es usado en %n apunte(s), y será remplazado por el icono predeterminado. ¿Está seguro que desea eliminarlo?</numerusform></translation>
</message>
<message>
<source>You can enable the DuckDuckGo website icon service under Tools -&gt; Settings -&gt; Security</source>
@ -3203,9 +3203,9 @@ Your database may get very large and reduce performance.
Are you sure to add this file?</source>
<translation>%1 es un fichero grande (%2 MB).
Tu base de datos puede vovlerse muy grande y reducir el rendimiento.
Su base de datos puede vovlerse muy grande y reducir el rendimiento.
¿Estás seguro de añadir este fichero?</translation>
¿Está seguro de añadir este fichero?</translation>
</message>
<message>
<source>Confirm Attachment</source>
@ -3724,7 +3724,7 @@ Si ocurre nuevamente entonces su archivo de base de datos puede estar corrupto.<
</message>
<message>
<source>Unsupported key derivation function (KDF) or invalid parameters</source>
<translation>Función de derivación de cerradura no admitida (KDF) o parámetros no válidos</translation>
<translation>Función de derivación de clave no admitida (KDF) o parámetros no válidos</translation>
</message>
<message>
<source>Legacy header fields found in KDBX4 file.</source>
@ -4118,7 +4118,7 @@ Linea %2, columna %3</translation>
</message>
<message>
<source>Key transformation failed</source>
<translation>Error en la transformación de la llave</translation>
<translation>Error en la transformación de la clave</translation>
</message>
<message>
<source>Invalid group field type number</source>
@ -4317,7 +4317,7 @@ Si ocurre nuevamente entonces su archivo de base de datos puede estar corrupto.<
</message>
<message>
<source>Key File</source>
<translation>Fichero clave</translation>
<translation>Fichero Clave</translation>
</message>
<message>
<source>&lt;p&gt;You can add a key file containing random bytes for additional security.&lt;/p&gt;&lt;p&gt;You must keep it secret and never lose it or you will be locked out!&lt;/p&gt;</source>
@ -4325,7 +4325,7 @@ Si ocurre nuevamente entonces su archivo de base de datos puede estar corrupto.<
</message>
<message>
<source>Legacy key file format</source>
<translation>Formato de archivo llave heredado</translation>
<translation>Formato de archivo fichero clave heredado</translation>
</message>
<message>
<source>Error loading the key file '%1'
@ -4355,7 +4355,7 @@ Mensaje: %2</translation>
</message>
<message>
<source>Select a key file</source>
<translation>Seleccione un archivo llave</translation>
<translation>Seleccionar un fichero clave</translation>
</message>
<message>
<source>Key file selection</source>
@ -4829,11 +4829,11 @@ Espere algunos errores y problemas menores, esta versión no está destinada par
</message>
<message>
<source>Show Toolbar</source>
<translation>Mostrar barra de herrameintas</translation>
<translation>Mostrar barra de herramientas</translation>
</message>
<message>
<source>Show Preview Panel</source>
<translation>Mostrar panel de previsualizción</translation>
<translation>Mostrar panel de previsualización</translation>
</message>
<message>
<source>Don&apos;t show again for this version</source>
@ -5115,7 +5115,7 @@ Espere algunos errores y problemas menores, esta versión no está destinada par
<name>OpenSSHKey</name>
<message>
<source>Invalid key file, expecting an OpenSSH key</source>
<translation>Archivo llave no válido, esperando una llave de OpenSSH</translation>
<translation>Fichero clave no válido, esperando una clave de OpenSSH</translation>
</message>
<message>
<source>PEM boundary mismatch</source>
@ -5579,7 +5579,7 @@ Espere algunos errores y problemas menores, esta versión no está destinada par
</message>
<message>
<source>Empty</source>
<translation>Vacío</translation>
<translation>Vaciar</translation>
</message>
<message>
<source>Remove</source>
@ -6791,7 +6791,7 @@ Núcleo: %3 %4</translation>
</message>
<message>
<source>Hover over reason to show additional details. Double-click entries to edit.</source>
<translation>Pasar por encima de la razón para mostrar detalles adicionales. Doble clic para editar.</translation>
<translation>Pasar por encima del motivo para mostrar detalles adicionales. Doble clic para editar.</translation>
</message>
<message>
<source>Bad</source>
@ -6850,7 +6850,7 @@ Núcleo: %3 %4</translation>
</message>
<message>
<source>Reason</source>
<translation>Razón</translation>
<translation>Motivo</translation>
</message>
<message>
<source>Edit Entry...</source>
@ -7087,7 +7087,7 @@ Núcleo: %3 %4</translation>
</message>
<message>
<source>Agent refused this identity. Possible reasons include:</source>
<translation>El agente rechazó esta identidad. Las posibles razones incluyen:</translation>
<translation>El agente rechazó esta identidad. Los posibles motivos incluyen:</translation>
</message>
<message>
<source>The key has already been added.</source>
@ -7569,7 +7569,7 @@ Núcleo: %3 %4</translation>
<name>TotpDialog</name>
<message>
<source>Timed Password</source>
<translation>Contraseña cronometrada</translation>
<translation>Contraseña temporizada</translation>
</message>
<message>
<source>000000</source>
@ -7581,7 +7581,7 @@ Núcleo: %3 %4</translation>
</message>
<message numerus="yes">
<source>Expires in &lt;b&gt;%n&lt;/b&gt; second(s)</source>
<translation><numerusform>Caduca en &lt;b&gt;%n&lt;/b&gt; segundo(s)</numerusform><numerusform>Caduca en &lt;b&gt;%n&lt;/b&gt; segundo (s)</numerusform></translation>
<translation><numerusform>Caduca en &lt;b&gt;%n&lt;/b&gt; segundo</numerusform><numerusform>Caduca en &lt;b&gt;%n&lt;/b&gt; segundos</numerusform></translation>
</message>
</context>
<context>
@ -7601,7 +7601,7 @@ Núcleo: %3 %4</translation>
</message>
<message>
<source>Closing in %1 seconds.</source>
<translation>Cernado en %1 segundos.</translation>
<translation>Cerrando en %1 segundos.</translation>
</message>
</context>
<context>
@ -7807,19 +7807,19 @@ Ejemplo: JBSWY3DPEHPK3PXP</translation>
</message>
<message>
<source>Hardware key is currently in use.</source>
<translation>La clave hardware está actualmente en uso.</translation>
<translation>La llave por hardware está actualmente en uso.</translation>
</message>
<message>
<source>Could not find hardware key with serial number %1. Please plug it in to continue.</source>
<translation>No se puede encontrar hardware con número de serie %1. Conéctelo para continuar.</translation>
<translation>No se puede encontrar llave por hardware con número de serie %1. Conéctelo para continuar.</translation>
</message>
<message>
<source>Hardware key timed out waiting for user interaction.</source>
<translation>La clave hardware expiró esperando interacción del usuario.</translation>
<translation>La llave por hardware expiró esperando interacción del usuario.</translation>
</message>
<message>
<source>A USB error ocurred when accessing the hardware key: %1</source>
<translation>Ha ocurrido un error USB al acceder a la clave hardware: %1</translation>
<translation>Ha ocurrido un error USB al acceder a la llave por hardware: %1</translation>
</message>
<message>
<source>Failed to complete a challenge-response, the specific error was: %1</source>
@ -7846,23 +7846,23 @@ Ejemplo: JBSWY3DPEHPK3PXP</translation>
</message>
<message>
<source>Hardware key slot selection</source>
<translation>Selección de ranura de clave hardware</translation>
<translation>Selección de ranura de llave por hardware</translation>
</message>
<message>
<source>Could not find any hardware keys!</source>
<translation>¡No se puede encontrar ninguna clave hardware!</translation>
<translation>¡No se puede encontrar ninguna llave por hardware!</translation>
</message>
<message>
<source>Selected hardware key slot does not support challenge-response!</source>
<translation>¡La ranura de la clave hardware seleccionada no soporta reto-respuesta!</translation>
<translation>¡La ranura de la llave por hardware seleccionada no soporta reto-respuesta!</translation>
</message>
<message>
<source>Detecting hardware keys</source>
<translation>Detectando claves hardware...</translation>
<translation>Detectando llaves por hardware...</translation>
</message>
<message>
<source>No hardware keys detected</source>
<translation>No se detectaron claves hardware</translation>
<translation>No se detectaron llaves por hardware</translation>
</message>
</context>
</TS>

View File

@ -137,7 +137,7 @@
</message>
<message>
<source>You must restart the application to set the new language. Would you like to restart now?</source>
<translation type="unfinished"/>
<translation>Ohjelma täytyy käynnistää uudelleen, jotta uusi kieli voidaan ottaa käyttöön. Haluatko käynnistää uudelleen nyt?</translation>
</message>
</context>
<context>
@ -342,15 +342,15 @@
</message>
<message>
<source>Automatically save when locking database</source>
<translation type="unfinished"/>
<translation>Tallenna automaattisesti, kun tietokanta lukitaan</translation>
</message>
<message>
<source>Automatically save non-data changes when locking database</source>
<translation type="unfinished"/>
<translation>Tallenna automaattisesti asetukset jotka eivät liity varsinaisiin tietoihin, kun tietokanta lukitaan</translation>
</message>
<message>
<source>Tray icon type</source>
<translation type="unfinished"/>
<translation>Ilmoitusalueen ikonin tyyppi</translation>
</message>
</context>
<context>
@ -4849,23 +4849,23 @@ Bugeja ja ongelmia voi esiintyä. Tämä versio ei ole tarkoitettu päivittäise
</message>
<message>
<source>Perform Auto-Type Sequence</source>
<translation type="unfinished"/>
<translation>Suorita automaattisyötön sekvenssi</translation>
</message>
<message>
<source>{USERNAME}</source>
<translation type="unfinished"/>
<translation>{USERNAME}</translation>
</message>
<message>
<source>{USERNAME}{ENTER}</source>
<translation type="unfinished"/>
<translation>{USERNAME}{ENTER}</translation>
</message>
<message>
<source>{PASSWORD}</source>
<translation type="unfinished"/>
<translation>{PASSWORD}</translation>
</message>
<message>
<source>{PASSWORD}{ENTER}</source>
<translation type="unfinished"/>
<translation>{PASSWORD}{ENTER}</translation>
</message>
</context>
<context>

View File

@ -330,15 +330,15 @@
</message>
<message>
<source>Auto-Type typing delay:</source>
<translation>Vitesse de remplissage de la saisie automatique :</translation>
<translation>Vitesse de remplissage de la saisie automatique :</translation>
</message>
<message>
<source>Global Auto-Type shortcut:</source>
<translation>Raccourci de la saisie automatique :</translation>
<translation>Raccourci de la saisie automatique :</translation>
</message>
<message>
<source>Auto-Type start delay:</source>
<translation>Délai de démarrage de la saisie automatique :</translation>
<translation>Délai de démarrage de la saisie automatique :</translation>
</message>
<message>
<source>Automatically save when locking database</source>
@ -439,11 +439,11 @@
</message>
<message>
<source>Require password repeat when it is visible</source>
<translation>Demander de confirmer le mot de passe lorsque celui-ci est visible</translation>
<translation>Exiger la confirmation du mot de passe sil est visible</translation>
</message>
<message>
<source>Hide passwords when editing them</source>
<translation>Cacher les mots de passe pendant leur modification</translation>
<translation>Cacher les mots de passe lors de leur modification</translation>
</message>
<message>
<source>Use placeholder for empty password fields</source>
@ -564,11 +564,11 @@
<name>BrowserAccessControlDialog</name>
<message>
<source>KeePassXC - Browser Access Request</source>
<translation>Requiert laccès à KeePassXC-Browser</translation>
<translation>KeePassXC-Browser - Requête d&apos;accès au navigateur</translation>
</message>
<message>
<source>%1 is requesting access to the following entries:</source>
<translation>%1 demande laccès aux entrées suivantes :</translation>
<translation>%1 demande laccès aux entrées suivantes :</translation>
</message>
<message>
<source>Remember access to checked entries</source>
@ -892,7 +892,7 @@ chrome-laptop</translation>
</message>
<message>
<source>Browser type:</source>
<translation>Type de navigateur :</translation>
<translation>Type de navigateur :</translation>
</message>
<message>
<source>Toolbar button style</source>
@ -900,7 +900,7 @@ chrome-laptop</translation>
</message>
<message>
<source>Config Location:</source>
<translation>Emplacement de configuration :</translation>
<translation>Emplacement de configuration :</translation>
</message>
<message>
<source>Custom browser location field</source>
@ -2764,7 +2764,7 @@ Désactiver les enregistrements sécurisés et ressayer?</translation>
</message>
<message>
<source>KeeShare unsigned container</source>
<translation>Conteneur KeeShare non signé</translation>
<translation>Conteneur KeeShare non signé</translation>
</message>
<message>
<source>KeeShare signed container</source>
@ -4135,7 +4135,7 @@ Ligne %2, colonne %3</translation>
</message>
<message>
<source>Incorrect group creation time field size</source>
<translation>Taille du champ &quot;date du la création du groupe&quot; incorrect.</translation>
<translation>Taille du champ « date du la création du groupe » incorrect.</translation>
</message>
<message>
<source>Incorrect group modification time field size</source>
@ -4151,7 +4151,7 @@ Ligne %2, colonne %3</translation>
</message>
<message>
<source>Incorrect group icon field size</source>
<translation>Taille du champ &quot;icône du groupe&quot; incorrect.</translation>
<translation>Taille du champ « icône du groupe » incorrect.</translation>
</message>
<message>
<source>Incorrect group level field size</source>
@ -4911,11 +4911,11 @@ Attendez-vous à des bogues et des problèmes mineurs. Cette version nest pas
</message>
<message>
<source>Overwriting %1 [%2]</source>
<translation>Écrasement de %1 [%2]</translation>
<translation>Remplacement de %1 [%2]</translation>
</message>
<message>
<source>older entry merged from database &quot;%1&quot;</source>
<translation>ancienne entrée fusionnée de la base de données &quot;%1&quot;</translation>
<translation>ancienne entrée fusionnée de la base de données « %1 »</translation>
</message>
<message>
<source>Adding backup for older target %1 [%2]</source>
@ -5065,7 +5065,7 @@ Attendez-vous à des bogues et des problèmes mineurs. Cette version nest pas
</message>
<message>
<source>Unable to process clearText in place</source>
<translation>Impossible dactiver le traitement de ClearText</translation>
<translation>Impossible dappliquer l&apos;amélioration ClearText</translation>
</message>
<message>
<source>Expected %1 bytes of clear-text, found %2</source>
@ -5282,7 +5282,7 @@ Attendez-vous à des bogues et des problèmes mineurs. Cette version nest pas
</message>
<message>
<source>Character Types</source>
<translation>Types de caractères:</translation>
<translation>Types de caractères</translation>
</message>
<message>
<source>Numbers</source>
@ -5326,7 +5326,7 @@ Attendez-vous à des bogues et des problèmes mineurs. Cette version nest pas
</message>
<message>
<source>Password Quality: %1</source>
<translation>Qualité du mot de passe : %1</translation>
<translation>Qualité du mot de passe : %1</translation>
</message>
<message>
<source>Poor</source>
@ -5382,7 +5382,7 @@ Attendez-vous à des bogues et des problèmes mineurs. Cette version nest pas
</message>
<message>
<source>Add non-hex letters to &quot;do not include&quot; list</source>
<translation>Ajouter les lettres non-hexadécimales à la liste &quot;Ne pas inclure&quot;</translation>
<translation>Ajouter les lettres non-hexadécimales à la liste « Ne pas inclure »</translation>
</message>
<message>
<source>Hex</source>
@ -5815,7 +5815,7 @@ Commandes proposées :
</message>
<message>
<source>malformed string</source>
<translation>chaîne de caractères malformée</translation>
<translation>chaîne de caractères incorrecte</translation>
</message>
<message>
<source>missing closing quote</source>
@ -5889,7 +5889,7 @@ Commandes proposées :
</message>
<message>
<source>Successfully added entry %1.</source>
<translation>Ajouté avec succès lentrée %1.</translation>
<translation>Lentrée %1 a bien é ajoutée.</translation>
</message>
<message>
<source>Invalid timeout value %1.</source>
@ -6091,7 +6091,7 @@ Commandes proposées :
<message>
<source>Error reading merge file:
%1</source>
<translation>Erreur lors de la lecture du fichier fusionner :
<translation>Erreur lors de la lecture du fichier à fusionner :
%1</translation>
</message>
<message>
@ -6104,11 +6104,11 @@ Commandes proposées :
</message>
<message>
<source>Successfully recycled entry %1.</source>
<translation>Entrée %1 recyclée avec succès.</translation>
<translation>L&apos;entrée %1 a bien é recyclée.</translation>
</message>
<message>
<source>Successfully deleted entry %1.</source>
<translation>Supprimé lentrée %1 avec succès.</translation>
<translation>Lentrée %1 a bien é supprimée.</translation>
</message>
<message>
<source>Show the entry&apos;s current TOTP.</source>
@ -6128,7 +6128,7 @@ Commandes proposées :
</message>
<message>
<source>%1: (row, col) %2,%3</source>
<translation>%1: (ligne,colonne) %2,%3</translation>
<translation>%1 : (ligne, colonne) %2, %3</translation>
</message>
<message>
<source>Argon2 (KDBX 4 recommended)</source>
@ -6174,7 +6174,7 @@ Commandes proposées :
</message>
<message>
<source>No key is set. Aborting database creation.</source>
<translation>Aucune clé définie. Abandon de la création de la base de données.</translation>
<translation>Aucune clé définie. La création de la base de données a é abandonnée.</translation>
</message>
<message>
<source>Failed to save the database: %1.</source>
@ -6186,11 +6186,11 @@ Commandes proposées :
</message>
<message>
<source>Creating KeyFile %1 failed: %2</source>
<translation>Creation du fichier clé %1 échoué : %2 </translation>
<translation>Impossible de créer le fichier clé %1 : %2 </translation>
</message>
<message>
<source>Loading KeyFile %1 failed: %2</source>
<translation>Chargement du fichier clé %1 échoué : %2</translation>
<translation>Impossible de charger le fichier clé %1 : %2</translation>
</message>
<message>
<source>Path of the entry to remove.</source>
@ -6366,7 +6366,7 @@ Noyau : %3 %4</translation>
</message>
<message>
<source>Failed to open HIBP file %1: %2</source>
<translation>Échec de louverture du fichier HIBP %1 : %2</translation>
<translation>Impossible d&apos;ouvrir le fichier HIBP %1 : %2</translation>
</message>
<message>
<source>Evaluating database entries against HIBP file, this will take a while...</source>
@ -6728,7 +6728,7 @@ Noyau : %3 %4</translation>
</message>
<message>
<source>ChaCha20 256-bit</source>
<translation>ChaCha20 : 256 bits {20 256 à ?}</translation>
<translation>ChaCha20 : 256 bits {20 256 à ?}</translation>
</message>
<message>
<source>Benchmark %1 delay</source>
@ -7272,7 +7272,7 @@ Noyau : %3 %4</translation>
</message>
<message>
<source>Key:</source>
<translation>Clé:</translation>
<translation>Clé :</translation>
</message>
<message>
<source>Generate</source>
@ -7357,7 +7357,7 @@ Noyau : %3 %4</translation>
</message>
<message>
<source>The exported certificate is not the same as the one in use. Do you want to export the current certificate?</source>
<translation>Le certificat exporté est différent de celui en cours dutilisation. Souhaitez-vous exporter le certificat actuel?</translation>
<translation>Le certificat exporté est différent de celui en cours dutilisation. Souhaitez-vous exporter le certificat actuel ?</translation>
</message>
<message>
<source>Signer:</source>
@ -7565,7 +7565,7 @@ Noyau : %3 %4</translation>
<name>TotpDialog</name>
<message>
<source>Timed Password</source>
<translation>Mot de passe programmé</translation>
<translation>Mot de passe planifié</translation>
</message>
<message>
<source>000000</source>
@ -7633,7 +7633,7 @@ Noyau : %3 %4</translation>
</message>
<message>
<source>Code size:</source>
<translation>Taille du code:</translation>
<translation>Taille du code :</translation>
</message>
<message>
<source>Secret Key:</source>
@ -7701,7 +7701,7 @@ Exemple : JBSWY3DPEHPK3PXP</translation>
</message>
<message>
<source>Update Error!</source>
<translation>Erreur de mise à jour!</translation>
<translation>Erreur lors de mise à jour !</translation>
</message>
<message>
<source>An error occurred in retrieving update information.</source>
@ -7729,7 +7729,7 @@ Exemple : JBSWY3DPEHPK3PXP</translation>
</message>
<message>
<source>You&apos;re up-to-date!</source>
<translation>Votre version est à jour!</translation>
<translation>Votre version est à jour !</translation>
</message>
<message>
<source>KeePassXC %1 is currently the newest version available</source>

File diff suppressed because it is too large Load Diff

View File

@ -3032,7 +3032,7 @@ Támogatott kiterjesztések: %1.</translation>
</message>
<message>
<source>Plugin Data</source>
<translation>Beépülő adati</translation>
<translation>Beépülő adatai</translation>
</message>
<message>
<source>Remove</source>
@ -3085,7 +3085,7 @@ Ez a kijelölt bővítmény hibás működését eredményezheti.</translation>
<name>Entry</name>
<message>
<source>%1 - Clone</source>
<translation>%1 Klónozás</translation>
<translation>%1 Klón</translation>
</message>
</context>
<context>
@ -4851,19 +4851,19 @@ Néhány hiba és kisebb nehézségek várhatóak, ezért ez a verzió nem aján
</message>
<message>
<source>{USERNAME}</source>
<translation type="unfinished"/>
<translation>{FELHASZNÁLÓNÉV}</translation>
</message>
<message>
<source>{USERNAME}{ENTER}</source>
<translation type="unfinished"/>
<translation>{FELHASZNÁLÓNÉV}{ENTER}</translation>
</message>
<message>
<source>{PASSWORD}</source>
<translation type="unfinished"/>
<translation>{JELSZÓ}</translation>
</message>
<message>
<source>{PASSWORD}{ENTER}</source>
<translation type="unfinished"/>
<translation>{JELSZÓ}{ENTER}</translation>
</message>
</context>
<context>
@ -5510,7 +5510,7 @@ Néhány hiba és kisebb nehézségek várhatóak, ezért ez a verzió nem aján
</message>
<message>
<source>Very weak password</source>
<translation>Nagy gyenge jelszó</translation>
<translation>Nagyon gyenge jelszó</translation>
</message>
<message>
<source>Password entropy is %1 bits</source>

View File

@ -350,7 +350,7 @@
</message>
<message>
<source>Tray icon type</source>
<translation type="unfinished"/>
<translation>Systeemvak-pictogram</translation>
</message>
</context>
<context>
@ -4852,19 +4852,19 @@ Wil je KeePassXC nu opnieuw opstarten?</translation>
</message>
<message>
<source>{USERNAME}</source>
<translation type="unfinished"/>
<translation>{GEBRUIKERSNAAM}</translation>
</message>
<message>
<source>{USERNAME}{ENTER}</source>
<translation type="unfinished"/>
<translation>{GEBRUIKERSNAAM}{ENTER}</translation>
</message>
<message>
<source>{PASSWORD}</source>
<translation type="unfinished"/>
<translation>{WACHTWOORD}</translation>
</message>
<message>
<source>{PASSWORD}{ENTER}</source>
<translation type="unfinished"/>
<translation>{WACHTWOORD}{ENTER}</translation>
</message>
</context>
<context>

View File

@ -11,7 +11,7 @@
</message>
<message>
<source>Report bugs at: &lt;a href=&quot;https://github.com/keepassxreboot/keepassxc/issues&quot; style=&quot;text-decoration: underline;&quot;&gt;https://github.com&lt;/a&gt;</source>
<translation>Сообщать об ошибках: &lt;a href=&quot;https://github.com/keepassxreboot/keepassxc/issues&quot; style=&quot;text-decoration: underline;&quot;&gt;https://github.com&lt;/a&gt;</translation>
<translation>Сообщить об &lt;a href=&quot;https://github.com/keepassxreboot/keepassxc/issues&quot; style=&quot;text-decoration: underline;&quot;&gt;ошибках&lt;/a&gt; по https://github.com</translation>
</message>
<message>
<source>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</source>
@ -19,11 +19,11 @@
</message>
<message>
<source>Contributors</source>
<translation>Авторы</translation>
<translation>Соавторы</translation>
</message>
<message>
<source>&lt;a href=&quot;https://github.com/keepassxreboot/keepassxc/graphs/contributors&quot;&gt;See Contributions on GitHub&lt;/a&gt;</source>
<translation>&lt;a href=&quot;https://github.com/keepassxreboot/keepassxc/graphs/contributors&quot;&gt;Посмотреть вклад на GitHub&lt;/a&gt;</translation>
<translation>&lt;a href=&quot;https://github.com/keepassxreboot/keepassxc/graphs/contributors&quot;&gt;Посмотреть соавторов на GitHub&lt;/a&gt;</translation>
</message>
<message>
<source>Debug Info</source>
@ -2368,7 +2368,7 @@ Disable safe saves and try again?</source>
</message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the entry will not appear in reports like Health Check and HIBP even if it doesn&apos;t match the quality requirements (e. g. password entropy or re-use). You can set the check mark if the password is beyond your control (e. g. if it needs to be a four-digit PIN) to prevent it from cluttering the reports.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"/>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;При включении, запись не появится в отчетах (например, Проверки безопасности или HIBP), даже если она не соответствует требованиям к качеству (энтропия, переиспользование). Можно включить этот параметр, если вы не можете контролировать этот пароль (например, 4-значные пин-кодыж), чтобы не засорять отчет.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>Exclude from database reports</source>
@ -2482,7 +2482,7 @@ Disable safe saves and try again?</source>
</message>
<message>
<source>Only send this setting to the browser for HTTP Auth dialogs. If enabled, normal login forms will not show this entry for selection.</source>
<translation type="unfinished"/>
<translation>Отправлять эту настройку только браузерным диалогам для HTTP Auth. Если включено, обычные формы авторизации не покажут запись среди вариантов выбора.</translation>
</message>
<message>
<source>Use this entry only with HTTP Basic Auth</source>
@ -6709,7 +6709,7 @@ Kernel: %3 %4</source>
<message>
<source>All clipping programs failed. Tried %1
</source>
<translation type="unfinished"/>
<translation>Ни одна программа копирования не сработала. Пробовали %1</translation>
</message>
<message>
<source>AES (%1 rounds)</source>
@ -6847,7 +6847,7 @@ Kernel: %3 %4</source>
</message>
<message>
<source>Reason</source>
<translation>Причика</translation>
<translation>Причина</translation>
</message>
<message>
<source>Edit Entry...</source>

View File

@ -137,7 +137,7 @@
</message>
<message>
<source>You must restart the application to set the new language. Would you like to restart now?</source>
<translation type="unfinished"/>
<translation>Musíte reštartovať aplikáciu, aby sa tieto zmeny prejavili. Chcete ju reštartovať teraz?</translation>
</message>
</context>
<context>
@ -342,15 +342,15 @@
</message>
<message>
<source>Automatically save when locking database</source>
<translation type="unfinished"/>
<translation>Pri zamknutí databázy automaticky uložiť</translation>
</message>
<message>
<source>Automatically save non-data changes when locking database</source>
<translation type="unfinished"/>
<translation>Pri zamknutí databázy automaticky uložiť nedátové zmeny</translation>
</message>
<message>
<source>Tray icon type</source>
<translation type="unfinished"/>
<translation>Typ ikona oznamovacej oblasti</translation>
</message>
</context>
<context>
@ -956,7 +956,7 @@ chrome-laptop.</translation>
</message>
<message>
<source>Select native messaging host folder location</source>
<translation type="unfinished"/>
<translation>Vyberte umiestnenie zložky hostiteľa správe medzi prehliadačom a KeePassXC</translation>
</message>
</context>
<context>
@ -4854,23 +4854,23 @@ Očakávajte chyby a menšie problémy, táto verzia nie je určená na produkč
</message>
<message>
<source>Perform Auto-Type Sequence</source>
<translation type="unfinished"/>
<translation>Vykonať Automatické vypĺňanie</translation>
</message>
<message>
<source>{USERNAME}</source>
<translation type="unfinished"/>
<translation>{POUŽÍVATEĽ}</translation>
</message>
<message>
<source>{USERNAME}{ENTER}</source>
<translation type="unfinished"/>
<translation>{POUŽÍVATEĽ}{ENTER}</translation>
</message>
<message>
<source>{PASSWORD}</source>
<translation type="unfinished"/>
<translation>{HESLO}</translation>
</message>
<message>
<source>{PASSWORD}{ENTER}</source>
<translation type="unfinished"/>
<translation>{HESLO}{ENTER}</translation>
</message>
</context>
<context>
@ -6571,7 +6571,7 @@ Jadro: %3 %4</translation>
</message>
<message>
<source>Could not save the native messaging script file for %1.</source>
<translation type="unfinished"/>
<translation>Nemožno uložiť súbor skriptu správ medzi prehliadačom a KeePassXC (native messaging) pre %1.</translation>
</message>
<message>
<source>Copy the given attribute to the clipboard. Defaults to &quot;password&quot; if not specified.</source>

View File

@ -3127,7 +3127,7 @@ This may cause the affected plugins to malfunction.</source>
</message>
<message numerus="yes">
<source>Are you sure you want to remove %n attachment(s)?</source>
<translation><numerusform> n </numerusform></translation>
<translation><numerusform></numerusform></translation>
</message>
<message>
<source>Save attachments</source>

View File

@ -4853,19 +4853,19 @@ Expect some bugs and minor issues, this version is not meant for production use.
</message>
<message>
<source>{USERNAME}</source>
<translation type="unfinished"/>
<translation>{USERNAME}</translation>
</message>
<message>
<source>{USERNAME}{ENTER}</source>
<translation type="unfinished"/>
<translation>{USERNAME}{ENTER}</translation>
</message>
<message>
<source>{PASSWORD}</source>
<translation type="unfinished"/>
<translation>{PASSWORD}</translation>
</message>
<message>
<source>{PASSWORD}{ENTER}</source>
<translation type="unfinished"/>
<translation>{PASSWORD}{ENTER}</translation>
</message>
</context>
<context>

View File

@ -1,5 +1,5 @@
name: keepassxc
version: 2.6.2
version: 2.6.3
grade: stable
summary: Community-driven port of the Windows application “KeePass Password Safe”
description: |

View File

@ -321,7 +321,7 @@ QString BrowserService::storeKey(const QString& key)
do {
QInputDialog keyDialog;
connect(m_currentDatabaseWidget, SIGNAL(databaseLocked()), &keyDialog, SLOT(reject()));
connect(m_currentDatabaseWidget, SIGNAL(databaseLockRequested()), &keyDialog, SLOT(reject()));
keyDialog.setWindowTitle(tr("KeePassXC: New key association request"));
keyDialog.setLabelText(tr("You have received an association request for the following database:\n%1\n\n"
"Give the connection a unique name or ID, for example:\nchrome-laptop.")
@ -745,7 +745,7 @@ BrowserService::sortEntries(QList<Entry*>& pwEntries, const QString& siteUrlStr,
// Sort same priority entries by Title or UserName
auto entries = priorities.values(key);
std::sort(entries.begin(), entries.end(), [&sortField](Entry* left, Entry* right) {
return QString::localeAwareCompare(left->attribute(sortField), right->attribute(sortField));
return QString::localeAwareCompare(left->attribute(sortField), right->attribute(sortField)) < 0;
});
results << entries;
if (browserSettings()->bestMatchOnly() && !results.isEmpty()) {
@ -772,7 +772,7 @@ QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
updateWindowState();
BrowserAccessControlDialog accessControlDialog;
connect(m_currentDatabaseWidget, SIGNAL(databaseLocked()), &accessControlDialog, SLOT(reject()));
connect(m_currentDatabaseWidget, SIGNAL(databaseLockRequested()), &accessControlDialog, SLOT(reject()));
connect(&accessControlDialog, &BrowserAccessControlDialog::disableAccess, [&](QTableWidgetItem* item) {
auto entry = pwEntriesToConfirm[item->row()];

View File

@ -83,7 +83,7 @@ int Import::execute(const QStringList& arguments)
QString errorMessage;
Database db;
db.setKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2));
db.setKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2D));
db.setKey(key);
if (!db.import(xmlExportPath, &errorMessage)) {

View File

@ -132,9 +132,9 @@ namespace Utils
return {};
}
if (fileKey->type() != FileKey::Hashed) {
err << QObject::tr("WARNING: You are using a legacy key file format which may become\n"
"unsupported in the future.\n\n"
if (fileKey->type() != FileKey::KeePass2XMLv2 && fileKey->type() != FileKey::Hashed) {
err << QObject::tr("WARNING: You are using an old key file format which KeePassXC may\n"
"stop supporting in the future.\n\n"
"Please consider generating a new key file.")
<< endl;
}

View File

@ -36,8 +36,6 @@ const int Entry::ResolveMaximumDepth = 10;
const QString Entry::AutoTypeSequenceUsername = "{USERNAME}{ENTER}";
const QString Entry::AutoTypeSequencePassword = "{PASSWORD}{ENTER}";
Entry::CloneFlags Entry::DefaultCloneFlags = Entry::CloneNewUuid | Entry::CloneResetTimeInfo;
Entry::Entry()
: m_attributes(new EntryAttributes(this))
, m_attachments(new EntryAttachments(this))
@ -465,7 +463,8 @@ void Entry::setTotp(QSharedPointer<Totp::Settings> settings)
m_data.totpSettings.reset();
} else {
m_data.totpSettings = std::move(settings);
auto text = Totp::writeSettings(m_data.totpSettings, title(), username());
auto text = Totp::writeSettings(
m_data.totpSettings, resolveMultiplePlaceholders(title()), resolveMultiplePlaceholders(username()));
if (m_data.totpSettings->format != Totp::StorageFormat::LEGACY) {
m_attributes->set(Totp::ATTRIBUTE_OTP, text, true);
} else {
@ -493,6 +492,15 @@ QSharedPointer<Totp::Settings> Entry::totpSettings() const
return m_data.totpSettings;
}
QString Entry::totpSettingsString() const
{
if (m_data.totpSettings) {
return Totp::writeSettings(
m_data.totpSettings, resolveMultiplePlaceholders(title()), resolveMultiplePlaceholders(username()), true);
}
return {};
}
void Entry::setUuid(const QUuid& uuid)
{
Q_ASSERT(!uuid.isNull());

View File

@ -106,6 +106,7 @@ public:
QString notes() const;
QString attribute(const QString& key) const;
QString totp() const;
QString totpSettingsString() const;
QSharedPointer<Totp::Settings> totpSettings() const;
int size() const;
@ -159,6 +160,8 @@ public:
CloneNewUuid = 1, // generate a random uuid for the clone
CloneResetTimeInfo = 2, // set all TimeInfo attributes to the current time
CloneIncludeHistory = 4, // clone the history items
CloneDefault = CloneNewUuid | CloneResetTimeInfo,
CloneCopy = CloneNewUuid | CloneResetTimeInfo | CloneIncludeHistory,
CloneRenameTitle = 8, // add "-Clone" after the original title
CloneUserAsRef = 16, // Add the user as a reference to the original entry
ClonePassAsRef = 32, // Add the password as a reference to the original entry
@ -208,7 +211,6 @@ public:
static const int ResolveMaximumDepth;
static const QString AutoTypeSequenceUsername;
static const QString AutoTypeSequencePassword;
static CloneFlags DefaultCloneFlags;
/**
* Creates a duplicate of this entry except that the returned entry isn't
@ -216,7 +218,7 @@ public:
* Note that you need to copy the custom icons manually when inserting the
* new entry into another database.
*/
Entry* clone(CloneFlags flags = DefaultCloneFlags) const;
Entry* clone(CloneFlags flags = CloneDefault) const;
void copyDataFrom(const Entry* other);
QString maskPasswordPlaceholders(const QString& str) const;
Entry* resolveReference(const QString& str) const;

View File

@ -36,9 +36,6 @@ const int Group::DefaultIconNumber = 48;
const int Group::RecycleBinIconNumber = 43;
const QString Group::RootAutoTypeSequence = "{USERNAME}{TAB}{PASSWORD}{ENTER}";
Group::CloneFlags Group::DefaultCloneFlags =
Group::CloneNewUuid | Group::CloneResetTimeInfo | Group::CloneIncludeEntries;
Group::Group()
: m_customData(new CustomData(this))
, m_updateTimeinfo(true)

View File

@ -56,6 +56,7 @@ public:
CloneNewUuid = 1, // generate a random uuid for the clone
CloneResetTimeInfo = 2, // set all TimeInfo attributes to the current time
CloneIncludeEntries = 4, // clone the group entries
CloneDefault = CloneNewUuid | CloneResetTimeInfo | CloneIncludeEntries,
};
Q_DECLARE_FLAGS(CloneFlags, CloneFlag)
@ -108,7 +109,6 @@ public:
static const int DefaultIconNumber;
static const int RecycleBinIconNumber;
static CloneFlags DefaultCloneFlags;
static const QString RootAutoTypeSequence;
Group* findChildByName(const QString& name);
@ -157,8 +157,8 @@ public:
QSet<QUuid> customIconsRecursive() const;
QList<QString> usernamesRecursive(int topN = -1) const;
Group* clone(Entry::CloneFlags entryFlags = Entry::DefaultCloneFlags,
CloneFlags groupFlags = DefaultCloneFlags) const;
Group* clone(Entry::CloneFlags entryFlags = Entry::CloneDefault,
Group::CloneFlags groupFlags = Group::CloneDefault) const;
void copyDataFrom(const Group* other);
QString print(bool recursive = false, bool flatten = false, int depth = 0);

View File

@ -73,7 +73,7 @@ QString Crypto::debugInfo()
Q_ASSERT(Crypto::initialized());
QString debugInfo = QObject::tr("Cryptographic libraries:").append("\n");
debugInfo.append(" libgcrypt ").append(m_backendVersion).append("\n");
debugInfo.append("- libgcrypt ").append(m_backendVersion).append("\n");
return debugInfo;
}

View File

@ -29,8 +29,9 @@
* a 256-bit salt is generated each time the database is saved, the tag length is 256 bits, no secret key
* or associated data. KeePass uses the latest version of Argon2, v1.3.
*/
Argon2Kdf::Argon2Kdf()
: Kdf::Kdf(KeePass2::KDF_ARGON2)
Argon2Kdf::Argon2Kdf(Type type)
: Kdf::Kdf(KeePass2::KDF_ARGON2D)
, m_type(type)
, m_version(0x13)
, m_memory(1 << 16)
, m_parallelism(static_cast<quint32>(QThread::idealThreadCount()))
@ -54,6 +55,16 @@ bool Argon2Kdf::setVersion(quint32 version)
return false;
}
Argon2Kdf::Type Argon2Kdf::type() const
{
return m_type;
}
void Argon2Kdf::setType(Type type)
{
m_type = type;
}
quint64 Argon2Kdf::memory() const
{
return m_memory;
@ -133,7 +144,11 @@ bool Argon2Kdf::processParameters(const QVariantMap& p)
QVariantMap Argon2Kdf::writeParameters()
{
QVariantMap p;
p.insert(KeePass2::KDFPARAM_UUID, KeePass2::KDF_ARGON2.toRfc4122());
if (type() == Type::Argon2d) {
p.insert(KeePass2::KDFPARAM_UUID, KeePass2::KDF_ARGON2D.toRfc4122());
} else {
p.insert(KeePass2::KDFPARAM_UUID, KeePass2::KDF_ARGON2ID.toRfc4122());
}
p.insert(KeePass2::KDFPARAM_ARGON2_VERSION, version());
p.insert(KeePass2::KDFPARAM_ARGON2_PARALLELISM, parallelism());
p.insert(KeePass2::KDFPARAM_ARGON2_MEMORY, memory() * 1024);
@ -158,18 +173,20 @@ bool Argon2Kdf::transform(const QByteArray& raw, QByteArray& result) const
{
result.clear();
result.resize(32);
return transformKeyRaw(raw, seed(), version(), rounds(), memory(), parallelism(), result);
return transformKeyRaw(raw, seed(), version(), type(), rounds(), memory(), parallelism(), result);
}
bool Argon2Kdf::transformKeyRaw(const QByteArray& key,
const QByteArray& seed,
quint32 version,
Type type,
quint32 rounds,
quint64 memory,
quint32 parallelism,
QByteArray& result)
{
// Time Cost, Mem Cost, Threads/Lanes, Password, length, Salt, length, out, length
int rc = argon2_hash(rounds,
memory,
parallelism,
@ -181,7 +198,7 @@ bool Argon2Kdf::transformKeyRaw(const QByteArray& key,
result.size(),
nullptr,
0,
Argon2_d,
type == Type::Argon2d ? Argon2_d : Argon2_id,
version);
if (rc != ARGON2_OK) {
qWarning("Argon2 error: %s", argon2_error_message(rc));
@ -205,7 +222,7 @@ int Argon2Kdf::benchmarkImpl(int msec) const
timer.start();
int rounds = 4;
if (transformKeyRaw(key, seed, version(), rounds, memory(), parallelism(), key)) {
if (transformKeyRaw(key, seed, version(), type(), rounds, memory(), parallelism(), key)) {
return static_cast<int>(rounds * (static_cast<float>(msec) / timer.elapsed()));
}
@ -214,5 +231,6 @@ int Argon2Kdf::benchmarkImpl(int msec) const
QString Argon2Kdf::toString() const
{
return QObject::tr("Argon2 (%1 rounds, %2 KB)").arg(QString::number(rounds()), QString::number(memory()));
return QObject::tr("Argon2%1 (%2 rounds, %3 KB)")
.arg(type() == Type::Argon2d ? "d" : "id", QString::number(rounds()), QString::number(memory()));
}

View File

@ -23,7 +23,13 @@
class Argon2Kdf : public Kdf
{
public:
Argon2Kdf();
enum class Type
{
Argon2d,
Argon2id
};
Argon2Kdf(Type type);
bool processParameters(const QVariantMap& p) override;
QVariantMap writeParameters() override;
@ -32,6 +38,8 @@ public:
quint32 version() const;
bool setVersion(quint32 version);
Type type() const;
void setType(Type type);
quint64 memory() const;
bool setMemory(quint64 kibibytes);
quint32 parallelism() const;
@ -41,6 +49,7 @@ public:
protected:
int benchmarkImpl(int msec) const override;
Type m_type;
quint32 m_version;
quint64 m_memory;
quint32 m_parallelism;
@ -49,6 +58,7 @@ private:
Q_REQUIRED_RESULT static bool transformKeyRaw(const QByteArray& key,
const QByteArray& seed,
quint32 version,
Type type,
quint32 rounds,
quint64 memory,
quint32 parallelism,

View File

@ -67,6 +67,10 @@ QString CsvExporter::exportHeader()
addColumn(header, "Password");
addColumn(header, "URL");
addColumn(header, "Notes");
addColumn(header, "TOTP");
addColumn(header, "Icon");
addColumn(header, "Last Modified");
addColumn(header, "Created");
return header + QString("\n");
}
@ -88,6 +92,10 @@ QString CsvExporter::exportGroup(const Group* group, QString groupPath)
addColumn(line, entry->password());
addColumn(line, entry->url());
addColumn(line, entry->notes());
addColumn(line, entry->totpSettingsString());
addColumn(line, QString::number(entry->iconNumber()));
addColumn(line, entry->timeInfo().lastModificationTime().toString(Qt::ISODate));
addColumn(line, entry->timeInfo().creationTime().toString(Qt::ISODate));
line.append("\n");
response.append(line);

View File

@ -30,7 +30,8 @@ const QUuid KeePass2::CIPHER_CHACHA20 = QUuid("d6038a2b-8b6f-4cb5-a524-339a31dbb
const QUuid KeePass2::KDF_AES_KDBX3 = QUuid("c9d9f39a-628a-4460-bf74-0d08c18a4fea");
const QUuid KeePass2::KDF_AES_KDBX4 = QUuid("7c02bb82-79a7-4ac0-927d-114a00648238");
const QUuid KeePass2::KDF_ARGON2 = QUuid("ef636ddf-8c29-444b-91f7-a9a403e30a0c");
const QUuid KeePass2::KDF_ARGON2D = QUuid("ef636ddf-8c29-444b-91f7-a9a403e30a0c");
const QUuid KeePass2::KDF_ARGON2ID = QUuid("9e298b19-56db-4773-b23d-fc3ec6f0a1e6");
const QByteArray KeePass2::INNER_STREAM_SALSA20_IV("\xe8\x30\x09\x4b\x97\x20\x5d\x2a");
@ -53,7 +54,8 @@ const QList<QPair<QUuid, QString>> KeePass2::CIPHERS{
qMakePair(KeePass2::CIPHER_CHACHA20, QObject::tr("ChaCha20 256-bit"))};
const QList<QPair<QUuid, QString>> KeePass2::KDFS{
qMakePair(KeePass2::KDF_ARGON2, QObject::tr("Argon2 (KDBX 4 recommended)")),
qMakePair(KeePass2::KDF_ARGON2D, QObject::tr("Argon2d (KDBX 4 recommended)")),
qMakePair(KeePass2::KDF_ARGON2ID, QObject::tr("Argon2id (KDBX 4)")),
qMakePair(KeePass2::KDF_AES_KDBX4, QObject::tr("AES-KDF (KDBX 4)")),
qMakePair(KeePass2::KDF_AES_KDBX3, QObject::tr("AES-KDF (KDBX 3.1)"))};
@ -109,8 +111,11 @@ QSharedPointer<Kdf> KeePass2::uuidToKdf(const QUuid& uuid)
if (uuid == KDF_AES_KDBX4) {
return QSharedPointer<AesKdf>::create();
}
if (uuid == KDF_ARGON2) {
return QSharedPointer<Argon2Kdf>::create();
if (uuid == KDF_ARGON2D) {
return QSharedPointer<Argon2Kdf>::create(Argon2Kdf::Type::Argon2d);
}
if (uuid == KDF_ARGON2ID) {
return QSharedPointer<Argon2Kdf>::create(Argon2Kdf::Type::Argon2id);
}
return {};

View File

@ -53,7 +53,8 @@ namespace KeePass2
extern const QUuid KDF_AES_KDBX3;
extern const QUuid KDF_AES_KDBX4;
extern const QUuid KDF_ARGON2;
extern const QUuid KDF_ARGON2D;
extern const QUuid KDF_ARGON2ID;
extern const QByteArray INNER_STREAM_SALSA20_IV;

View File

@ -72,7 +72,7 @@ Database* OpVaultReader::readDatabase(QDir& opdataDir, const QString& password)
key->addKey(QSharedPointer<PasswordKey>::create(password));
QScopedPointer<Database> db(new Database());
db->setKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2));
db->setKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2D));
db->setCipher(KeePass2::CIPHER_AES256);
db->setKey(key, true, false);
db->metadata()->setName(vaultName);

View File

@ -59,6 +59,7 @@ static const QString aboutContributors = R"(
<li>Kernellinux</li>
<li>Micha Ober</li>
<li>PublicByte</li>
<li>Clayton Casciato</li>
</ul>
<h3>Notable Code Contributions:</h3>
<ul>
@ -86,7 +87,6 @@ static const QString aboutContributors = R"(
</ul>
<h3>Patreon Supporters:</h3>
<ul>
<li>Igor Zinovik</li>
<li>Alexanderjb</li>
<li>Richard Ames</li>
<li>SLmanDR</li>
@ -94,7 +94,7 @@ static const QString aboutContributors = R"(
<li>Tyler Gass</li>
<li>Nuutti Toivola</li>
<li>Gregory Werbin</li>
<li>Lionel Laské</li>
<li>Lionel Laské</li>
<li>Ivar</li>
<li>Darren</li>
<li>Brad</li>

View File

@ -59,7 +59,12 @@ void Clipboard::setText(const QString& text, bool clear)
clipboard->setMimeData(mime, QClipboard::Clipboard);
#else
mime->setText(text);
#ifdef Q_OS_LINUX
mime->setData("x-kde-passwordManagerHint", QByteArrayLiteral("secret"));
#endif
#ifdef Q_OS_WIN
mime->setData("ExcludeClipboardContentFromMonitorProcessing", QByteArrayLiteral("1"));
#endif
clipboard->setMimeData(mime, QClipboard::Clipboard);
if (clipboard->supportsSelection()) {

View File

@ -20,12 +20,19 @@
#include "DatabaseWidget.h"
#include "core/Database.h"
#ifdef Q_OS_WIN
#include <QtPlatformHeaders/QWindowsWindowFunctions>
#endif
DatabaseOpenDialog::DatabaseOpenDialog(QWidget* parent)
: QDialog(parent)
, m_view(new DatabaseOpenWidget(this))
{
setWindowTitle(tr("Unlock Database - KeePassXC"));
setWindowFlags(Qt::Dialog | Qt::WindowStaysOnTopHint);
#ifdef Q_OS_WIN
QWindowsWindowFunctions::setWindowActivationBehavior(QWindowsWindowFunctions::AlwaysActivateWindow);
#endif
connect(m_view, SIGNAL(dialogFinished(bool)), this, SLOT(complete(bool)));
auto* layout = new QVBoxLayout();
layout->setMargin(0);

View File

@ -165,6 +165,7 @@ void DatabaseOpenWidget::clearForms()
m_ui->editPassword->setText("");
m_ui->editPassword->setShowPassword(false);
m_ui->keyFileLineEdit->clear();
m_ui->keyFileLineEdit->setShowPassword(false);
m_ui->checkTouchID->setChecked(false);
m_ui->challengeResponseCombo->clear();
m_db.reset();
@ -301,12 +302,14 @@ QSharedPointer<CompositeKey> DatabaseOpenWidget::buildDatabaseKey()
m_ui->messageWidget->showMessage(tr("Failed to open key file: %1").arg(errorMsg), MessageWidget::Error);
return {};
}
if (key->type() != FileKey::Hashed && !config()->get(Config::Messages_NoLegacyKeyFileWarning).toBool()) {
if (key->type() != FileKey::KeePass2XMLv2 && key->type() != FileKey::Hashed
&& !config()->get(Config::Messages_NoLegacyKeyFileWarning).toBool()) {
QMessageBox legacyWarning;
legacyWarning.setWindowTitle(tr("Legacy key file format"));
legacyWarning.setText(tr("You are using a legacy key file format which may become\n"
"unsupported in the future.\n\n"
"Please consider generating a new key file."));
legacyWarning.setWindowTitle(tr("Old key file format"));
legacyWarning.setText(tr("You are using an old key file format which KeePassXC may<br>"
"stop supporting in the future.<br><br>"
"Please consider generating a new key file by going to:<br>"
"<strong>Database / Database Security / Change Key File.</strong><br>"));
legacyWarning.setIcon(QMessageBox::Icon::Warning);
legacyWarning.addButton(QMessageBox::Ok);
legacyWarning.setDefaultButton(QMessageBox::Ok);
@ -355,7 +358,7 @@ void DatabaseOpenWidget::reject()
void DatabaseOpenWidget::browseKeyFile()
{
QString filters = QString("%1 (*);;%2 (*.key)").arg(tr("All files"), tr("Key files"));
QString filters = QString("%1 (*);;%2 (*.keyx; *.key)").arg(tr("All files"), tr("Key files"));
if (!config()->get(Config::RememberLastKeyFiles).toBool()) {
fileDialog()->setNextForgetDialog();
}
@ -378,6 +381,7 @@ void DatabaseOpenWidget::browseKeyFile()
void DatabaseOpenWidget::clearKeyFileText()
{
m_ui->keyFileLineEdit->clear();
m_ui->keyFileLineEdit->setShowPassword(false);
}
void DatabaseOpenWidget::pollHardwareKey()

View File

@ -406,7 +406,7 @@
<number>0</number>
</property>
<item row="0" column="1">
<widget class="QLineEdit" name="keyFileLineEdit">
<widget class="PasswordEdit" name="keyFileLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
@ -416,6 +416,9 @@
<property name="accessibleName">
<string>Key file to unlock the database</string>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>

View File

@ -216,7 +216,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
#ifdef WITH_XC_SSHAGENT
if (sshAgent()->isEnabled()) {
connect(this, SIGNAL(databaseLockRequested()), sshAgent(), SLOT(databaseLocked()));
connect(this, SIGNAL(databaseLocked()), sshAgent(), SLOT(databaseLocked()));
connect(this, SIGNAL(databaseUnlocked()), sshAgent(), SLOT(databaseUnlocked()));
}
#endif
@ -437,6 +437,7 @@ void DatabaseWidget::showTotp()
}
auto totpDialog = new TotpDialog(this, currentEntry);
connect(this, &DatabaseWidget::databaseLockRequested, totpDialog, &TotpDialog::close);
totpDialog->open();
}
@ -460,6 +461,7 @@ void DatabaseWidget::setupTotp()
auto setupTotpDialog = new TotpSetupDialog(this, currentEntry);
connect(setupTotpDialog, SIGNAL(totpUpdated()), SIGNAL(entrySelectionChanged()));
connect(this, &DatabaseWidget::databaseLockRequested, setupTotpDialog, &TotpSetupDialog::close);
setupTotpDialog->open();
}
@ -701,6 +703,7 @@ void DatabaseWidget::showTotpKeyQrCode()
auto currentEntry = currentSelectedEntry();
if (currentEntry) {
auto totpDisplayDialog = new TotpExportSettingsDialog(this, currentEntry);
connect(this, &DatabaseWidget::databaseLockRequested, totpDisplayDialog, &TotpExportSettingsDialog::close);
totpDisplayDialog->open();
}
}
@ -1081,6 +1084,9 @@ void DatabaseWidget::loadDatabase(bool accepted)
replaceDatabase(openWidget->database());
switchToMainView();
processAutoOpen();
restoreGroupEntryFocus(m_groupBeforeLock, m_entryBeforeLock);
m_groupBeforeLock = QUuid();
m_entryBeforeLock = QUuid();
m_saveAttempts = 0;
emit databaseUnlocked();
if (config()->get(Config::MinimizeAfterUnlock).toBool()) {
@ -1528,6 +1534,11 @@ bool DatabaseWidget::lock()
emit databaseLockRequested();
// ignore event if we are active and a modal dialog is still open (such as a message box or file dialog)
if (isVisible() && QApplication::activeModalWidget()) {
return false;
}
clipboard()->clearCopiedText();
if (isEditWidgetModified()) {

View File

@ -74,6 +74,18 @@ void EditWidget::addPage(const QString& labelText, const QIcon& icon, QWidget* w
m_ui->categoryList->addCategory(labelText, icon);
}
bool EditWidget::hasPage(QWidget* widget)
{
for (int i = 0; i < m_ui->stackedWidget->count(); i++) {
auto* scrollArea = qobject_cast<QScrollArea*>(m_ui->stackedWidget->widget(i));
if (scrollArea && scrollArea->widget() == widget) {
return true;
}
}
return false;
}
void EditWidget::setPageHidden(QWidget* widget, bool hidden)
{
int index = -1;

View File

@ -42,6 +42,7 @@ public:
~EditWidget();
void addPage(const QString& labelText, const QIcon& icon, QWidget* widget);
bool hasPage(QWidget* widget);
void setPageHidden(QWidget* widget, bool hidden);
void setCurrentPage(int index);
void setHeadline(const QString& text);

View File

@ -286,14 +286,18 @@ void EntryPreviewWidget::updateEntryAdvancedTab()
setTabEnabled(m_ui->entryTabWidget, m_ui->entryAdvancedTab, hasAttributes || hasAttachments);
if (hasAttributes) {
QString attributesText;
QString attributesText("<table>");
for (const QString& key : customAttributes) {
QString value = m_currentEntry->attributes()->value(key);
QString value;
if (m_currentEntry->attributes()->isProtected(key)) {
value = "<i>" + tr("[PROTECTED]") + "</i>";
} else {
value = m_currentEntry->attributes()->value(key).toHtmlEscaped();
value.replace('\n', QLatin1String("<br/>"));
}
attributesText.append(tr("<b>%1</b>: %2", "attributes line").arg(key, value).append("<br/>"));
attributesText.append(tr("<tr><td><b>%1</b>:</td><td>%2</td></tr>", "attributes line").arg(key, value));
}
attributesText.append("</table>");
m_ui->entryAttributesEdit->setText(attributesText);
}
@ -303,6 +307,8 @@ void EntryPreviewWidget::updateEntryAdvancedTab()
void EntryPreviewWidget::updateEntryAutotypeTab()
{
Q_ASSERT(m_currentEntry);
m_ui->entrySequenceLabel->setText(m_currentEntry->effectiveAutoTypeSequence());
m_ui->entryAutotypeTree->clear();
QList<QTreeWidgetItem*> items;
const AutoTypeAssociations* autotypeAssociations = m_currentEntry->autoTypeAssociations();
@ -314,7 +320,7 @@ void EntryPreviewWidget::updateEntryAutotypeTab()
}
m_ui->entryAutotypeTree->addTopLevelItems(items);
setTabEnabled(m_ui->entryTabWidget, m_ui->entryAutotypeTab, !items.isEmpty());
setTabEnabled(m_ui->entryTabWidget, m_ui->entryAutotypeTab, m_currentEntry->autoTypeEnabled());
}
void EntryPreviewWidget::updateGroupHeaderLine()

View File

@ -705,6 +705,62 @@
<string>Autotype</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QWidget" name="entryAutotypeWidget" native="true">
<layout class="QGridLayout" name="gridLayout_3">
<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>5</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="entrySequenceTitleLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Default Sequence</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="entrySequenceLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">sequence</string>
</property>
<property name="alignment">
<set>Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QTreeWidget" name="entryAutotypeTree">
<property name="focusPolicy">

View File

@ -497,6 +497,14 @@ MainWindow::MainWindow()
connect(m_ui->actionOnlineHelp, SIGNAL(triggered()), SLOT(openOnlineHelp()));
connect(m_ui->actionKeyboardShortcuts, SIGNAL(triggered()), SLOT(openKeyboardShortcuts()));
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
// Install event filter for empty-area drag
auto* eventFilter = new MainWindowEventFilter(this);
m_ui->menubar->installEventFilter(eventFilter);
m_ui->toolBar->installEventFilter(eventFilter);
m_ui->tabWidget->tabBar()->installEventFilter(eventFilter);
#endif
#ifdef Q_OS_MACOS
setUnifiedTitleAndToolBarOnMac(true);
#endif
@ -519,6 +527,11 @@ MainWindow::MainWindow()
m_ui->actionGroupDownloadFavicons->setVisible(false);
m_ui->actionEntryDownloadIcon->setVisible(false);
#endif
#ifndef WITH_XC_DOCS
m_ui->actionGettingStarted->setVisible(false);
m_ui->actionUserGuide->setVisible(false);
m_ui->actionKeyboardShortcuts->setVisible(false);
#endif
// clang-format off
connect(m_ui->tabWidget, SIGNAL(messageGlobal(QString,MessageWidget::MessageType)),
@ -743,7 +756,9 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
m_ui->actionGroupSortDesc->setEnabled(groupSelected && currentGroupHasChildren);
m_ui->actionGroupEmptyRecycleBin->setVisible(recycleBinSelected);
m_ui->actionGroupEmptyRecycleBin->setEnabled(recycleBinSelected);
#ifdef WITH_XC_NETWORKING
m_ui->actionGroupDownloadFavicons->setVisible(!recycleBinSelected);
#endif
m_ui->actionGroupDownloadFavicons->setEnabled(groupSelected && currentGroupHasEntries
&& !recycleBinSelected);
m_ui->actionDatabaseSecurity->setEnabled(true);
@ -1529,11 +1544,6 @@ void MainWindow::toggleWindow()
void MainWindow::lockDatabasesAfterInactivity()
{
// ignore event if a modal dialog is open (such as a message box or file dialog)
if (QApplication::activeModalWidget()) {
return;
}
m_ui->tabWidget->lockDatabases();
}
@ -1754,3 +1764,44 @@ void MainWindow::initViewMenu()
config()->set(Config::GUI_HidePasswords, checked);
});
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
MainWindowEventFilter::MainWindowEventFilter(QObject* parent)
: QObject(parent)
{
}
/**
* MainWindow event filter to initiate empty-area drag on the toolbar, menubar, and tabbar.
*/
bool MainWindowEventFilter::eventFilter(QObject* watched, QEvent* event)
{
auto* mainWindow = getMainWindow();
if (!mainWindow || !mainWindow->m_ui) {
return QObject::eventFilter(watched, event);
}
if (event->type() == QEvent::MouseButtonPress) {
if (watched == mainWindow->m_ui->menubar) {
mainWindow->windowHandle()->startSystemMove();
// Continue processing events, so menus keep working.
return false;
} else if (watched == mainWindow->m_ui->toolBar) {
if (!mainWindow->m_ui->toolBar->isMovable() || mainWindow->m_ui->toolBar->cursor() != Qt::SizeAllCursor) {
mainWindow->windowHandle()->startSystemMove();
return false;
}
} else if (watched == mainWindow->m_ui->tabWidget->tabBar()) {
auto* m = static_cast<QMouseEvent*>(event);
if (mainWindow->m_ui->tabWidget->tabBar()->tabAt(m->pos()) == -1) {
mainWindow->windowHandle()->startSystemMove();
return true;
}
}
}
return QObject::eventFilter(watched, event);
}
#endif

View File

@ -35,6 +35,7 @@ namespace Ui
class InactivityTimer;
class SearchWidget;
class MainWindowEventFilter;
class MainWindow : public QMainWindow
{
@ -181,8 +182,21 @@ private:
QTimer m_updateCheckTimer;
QTimer m_trayIconTriggerTimer;
QSystemTrayIcon::ActivationReason m_trayIconTriggerReason;
friend class MainWindowEventFilter;
};
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
class MainWindowEventFilter : public QObject
{
Q_OBJECT
public:
explicit MainWindowEventFilter(QObject* parent);
bool eventFilter(QObject* watched, QEvent* event) override;
};
#endif
/**
* Return instance of MainWindow created on app load
* non-gui instances will return nullptr

View File

@ -173,9 +173,9 @@ void PasswordGeneratorWidget::saveSettings()
config()->set(Config::PasswordGenerator_AdvancedMode, m_ui->buttonAdvancedMode->isChecked());
if (m_ui->buttonAdvancedMode->isChecked()) {
config()->set(Config::PasswordGenerator_SpecialChars, m_ui->checkBoxSpecialChars->isChecked());
} else {
config()->set(Config::PasswordGenerator_Logograms, m_ui->checkBoxSpecialChars->isChecked());
} else {
config()->set(Config::PasswordGenerator_SpecialChars, m_ui->checkBoxSpecialChars->isChecked());
}
config()->set(Config::PasswordGenerator_Braces, m_ui->checkBoxBraces->isChecked());
config()->set(Config::PasswordGenerator_Punctuation, m_ui->checkBoxPunctuation->isChecked());

View File

@ -259,9 +259,6 @@ QProgressBar::chunk {
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>128</number>
</property>
<property name="value">
<number>20</number>
</property>

View File

@ -31,10 +31,7 @@ TotpDialog::TotpDialog(QWidget* parent, Entry* entry)
, m_ui(new Ui::TotpDialog())
, m_entry(entry)
{
if (!m_entry->hasTotp()) {
close();
return;
}
setAttribute(Qt::WA_DeleteOnClose);
m_ui->setupUi(this);
@ -42,14 +39,11 @@ TotpDialog::TotpDialog(QWidget* parent, Entry* entry)
resetCounter();
updateProgressBar();
connect(parent, SIGNAL(databaseLocked()), SLOT(close()));
connect(&m_totpUpdateTimer, SIGNAL(timeout()), this, SLOT(updateProgressBar()));
connect(&m_totpUpdateTimer, SIGNAL(timeout()), this, SLOT(updateSeconds()));
m_totpUpdateTimer.start(m_step * 10);
updateTotp();
setAttribute(Qt::WA_DeleteOnClose);
new QShortcut(QKeySequence(QKeySequence::Copy), this, SLOT(copyToClipboard()));
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Copy"));

View File

@ -59,7 +59,6 @@ TotpExportSettingsDialog::TotpExportSettingsDialog(DatabaseWidget* parent, Entry
connect(m_buttonBox, SIGNAL(rejected()), SLOT(close()));
connect(m_buttonBox, SIGNAL(accepted()), SLOT(copyToClipboard()));
connect(m_timer, SIGNAL(timeout()), SLOT(autoClose()));
connect(parent, SIGNAL(lockedDatabase()), SLOT(close()));
new QShortcut(QKeySequence(QKeySequence::Copy), this, SLOT(copyToClipboard()));

View File

@ -27,6 +27,7 @@
#include "format/KeePass2Writer.h"
#include "gui/MessageBox.h"
#include "gui/MessageWidget.h"
#include "totp/totp.h"
// I wanted to make the CSV import GUI future-proof, so if one day you need a new field,
// all you have to do is add a field to m_columnHeader, and the GUI will follow:
@ -39,7 +40,8 @@ CsvImportWidget::CsvImportWidget(QWidget* parent)
, m_comboModel(new QStringListModel(this))
, m_columnHeader(QStringList() << QObject::tr("Group") << QObject::tr("Title") << QObject::tr("Username")
<< QObject::tr("Password") << QObject::tr("URL") << QObject::tr("Notes")
<< QObject::tr("Last Modified") << QObject::tr("Created"))
<< QObject::tr("TOTP") << QObject::tr("Icon") << QObject::tr("Last Modified")
<< QObject::tr("Created"))
, m_fieldSeparatorList(QStringList() << ","
<< ";"
<< "-"
@ -54,7 +56,7 @@ CsvImportWidget::CsvImportWidget(QWidget* parent)
m_ui->messageWidget->setHidden(true);
m_combos << m_ui->groupCombo << m_ui->titleCombo << m_ui->usernameCombo << m_ui->passwordCombo << m_ui->urlCombo
<< m_ui->notesCombo << m_ui->lastModifiedCombo << m_ui->createdCombo;
<< m_ui->notesCombo << m_ui->totpCombo << m_ui->iconCombo << m_ui->lastModifiedCombo << m_ui->createdCombo;
for (auto combo : m_combos) {
combo->setModel(m_comboModel);
@ -206,17 +208,38 @@ void CsvImportWidget::writeDatabase()
entry->setUrl(m_parserModel->data(m_parserModel->index(r, 4)).toString());
entry->setNotes(m_parserModel->data(m_parserModel->index(r, 5)).toString());
TimeInfo timeInfo;
if (m_parserModel->data(m_parserModel->index(r, 6)).isValid()) {
qint64 lastModified = m_parserModel->data(m_parserModel->index(r, 6)).toString().toLongLong();
if (lastModified) {
timeInfo.setLastModificationTime(Clock::datetimeUtc(lastModified * 1000));
auto totp = Totp::parseSettings(m_parserModel->data(m_parserModel->index(r, 6)).toString());
entry->setTotp(totp);
}
bool ok;
int icon = m_parserModel->data(m_parserModel->index(r, 7)).toInt(&ok);
if (ok) {
entry->setIcon(icon);
}
TimeInfo timeInfo;
if (m_parserModel->data(m_parserModel->index(r, 8)).isValid()) {
auto datetime = m_parserModel->data(m_parserModel->index(r, 8)).toString();
if (datetime.contains(QRegularExpression("^\\d+$"))) {
timeInfo.setLastModificationTime(Clock::datetimeUtc(datetime.toLongLong() * 1000));
} else {
auto lastModified = QDateTime::fromString(datetime, Qt::ISODate);
if (lastModified.isValid()) {
timeInfo.setLastModificationTime(lastModified);
}
}
}
if (m_parserModel->data(m_parserModel->index(r, 7)).isValid()) {
qint64 created = m_parserModel->data(m_parserModel->index(r, 7)).toString().toLongLong();
if (created) {
timeInfo.setCreationTime(Clock::datetimeUtc(created * 1000));
if (m_parserModel->data(m_parserModel->index(r, 9)).isValid()) {
auto datetime = m_parserModel->data(m_parserModel->index(r, 9)).toString();
if (datetime.contains(QRegularExpression("^\\d+$"))) {
timeInfo.setCreationTime(Clock::datetimeUtc(datetime.toLongLong() * 1000));
} else {
auto created = QDateTime::fromString(datetime, Qt::ISODate);
if (created.isValid()) {
timeInfo.setCreationTime(created);
}
}
}
entry->setTimeInfo(timeInfo);

View File

@ -96,21 +96,8 @@
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QGridLayout" name="gridLayout_combos">
<item row="2" column="2">
<widget class="QLabel" name="lastModifiedLabel">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Last Modified</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
<item row="3" column="1">
<widget class="QComboBox" name="passwordCombo"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="passwordLabel">
@ -126,13 +113,13 @@
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="indent">
<number>2</number>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="passwordCombo"/>
</item>
<item row="3" column="2">
<widget class="QLabel" name="createdLabel">
<item row="2" column="0">
<widget class="QLabel" name="usernameLabel">
<property name="font">
<font>
<weight>50</weight>
@ -140,28 +127,21 @@
</font>
</property>
<property name="text">
<string>Created</string>
<string>Username</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="indent">
<number>2</number>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="notesLabel">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Notes</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
<item row="2" column="1">
<widget class="QComboBox" name="usernameCombo"/>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="titleCombo"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="titleLabel">
@ -177,6 +157,9 @@
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="indent">
<number>2</number>
</property>
</widget>
</item>
<item row="0" column="1">
@ -196,9 +179,12 @@
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="indent">
<number>2</number>
</property>
</widget>
</item>
<item row="0" column="2">
<item row="4" column="0">
<widget class="QLabel" name="urlLabel">
<property name="font">
<font>
@ -212,10 +198,16 @@
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="indent">
<number>2</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="usernameLabel">
<item row="4" column="1">
<widget class="QComboBox" name="urlCombo"/>
</item>
<item row="0" column="2">
<widget class="QLabel" name="notesLabel">
<property name="font">
<font>
<weight>50</weight>
@ -223,30 +215,106 @@
</font>
</property>
<property name="text">
<string>Username</string>
<string>Notes</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="indent">
<number>2</number>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="usernameCombo"/>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="titleCombo"/>
</item>
<item row="0" column="3">
<widget class="QComboBox" name="urlCombo"/>
</item>
<item row="1" column="3">
<widget class="QComboBox" name="notesCombo"/>
</item>
<item row="2" column="3">
<widget class="QComboBox" name="lastModifiedCombo"/>
<item row="1" column="2">
<widget class="QLabel" name="totpLabel">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>TOTP</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="indent">
<number>2</number>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QComboBox" name="totpCombo"/>
</item>
<item row="4" column="3">
<widget class="QComboBox" name="createdCombo"/>
</item>
<item row="4" column="2">
<widget class="QLabel" name="createdLabel">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Created</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="indent">
<number>2</number>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLabel" name="lastModifiedLabel">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Last Modified</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="indent">
<number>2</number>
</property>
</widget>
</item>
<item row="3" column="3">
<widget class="QComboBox" name="createdCombo"/>
<widget class="QComboBox" name="lastModifiedCombo"/>
</item>
<item row="2" column="2">
<widget class="QLabel" name="iconLabel">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Icon</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="indent">
<number>2</number>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QComboBox" name="iconCombo"/>
</item>
</layout>
</item>
@ -682,10 +750,6 @@
<tabstop>titleCombo</tabstop>
<tabstop>usernameCombo</tabstop>
<tabstop>passwordCombo</tabstop>
<tabstop>urlCombo</tabstop>
<tabstop>notesCombo</tabstop>
<tabstop>lastModifiedCombo</tabstop>
<tabstop>createdCombo</tabstop>
<tabstop>comboBoxCodec</tabstop>
<tabstop>comboBoxTextQualifier</tabstop>
<tabstop>comboBoxFieldSeparator</tabstop>

View File

@ -47,12 +47,12 @@ bool KeyFileEditWidget::addToCompositeKey(QSharedPointer<CompositeKey> key)
return false;
}
if (fileKey->type() != FileKey::Hashed) {
if (fileKey->type() != FileKey::KeePass2XMLv2 && fileKey->type() != FileKey::Hashed) {
QMessageBox::warning(getMainWindow(),
tr("Legacy key file format"),
tr("You are using a legacy key file format which may become\n"
"unsupported in the future.\n\n"
"Generate a new key file in the database security settings."),
tr("Old key file format"),
tr("You selected a key file in an old format which KeePassXC<br>"
"may stop supporting in the future.<br><br>"
"Please consider generating a new key file instead."),
QMessageBox::Ok);
}
@ -96,7 +96,7 @@ void KeyFileEditWidget::createKeyFile()
if (!m_compEditWidget) {
return;
}
QString filters = QString("%1 (*.key);;%2 (*)").arg(tr("Key files"), tr("All files"));
QString filters = QString("%1 (*.keyx; *.key);;%2 (*)").arg(tr("Key files"), tr("All files"));
QString fileName = fileDialog()->getSaveFileName(this, tr("Create Key File..."), QString(), filters);
if (!fileName.isEmpty()) {
@ -119,7 +119,7 @@ void KeyFileEditWidget::browseKeyFile()
if (!m_compEditWidget) {
return;
}
QString filters = QString("%1 (*.key);;%2 (*)").arg(tr("Key files"), tr("All files"));
QString filters = QString("%1 (*.keyx; *.key);;%2 (*)").arg(tr("Key files"), tr("All files"));
QString fileName = fileDialog()->getOpenFileName(this, tr("Select a key file"), QString(), filters);
if (QFileInfo(fileName).canonicalFilePath() == m_parent->getDatabase()->canonicalFilePath()) {

View File

@ -43,7 +43,7 @@ DatabaseSettingsWidgetEncryption::DatabaseSettingsWidgetEncryption(QWidget* pare
connect(m_ui->memorySpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryChanged(int)));
connect(m_ui->parallelismSpinBox, SIGNAL(valueChanged(int)), this, SLOT(parallelismChanged(int)));
m_ui->compatibilitySelection->addItem(tr("KDBX 4.0 (recommended)"), KeePass2::KDF_ARGON2.toByteArray());
m_ui->compatibilitySelection->addItem(tr("KDBX 4.0 (recommended)"), KeePass2::KDF_ARGON2D.toByteArray());
m_ui->compatibilitySelection->addItem(tr("KDBX 3.1"), KeePass2::KDF_AES_KDBX3.toByteArray());
m_ui->decryptionTimeSlider->setMinimum(Kdf::MIN_ENCRYPTION_TIME / 100);
m_ui->decryptionTimeSlider->setMaximum(Kdf::MAX_ENCRYPTION_TIME / 100);
@ -75,6 +75,9 @@ DatabaseSettingsWidgetEncryption::~DatabaseSettingsWidgetEncryption()
{
}
#define IS_ARGON2(uuid) (uuid == KeePass2::KDF_ARGON2D || uuid == KeePass2::KDF_ARGON2ID)
#define IS_AES_KDF(uuid) (uuid == KeePass2::KDF_AES_KDBX3 || uuid == KeePass2::KDF_AES_KDBX4)
void DatabaseSettingsWidgetEncryption::initialize()
{
Q_ASSERT(m_db);
@ -85,7 +88,7 @@ void DatabaseSettingsWidgetEncryption::initialize()
bool isDirty = false;
if (!m_db->kdf()) {
m_db->setKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2));
m_db->setKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2D));
isDirty = true;
}
if (!m_db->key()) {
@ -175,7 +178,7 @@ void DatabaseSettingsWidgetEncryption::loadKdfParameters()
}
m_ui->transformRoundsSpinBox->setValue(kdf->rounds());
if (m_db->kdf()->uuid() == KeePass2::KDF_ARGON2) {
if (IS_ARGON2(m_db->kdf()->uuid())) {
auto argon2Kdf = kdf.staticCast<Argon2Kdf>();
m_ui->memorySpinBox->setValue(static_cast<int>(argon2Kdf->memory()) / (1 << 10));
m_ui->parallelismSpinBox->setValue(argon2Kdf->parallelism());
@ -188,13 +191,10 @@ void DatabaseSettingsWidgetEncryption::updateKdfFields()
{
QUuid id = m_db->kdf()->uuid();
bool memoryVisible = (id == KeePass2::KDF_ARGON2);
m_ui->memoryUsageLabel->setVisible(memoryVisible);
m_ui->memorySpinBox->setVisible(memoryVisible);
bool parallelismVisible = (id == KeePass2::KDF_ARGON2);
m_ui->parallelismLabel->setVisible(parallelismVisible);
m_ui->parallelismSpinBox->setVisible(parallelismVisible);
m_ui->memoryUsageLabel->setVisible(IS_ARGON2(id));
m_ui->memorySpinBox->setVisible(IS_ARGON2(id));
m_ui->parallelismLabel->setVisible(IS_ARGON2(id));
m_ui->parallelismSpinBox->setVisible(IS_ARGON2(id));
}
void DatabaseSettingsWidgetEncryption::activateChangeDecryptionTime()
@ -253,7 +253,7 @@ bool DatabaseSettingsWidgetEncryption::save()
m_db->metadata()->customData()->remove(CD_DECRYPTION_TIME_PREFERENCE_KEY);
// first perform safety check for KDF rounds
if (kdf->uuid() == KeePass2::KDF_ARGON2 && m_ui->transformRoundsSpinBox->value() > 10000) {
if (IS_ARGON2(kdf->uuid()) && m_ui->transformRoundsSpinBox->value() > 10000) {
QMessageBox warning;
warning.setIcon(QMessageBox::Warning);
warning.setWindowTitle(tr("Number of rounds too high", "Key transformation rounds"));
@ -266,8 +266,7 @@ bool DatabaseSettingsWidgetEncryption::save()
if (warning.clickedButton() != ok) {
return false;
}
} else if ((kdf->uuid() == KeePass2::KDF_AES_KDBX3 || kdf->uuid() == KeePass2::KDF_AES_KDBX4)
&& m_ui->transformRoundsSpinBox->value() < 100000) {
} else if (IS_AES_KDF(kdf->uuid()) && m_ui->transformRoundsSpinBox->value() < 100000) {
QMessageBox warning;
warning.setIcon(QMessageBox::Warning);
warning.setWindowTitle(tr("Number of rounds too low", "Key transformation rounds"));
@ -286,7 +285,7 @@ bool DatabaseSettingsWidgetEncryption::save()
// Save kdf parameters
kdf->setRounds(m_ui->transformRoundsSpinBox->value());
if (kdf->uuid() == KeePass2::KDF_ARGON2) {
if (IS_ARGON2(kdf->uuid())) {
auto argon2Kdf = kdf.staticCast<Argon2Kdf>();
argon2Kdf->setMemory(static_cast<quint64>(m_ui->memorySpinBox->value()) * (1 << 10));
argon2Kdf->setParallelism(static_cast<quint32>(m_ui->parallelismSpinBox->value()));
@ -317,7 +316,7 @@ void DatabaseSettingsWidgetEncryption::benchmarkTransformRounds(int millisecs)
// Create a new kdf with the current parameters
auto kdf = KeePass2::uuidToKdf(QUuid(m_ui->kdfComboBox->currentData().toByteArray()));
kdf->setRounds(m_ui->transformRoundsSpinBox->value());
if (kdf->uuid() == KeePass2::KDF_ARGON2) {
if (IS_ARGON2(kdf->uuid())) {
auto argon2Kdf = kdf.staticCast<Argon2Kdf>();
if (!argon2Kdf->setMemory(static_cast<quint64>(m_ui->memorySpinBox->value()) * (1 << 10))) {
m_ui->memorySpinBox->setValue(static_cast<int>(argon2Kdf->memory() / (1 << 10)));
@ -402,7 +401,7 @@ void DatabaseSettingsWidgetEncryption::updateFormatCompatibility(int index, bool
auto kdf = KeePass2::uuidToKdf(kdfUuid);
m_db->setKdf(kdf);
if (kdf->uuid() == KeePass2::KDF_ARGON2) {
if (IS_ARGON2(kdf->uuid())) {
auto argon2Kdf = kdf.staticCast<Argon2Kdf>();
// Default to 64 MiB of memory and 2 threads
// these settings are safe for desktop and mobile devices

View File

@ -178,9 +178,6 @@ void EditEntryWidget::setupMain()
m_mainUi->expirePresets->setMenu(createPresetsMenu());
connect(m_mainUi->expirePresets->menu(), SIGNAL(triggered(QAction*)), this, SLOT(useExpiryPreset(QAction*)));
// HACK: Align username text with other line edits. Qt does not let you do this with an application stylesheet.
m_mainUi->usernameComboBox->lineEdit()->setStyleSheet("padding-left: 8px;");
}
void EditEntryWidget::setupAdvanced()
@ -268,9 +265,8 @@ void EditEntryWidget::setupAutoType()
#ifdef WITH_XC_BROWSER
void EditEntryWidget::setupBrowser()
{
m_browserUi->setupUi(m_browserWidget);
if (config()->get(Config::Browser_Enabled).toBool()) {
m_browserUi->setupUi(m_browserWidget);
addPage(tr("Browser Integration"), Resources::instance()->icon("internet-web-browser"), m_browserWidget);
m_additionalURLsDataModel->setEntryAttributes(m_entryAttributes);
m_browserUi->additionalURLsView->setModel(m_additionalURLsDataModel);
@ -935,35 +931,44 @@ void EditEntryWidget::setForms(Entry* entry, bool restore)
#endif
#ifdef WITH_XC_BROWSER
if (m_customData->contains(BrowserService::OPTION_SKIP_AUTO_SUBMIT)) {
// clang-format off
m_browserUi->skipAutoSubmitCheckbox->setChecked(m_customData->value(BrowserService::OPTION_SKIP_AUTO_SUBMIT) == TRUE_STR);
// clang-format on
} else {
m_browserUi->skipAutoSubmitCheckbox->setChecked(false);
if (config()->get(Config::Browser_Enabled).toBool()) {
if (!hasPage(m_browserWidget)) {
setupBrowser();
}
if (m_customData->contains(BrowserService::OPTION_SKIP_AUTO_SUBMIT)) {
// clang-format off
m_browserUi->skipAutoSubmitCheckbox->setChecked(m_customData->value(BrowserService::OPTION_SKIP_AUTO_SUBMIT) == TRUE_STR);
// clang-format on
} else {
m_browserUi->skipAutoSubmitCheckbox->setChecked(false);
}
if (m_customData->contains(BrowserService::OPTION_HIDE_ENTRY)) {
m_browserUi->hideEntryCheckbox->setChecked(m_customData->value(BrowserService::OPTION_HIDE_ENTRY)
== TRUE_STR);
} else {
m_browserUi->hideEntryCheckbox->setChecked(false);
}
if (m_customData->contains(BrowserService::OPTION_ONLY_HTTP_AUTH)) {
m_browserUi->onlyHttpAuthCheckbox->setChecked(m_customData->value(BrowserService::OPTION_ONLY_HTTP_AUTH)
== TRUE_STR);
} else {
m_browserUi->onlyHttpAuthCheckbox->setChecked(false);
}
m_browserUi->addURLButton->setEnabled(!m_history);
m_browserUi->removeURLButton->setEnabled(false);
m_browserUi->editURLButton->setEnabled(false);
m_browserUi->additionalURLsView->setEditTriggers(editTriggers);
if (m_additionalURLsDataModel->rowCount() != 0) {
m_browserUi->additionalURLsView->setCurrentIndex(m_additionalURLsDataModel->index(0, 0));
}
}
if (m_customData->contains(BrowserService::OPTION_HIDE_ENTRY)) {
m_browserUi->hideEntryCheckbox->setChecked(m_customData->value(BrowserService::OPTION_HIDE_ENTRY) == TRUE_STR);
} else {
m_browserUi->hideEntryCheckbox->setChecked(false);
}
if (m_customData->contains(BrowserService::OPTION_ONLY_HTTP_AUTH)) {
m_browserUi->onlyHttpAuthCheckbox->setChecked(m_customData->value(BrowserService::OPTION_ONLY_HTTP_AUTH)
== TRUE_STR);
} else {
m_browserUi->onlyHttpAuthCheckbox->setChecked(false);
}
m_browserUi->addURLButton->setEnabled(!m_history);
m_browserUi->removeURLButton->setEnabled(false);
m_browserUi->editURLButton->setEnabled(false);
m_browserUi->additionalURLsView->setEditTriggers(editTriggers);
if (m_additionalURLsDataModel->rowCount() != 0) {
m_browserUi->additionalURLsView->setCurrentIndex(m_additionalURLsDataModel->index(0, 0));
}
setPageHidden(m_browserWidget, !config()->get(Config::Browser_Enabled).toBool());
#endif
m_editWidgetProperties->setFields(entry->timeInfo(), entry->uuid());
@ -997,6 +1002,15 @@ bool EditEntryWidget::commitEntry()
return true;
}
// HACK: Check that entry pointer is still valid, see https://github.com/keepassxreboot/keepassxc/issues/5722
if (!m_entry) {
QMessageBox::information(this,
tr("Invalid Entry"),
tr("An external merge operation has invalidated this entry.\n"
"Unfortunately, any changes made have been lost."));
return true;
}
// Check Auto-Type validity early
if (!AutoType::verifyAutoTypeSyntax(m_autoTypeUi->sequenceEdit->text())) {
return false;

View File

@ -262,13 +262,13 @@ bool GroupModel::dropMimeData(const QMimeData* data,
targetDb->metadata()->copyCustomIcons(customIcons, sourceDb->metadata());
// Always clone the group across db's to reset UUIDs
group = dragGroup->clone();
group = dragGroup->clone(Entry::CloneDefault | Entry::CloneIncludeHistory);
if (action == Qt::MoveAction) {
// Remove the original group from the sourceDb
delete dragGroup;
}
} else if (action == Qt::CopyAction) {
group = dragGroup->clone();
group = dragGroup->clone(Entry::CloneCopy);
}
group->setParent(parentGroup, row);
@ -303,13 +303,13 @@ bool GroupModel::dropMimeData(const QMimeData* data,
targetDb->metadata()->addCustomIcon(customIcon, sourceDb->metadata()->customIcon(customIcon));
}
// Always clone the entry across db's to reset the UUID
entry = dragEntry->clone();
// Reset the UUID when moving across db boundary
entry = dragEntry->clone(Entry::CloneDefault | Entry::CloneIncludeHistory);
if (action == Qt::MoveAction) {
delete dragEntry;
}
} else if (action == Qt::CopyAction) {
entry = dragEntry->clone();
entry = dragEntry->clone(Entry::CloneCopy);
}
entry->setGroup(parentGroup);

View File

@ -104,10 +104,12 @@ void NixUtils::setLaunchAtStartup(bool enable)
<< QStringLiteral("StartupNotify=true") << '\n'
<< QStringLiteral("Terminal=false") << '\n'
<< QStringLiteral("Type=Application") << '\n'
<< QStringLiteral("Version=1.0") << "true" << '\n'
<< QStringLiteral("Version=1.0") << '\n'
<< QStringLiteral("Categories=Utility;Security;Qt;") << '\n'
<< QStringLiteral("MimeType=application/x-keepass2;") << '\n'
<< QStringLiteral("X-GNOME-Autostart-enabled=true") << endl;
<< QStringLiteral("X-GNOME-Autostart-enabled=true") << '\n'
<< QStringLiteral("X-GNOME-Autostart-Delay=2") << '\n'
<< QStringLiteral("X-KDE-autostart-after=panel") << endl;
desktopFile.close();
} else if (isLaunchAtStartupEnabled()) {
QFile::remove(getAutostartDesktopFilename());

View File

@ -58,7 +58,8 @@ namespace
// Get average password length
int averagePwdLength() const
{
return m_passwords.empty() ? 0 : pwdTotalLen / m_passwords.size();
const auto nPwds = nPwdsUnique + nPwdsReused;
return nPwds == 0 ? 0 : std::round(pwdTotalLen / double(nPwds));
}
// Get max number of password reuse (=how many entries

View File

@ -4775,7 +4775,11 @@ QRect BaseStyle::subElementRect(SubElement sr, const QStyleOption* opt, const QW
}
case SE_LineEditContents: {
QRect r = QCommonStyle::subElementRect(sr, opt, w);
int pad = Phantom::dpiScaled(Phantom::LineEdit_ContentsHPad);
int pad = Phantom::LineEdit_ContentsHPad;
if (w && qobject_cast<const QComboBox*>(w->parentWidget())) {
pad += 3;
}
pad = Phantom::dpiScaled(pad);
return r.adjusted(pad, 0, -pad, 0);
}
default:

View File

@ -13,3 +13,7 @@ DatabaseWidget #SearchBanner, DatabaseWidget #KeeShareBanner {
border: 1px solid rgb(190, 190, 190);
padding: 2px;
}
QLineEdit {
padding-left: 2px;
}

View File

@ -186,23 +186,29 @@ void ShareObserver::handleDatabaseChanged()
void ShareObserver::handleFileUpdated(const QString& path)
{
const Result result = importShare(path);
if (!result.isValid()) {
return;
if (!m_inFileUpdate) {
QTimer::singleShot(100, this, [this, path] {
const Result result = importShare(path);
m_inFileUpdate = false;
if (!result.isValid()) {
return;
}
QStringList success;
QStringList warning;
QStringList error;
if (result.isError()) {
error << tr("Import from %1 failed (%2)").arg(result.path, result.message);
} else if (result.isWarning()) {
warning << tr("Import from %1 failed (%2)").arg(result.path, result.message);
} else if (result.isInfo()) {
success << tr("Import from %1 successful (%2)").arg(result.path, result.message);
} else {
success << tr("Imported from %1").arg(result.path);
}
notifyAbout(success, warning, error);
});
m_inFileUpdate = true;
}
QStringList success;
QStringList warning;
QStringList error;
if (result.isError()) {
error << tr("Import from %1 failed (%2)").arg(result.path, result.message);
} else if (result.isWarning()) {
warning << tr("Import from %1 failed (%2)").arg(result.path, result.message);
} else if (result.isInfo()) {
success << tr("Import from %1 successful (%2)").arg(result.path, result.message);
} else {
success << tr("Imported from %1").arg(result.path);
}
notifyAbout(success, warning, error);
}
ShareObserver::Result ShareObserver::importShare(const QString& path)

View File

@ -83,6 +83,7 @@ private:
QMap<QPointer<Group>, KeeShareSettings::Reference> m_groupToReference;
QMap<QString, QPointer<Group>> m_shareToGroup;
QMap<QString, QSharedPointer<FileWatcher>> m_fileWatchers;
bool m_inFileUpdate = false;
};
#endif // KEEPASSXC_SHAREOBSERVER_H

View File

@ -64,9 +64,10 @@ FileKey::~FileKey()
* removed in a future version.
*
* @param device input device
* @param errorMsg error message in case of fatal failure
* @return true if key file was loaded successfully
*/
bool FileKey::load(QIODevice* device)
bool FileKey::load(QIODevice* device, QString* errorMsg)
{
m_type = None;
@ -75,32 +76,33 @@ bool FileKey::load(QIODevice* device)
return false;
}
if (device->size() == 0) {
if (device->size() == 0 || !device->reset()) {
return false;
}
// try different legacy key file formats
if (!device->reset()) {
return false;
}
if (loadXml(device)) {
m_type = KeePass2XML;
// load XML key file v1 or v2
QString xmlError;
if (loadXml(device, &xmlError)) {
return true;
}
if (!device->reset()) {
if (!device->reset() || !xmlError.isEmpty()) {
if (errorMsg) {
*errorMsg = xmlError;
}
return false;
}
// try legacy key file formats
if (loadBinary(device)) {
m_type = FixedBinary;
return true;
}
if (!device->reset()) {
return false;
}
if (loadHex(device)) {
m_type = FixedBinaryHex;
return true;
}
@ -109,7 +111,6 @@ bool FileKey::load(QIODevice* device)
return false;
}
if (loadHashed(device)) {
m_type = Hashed;
return true;
}
@ -145,10 +146,14 @@ bool FileKey::load(const QString& fileName, QString* errorMsg)
}
return false;
}
bool result = load(&file);
bool result = load(&file, errorMsg);
file.close();
if (errorMsg && !errorMsg->isEmpty()) {
return false;
}
if (file.error()) {
result = false;
if (errorMsg) {
@ -171,16 +176,64 @@ QByteArray FileKey::rawKey() const
}
/**
* Generate a new key file from random bytes.
* Generate a new key file with random bytes.
*
* @param device output device
* @param number of random bytes to generate
*/
void FileKey::create(QIODevice* device, int size)
void FileKey::createRandom(QIODevice* device, int size)
{
device->write(randomGen()->randomArray(size));
}
/**
* Generate a new key file in the KeePass2 XML format v2.
*
* @param device output device
* @param number of random bytes to generate
*/
void FileKey::createXMLv2(QIODevice* device, int size)
{
QXmlStreamWriter w(device);
w.setAutoFormatting(true);
w.setAutoFormattingIndent(4);
w.writeStartDocument();
w.writeStartElement("KeyFile");
w.writeStartElement("Meta");
w.writeTextElement("Version", "2.0");
w.writeEndElement();
w.writeStartElement("Key");
w.writeStartElement("Data");
QByteArray key = randomGen()->randomArray(size);
CryptoHash hash(CryptoHash::Sha256);
hash.addData(key);
QByteArray result = hash.result().left(4);
key = key.toHex().toUpper();
w.writeAttribute("Hash", result.toHex().toUpper());
w.writeCharacters("\n ");
for (int i = 0; i < key.size(); ++i) {
// Pretty-print hex value (not strictly necessary, but nicer to read and KeePass2 does it)
if (i != 0 && i % 32 == 0) {
w.writeCharacters("\n ");
} else if (i != 0 && i % 8 == 0) {
w.writeCharacters(" ");
}
w.writeCharacters(QChar(key[i]));
}
sodium_memzero(key.data(), static_cast<std::size_t>(key.capacity()));
w.writeCharacters("\n ");
w.writeEndElement();
w.writeEndElement();
w.writeEndDocument();
}
/**
* Create a new key file from random bytes.
*
@ -189,7 +242,7 @@ void FileKey::create(QIODevice* device, int size)
* @param number of random bytes to generate
* @return true on successful creation
*/
bool FileKey::create(const QString& fileName, QString* errorMsg, int size)
bool FileKey::create(const QString& fileName, QString* errorMsg)
{
QFile file(fileName);
if (!file.open(QFile::WriteOnly)) {
@ -198,7 +251,11 @@ bool FileKey::create(const QString& fileName, QString* errorMsg, int size)
}
return false;
}
create(&file, size);
if (fileName.endsWith(".keyx")) {
createXMLv2(&file);
} else {
createRandom(&file);
}
file.close();
file.setPermissions(QFile::ReadUser);
@ -218,87 +275,86 @@ bool FileKey::create(const QString& fileName, QString* errorMsg, int size)
*
* @param device input device
* @return true on success
* @deprecated
*/
bool FileKey::loadXml(QIODevice* device)
bool FileKey::loadXml(QIODevice* device, QString* errorMsg)
{
QXmlStreamReader xmlReader(device);
if (!xmlReader.error() && xmlReader.readNextStartElement()) {
if (xmlReader.name() != "KeyFile") {
return false;
}
} else {
if (xmlReader.error()) {
return false;
}
if (xmlReader.readNextStartElement() && xmlReader.name() != "KeyFile") {
return false;
}
bool correctMeta = false;
QByteArray data;
struct
{
QString version;
QByteArray hash;
QByteArray data;
} keyFileData;
while (!xmlReader.error() && xmlReader.readNextStartElement()) {
if (xmlReader.name() == "Meta") {
correctMeta = loadXmlMeta(xmlReader);
while (!xmlReader.error() && xmlReader.readNextStartElement()) {
if (xmlReader.name() == "Version") {
keyFileData.version = xmlReader.readElementText();
if (keyFileData.version.startsWith("1.0")) {
m_type = KeePass2XML;
} else if (keyFileData.version == "2.0") {
m_type = KeePass2XMLv2;
} else {
if (errorMsg) {
*errorMsg = QObject::tr("Unsupported key file version: %1").arg(keyFileData.version);
}
return false;
}
}
}
} else if (xmlReader.name() == "Key") {
data = loadXmlKey(xmlReader);
while (!xmlReader.error() && xmlReader.readNextStartElement()) {
if (xmlReader.name() == "Data") {
keyFileData.hash = QByteArray::fromHex(xmlReader.attributes().value("Hash").toLatin1());
QByteArray rawData = xmlReader.readElementText().simplified().replace(" ", "").toLatin1();
if (keyFileData.version.startsWith("1.0") && Tools::isBase64(rawData)) {
keyFileData.data = QByteArray::fromBase64(rawData);
} else if (keyFileData.version == "2.0" && Tools::isHex(rawData)) {
keyFileData.data = QByteArray::fromHex(rawData);
CryptoHash hash(CryptoHash::Sha256);
hash.addData(keyFileData.data);
QByteArray result = hash.result().left(4);
if (keyFileData.hash != result) {
if (errorMsg) {
*errorMsg = QObject::tr("Checksum mismatch! Key file may be corrupt.");
}
return false;
}
} else {
if (errorMsg) {
*errorMsg = QObject::tr("Unexpected key file data! Key file may be corrupt.");
}
return false;
}
sodium_memzero(rawData.data(), static_cast<std::size_t>(rawData.capacity()));
}
}
}
}
bool ok = false;
if (!xmlReader.error() && correctMeta && !data.isEmpty()) {
std::memcpy(m_key, data.data(), std::min(SHA256_SIZE, data.size()));
if (!xmlReader.error() && !keyFileData.data.isEmpty()) {
std::memcpy(m_key, keyFileData.data.data(), std::min(SHA256_SIZE, keyFileData.data.size()));
ok = true;
}
sodium_memzero(data.data(), static_cast<std::size_t>(data.capacity()));
sodium_memzero(keyFileData.data.data(), static_cast<std::size_t>(keyFileData.data.capacity()));
return ok;
}
/**
* Load meta data from legacy KeePass 2 XML key file.
*
* @param xmlReader input XML reader
* @return true on success
* @deprecated
*/
bool FileKey::loadXmlMeta(QXmlStreamReader& xmlReader)
{
bool correctVersion = false;
while (!xmlReader.error() && xmlReader.readNextStartElement()) {
if (xmlReader.name() == "Version") {
if (xmlReader.readElementText() == "1.00") {
correctVersion = true;
}
}
}
return correctVersion;
}
/**
* Load base64 key data from legacy KeePass 2 XML key file.
*
* @param xmlReader input XML reader
* @return true on success
* @deprecated
*/
QByteArray FileKey::loadXmlKey(QXmlStreamReader& xmlReader)
{
QByteArray data;
while (!xmlReader.error() && xmlReader.readNextStartElement()) {
if (xmlReader.name() == "Data") {
QByteArray rawData = xmlReader.readElementText().toLatin1();
if (Tools::isBase64(rawData)) {
data = QByteArray::fromBase64(rawData);
}
}
}
return data;
}
/**
* Load fixed 32-bit binary key file.
*
@ -315,11 +371,12 @@ bool FileKey::loadBinary(QIODevice* device)
QByteArray data;
if (!Tools::readAllFromDevice(device, data) || data.size() != 32) {
return false;
} else {
std::memcpy(m_key, data.data(), std::min(SHA256_SIZE, data.size()));
sodium_memzero(data.data(), static_cast<std::size_t>(data.capacity()));
return true;
}
std::memcpy(m_key, data.data(), std::min(SHA256_SIZE, data.size()));
sodium_memzero(data.data(), static_cast<std::size_t>(data.capacity()));
m_type = FixedBinary;
return true;
}
/**
@ -354,6 +411,7 @@ bool FileKey::loadHex(QIODevice* device)
std::memcpy(m_key, key.data(), std::min(SHA256_SIZE, key.size()));
sodium_memzero(key.data(), static_cast<std::size_t>(key.capacity()));
m_type = FixedBinaryHex;
return true;
}
@ -379,6 +437,7 @@ bool FileKey::loadHashed(QIODevice* device)
std::memcpy(m_key, result.data(), std::min(SHA256_SIZE, result.size()));
sodium_memzero(result.data(), static_cast<std::size_t>(result.capacity()));
m_type = Hashed;
return true;
}

View File

@ -35,25 +35,25 @@ public:
None,
Hashed,
KeePass2XML,
KeePass2XMLv2,
FixedBinary,
FixedBinaryHex
};
FileKey();
~FileKey() override;
bool load(QIODevice* device);
bool load(QIODevice* device, QString* errorMsg = nullptr);
bool load(const QString& fileName, QString* errorMsg = nullptr);
QByteArray rawKey() const override;
Type type() const;
static void create(QIODevice* device, int size = 128);
static bool create(const QString& fileName, QString* errorMsg = nullptr, int size = 128);
static void createRandom(QIODevice* device, int size = 128);
static void createXMLv2(QIODevice* device, int size = 32);
static bool create(const QString& fileName, QString* errorMsg = nullptr);
private:
static constexpr int SHA256_SIZE = 32;
bool loadXml(QIODevice* device);
bool loadXmlMeta(QXmlStreamReader& xmlReader);
QByteArray loadXmlKey(QXmlStreamReader& xmlReader);
bool loadXml(QIODevice* device, QString* errorMsg = nullptr);
bool loadBinary(QIODevice* device);
bool loadHex(QIODevice* device);
bool loadHashed(QIODevice* device);

View File

@ -928,10 +928,10 @@ void TestCli::testExport()
setInput("a");
execCmd(exportCmd, {"export", "-f", "csv", m_dbFile->fileName()});
QByteArray csvHeader = m_stdout->readLine();
QCOMPARE(csvHeader, QByteArray("\"Group\",\"Title\",\"Username\",\"Password\",\"URL\",\"Notes\"\n"));
QVERIFY(csvHeader.contains(QByteArray("\"Group\",\"Title\",\"Username\",\"Password\",\"URL\",\"Notes\"")));
QByteArray csvData = m_stdout->readAll();
QVERIFY(csvData.contains(QByteArray(
"\"NewDatabase\",\"Sample Entry\",\"User Name\",\"Password\",\"http://www.somesite.com/\",\"Notes\"\n")));
"\"NewDatabase\",\"Sample Entry\",\"User Name\",\"Password\",\"http://www.somesite.com/\",\"Notes\"")));
// test invalid format
setInput("a");

View File

@ -23,11 +23,13 @@
#include "crypto/Crypto.h"
#include "format/CsvExporter.h"
#include "totp/totp.h"
QTEST_GUILESS_MAIN(TestCsvExporter)
const QString TestCsvExporter::ExpectedHeaderLine =
QString("\"Group\",\"Title\",\"Username\",\"Password\",\"URL\",\"Notes\"\n");
QString("\"Group\",\"Title\",\"Username\",\"Password\",\"URL\",\"Notes\",\"TOTP\",\"Icon\",\"Last "
"Modified\",\"Created\"\n");
void TestCsvExporter::init()
{
@ -57,17 +59,23 @@ void TestCsvExporter::testExport()
entry->setPassword("Test Password");
entry->setUrl("http://test.url");
entry->setNotes("Test Notes");
entry->setTotp(Totp::createSettings("DFDF", Totp::DEFAULT_DIGITS, Totp::DEFAULT_STEP));
entry->setIcon(5);
QBuffer buffer;
QVERIFY(buffer.open(QIODevice::ReadWrite));
m_csvExporter->exportDatabase(&buffer, m_db);
auto exported = QString::fromUtf8(buffer.buffer());
QString expectedResult = QString()
.append(ExpectedHeaderLine)
.append("\"Passwords/Test Group Name\",\"Test Entry Title\",\"Test Username\",\"Test "
"Password\",\"http://test.url\",\"Test Notes\"\n");
"Password\",\"http://test.url\",\"Test Notes\"");
QCOMPARE(QString::fromUtf8(buffer.buffer().constData()), expectedResult);
QVERIFY(exported.startsWith(expectedResult));
exported.remove(expectedResult);
QVERIFY(exported.contains("otpauth://"));
QVERIFY(exported.contains(",\"5\","));
}
void TestCsvExporter::testEmptyDatabase()
@ -95,10 +103,9 @@ void TestCsvExporter::testNestedGroups()
QBuffer buffer;
QVERIFY(buffer.open(QIODevice::ReadWrite));
m_csvExporter->exportDatabase(&buffer, m_db);
QCOMPARE(
QString::fromUtf8(buffer.buffer().constData()),
auto exported = QString::fromUtf8(buffer.buffer());
QVERIFY(exported.startsWith(
QString()
.append(ExpectedHeaderLine)
.append("\"Passwords/Test Group Name/Test Sub Group Name\",\"Test Entry Title\",\"\",\"\",\"\",\"\"\n"));
.append("\"Passwords/Test Group Name/Test Sub Group Name\",\"Test Entry Title\",\"\",\"\",\"\",\"\"")));
}

View File

@ -42,8 +42,8 @@ int main(int argc, char* argv[])
void TestKdbx4Argon2::initTestCaseImpl()
{
m_xmlDb->changeKdf(fastKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2)));
m_kdbxSourceDb->changeKdf(fastKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2)));
m_xmlDb->changeKdf(fastKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2D)));
m_kdbxSourceDb->changeKdf(fastKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2D)));
}
QSharedPointer<Database>
@ -108,7 +108,7 @@ void TestKdbx4Argon2::readKdbx(const QString& path,
void TestKdbx4Argon2::writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString)
{
if (db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3) {
db->changeKdf(fastKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2)));
db->changeKdf(fastKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2D)));
}
KeePass2Writer writer;
hasError = writer.writeDatabase(device, db);
@ -213,26 +213,32 @@ void TestKdbx4Argon2::testFormat400Upgrade_data()
auto constexpr kdbx3 = KeePass2::FILE_VERSION_3_1 & KeePass2::FILE_VERSION_CRITICAL_MASK;
auto constexpr kdbx4 = KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK;
QTest::newRow("Argon2 + AES") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_AES256 << false << kdbx4;
QTest::newRow("AES-KDF + AES") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES256 << false << kdbx4;
QTest::newRow("AES-KDF (legacy) + AES") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES256 << false << kdbx3;
QTest::newRow("Argon2 + AES + CustomData") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_AES256 << true << kdbx4;
QTest::newRow("AES-KDF + AES + CustomData") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES256 << true << kdbx4;
QTest::newRow("AES-KDF (legacy) + AES + CustomData") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES256 << true << kdbx4;
QTest::newRow("Argon2d + AES") << KeePass2::KDF_ARGON2D << KeePass2::CIPHER_AES256 << false << kdbx4;
QTest::newRow("Argon2id + AES") << KeePass2::KDF_ARGON2ID << KeePass2::CIPHER_AES256 << false << kdbx4;
QTest::newRow("AES-KDF + AES") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES256 << false << kdbx4;
QTest::newRow("AES-KDF (legacy) + AES") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES256 << false << kdbx3;
QTest::newRow("Argon2d + AES + CustomData") << KeePass2::KDF_ARGON2D << KeePass2::CIPHER_AES256 << true << kdbx4;
QTest::newRow("Argon2id + AES + CustomData") << KeePass2::KDF_ARGON2ID << KeePass2::CIPHER_AES256 << true << kdbx4;
QTest::newRow("AES-KDF + AES + CustomData") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES256 << true << kdbx4;
QTest::newRow("AES-KDF (legacy) + AES + CustomData") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES256 << true << kdbx4;
QTest::newRow("Argon2 + ChaCha20") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_CHACHA20 << false << kdbx4;
QTest::newRow("AES-KDF + ChaCha20") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_CHACHA20 << false << kdbx4;
QTest::newRow("AES-KDF (legacy) + ChaCha20") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_CHACHA20 << false << kdbx3;
QTest::newRow("Argon2 + ChaCha20 + CustomData") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_CHACHA20 << true << kdbx4;
QTest::newRow("AES-KDF + ChaCha20 + CustomData") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_CHACHA20 << true << kdbx4;
QTest::newRow("AES-KDF (legacy) + ChaCha20 + CustomData") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_CHACHA20 << true << kdbx4;
QTest::newRow("Argon2d + ChaCha20") << KeePass2::KDF_ARGON2D << KeePass2::CIPHER_CHACHA20 << false << kdbx4;
QTest::newRow("Argon2id + ChaCha20") << KeePass2::KDF_ARGON2ID << KeePass2::CIPHER_CHACHA20 << false << kdbx4;
QTest::newRow("AES-KDF + ChaCha20") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_CHACHA20 << false << kdbx4;
QTest::newRow("AES-KDF (legacy) + ChaCha20") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_CHACHA20 << false << kdbx3;
QTest::newRow("Argon2d + ChaCha20 + CustomData") << KeePass2::KDF_ARGON2D << KeePass2::CIPHER_CHACHA20 << true << kdbx4;
QTest::newRow("Argon2id + ChaCha20 + CustomData") << KeePass2::KDF_ARGON2ID << KeePass2::CIPHER_CHACHA20 << true << kdbx4;
QTest::newRow("AES-KDF + ChaCha20 + CustomData") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_CHACHA20 << true << kdbx4;
QTest::newRow("AES-KDF (legacy) + ChaCha20 + CustomData") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_CHACHA20 << true << kdbx4;
QTest::newRow("Argon2 + Twofish") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_TWOFISH << false << kdbx4;
QTest::newRow("AES-KDF + Twofish") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_TWOFISH << false << kdbx4;
QTest::newRow("AES-KDF (legacy) + Twofish") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_TWOFISH << false << kdbx3;
QTest::newRow("Argon2 + Twofish + CustomData") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_TWOFISH << true << kdbx4;
QTest::newRow("AES-KDF + Twofish + CustomData") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_TWOFISH << true << kdbx4;
QTest::newRow("AES-KDF (legacy) + Twofish + CustomData") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_TWOFISH << true << kdbx4;
QTest::newRow("Argon2d + Twofish") << KeePass2::KDF_ARGON2D << KeePass2::CIPHER_TWOFISH << false << kdbx4;
QTest::newRow("Argon2id + Twofish") << KeePass2::KDF_ARGON2ID << KeePass2::CIPHER_TWOFISH << false << kdbx4;
QTest::newRow("AES-KDF + Twofish") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_TWOFISH << false << kdbx4;
QTest::newRow("AES-KDF (legacy) + Twofish") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_TWOFISH << false << kdbx3;
QTest::newRow("Argon2d + Twofish + CustomData") << KeePass2::KDF_ARGON2D << KeePass2::CIPHER_TWOFISH << true << kdbx4;
QTest::newRow("Argon2id + Twofish + CustomData") << KeePass2::KDF_ARGON2ID << KeePass2::CIPHER_TWOFISH << true << kdbx4;
QTest::newRow("AES-KDF + Twofish + CustomData") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_TWOFISH << true << kdbx4;
QTest::newRow("AES-KDF (legacy) + Twofish + CustomData") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_TWOFISH << true << kdbx4;
}
// clang-format on
@ -270,7 +276,7 @@ void TestKdbx4Argon2::testUpgradeMasterKeyIntegrity()
} else if (upgradeAction == "kdf-aes-kdbx3") {
db->changeKdf(fastKdf(KeePass2::uuidToKdf(KeePass2::KDF_AES_KDBX3)));
} else if (upgradeAction == "kdf-argon2") {
db->changeKdf(fastKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2)));
db->changeKdf(fastKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2D)));
} else if (upgradeAction == "kdf-aes-kdbx4") {
db->changeKdf(fastKdf(KeePass2::uuidToKdf(KeePass2::KDF_AES_KDBX4)));
} else if (upgradeAction == "public-customdata") {

View File

@ -809,7 +809,7 @@ QSharedPointer<Kdf> TestKeePass2Format::fastKdf(QSharedPointer<Kdf> kdf) const
{
kdf->setRounds(1);
if (kdf->uuid() == KeePass2::KDF_ARGON2) {
if (kdf->uuid() == KeePass2::KDF_ARGON2D) {
kdf->processParameters({{KeePass2::KDFPARAM_ARGON2_MEMORY, 1024}, {KeePass2::KDFPARAM_ARGON2_PARALLELISM, 1}});
}

View File

@ -69,22 +69,36 @@ void TestKeys::testComposite()
void TestKeys::testFileKey()
{
QFETCH(FileKey::Type, type);
QFETCH(QString, typeString);
QFETCH(QString, keyExt);
QFETCH(bool, fileKeyOk);
QString name = QString("FileKey").append(typeString);
QString name = QString("FileKey").append(QTest::currentDataTag());
KeePass2Reader reader;
QString dbFilename = QString("%1/%2.kdbx").arg(QString(KEEPASSX_TEST_DATA_DIR), name);
QString keyFilename = QString("%1/%2.key").arg(QString(KEEPASSX_TEST_DATA_DIR), name);
QString keyFilename = QString("%1/%2.%3").arg(QString(KEEPASSX_TEST_DATA_DIR), name, keyExt);
auto compositeKey = QSharedPointer<CompositeKey>::create();
auto fileKey = QSharedPointer<FileKey>::create();
QVERIFY(fileKey->load(keyFilename));
QCOMPARE(fileKey->rawKey().size(), 32);
QString error;
QVERIFY(fileKey->load(keyFilename, &error) == fileKeyOk);
QVERIFY(error.isEmpty() == fileKeyOk);
QCOMPARE(fileKey->type(), type);
// Test for same behaviour on code path without error parameter
auto fileKeyNoErrorParam = QSharedPointer<FileKey>::create();
QVERIFY(fileKeyNoErrorParam->load(keyFilename) == fileKeyOk);
QCOMPARE(fileKeyNoErrorParam->type(), type);
QCOMPARE(fileKey->rawKey(), fileKeyNoErrorParam->rawKey());
if (!fileKeyOk) {
return;
}
QCOMPARE(fileKey->rawKey().size(), 32);
compositeKey->addKey(fileKey);
auto db = QSharedPointer<Database>::create();
@ -97,12 +111,16 @@ void TestKeys::testFileKey()
void TestKeys::testFileKey_data()
{
QTest::addColumn<FileKey::Type>("type");
QTest::addColumn<QString>("typeString");
QTest::newRow("Xml") << FileKey::KeePass2XML << QString("Xml");
QTest::newRow("XmlBrokenBase64") << FileKey::Hashed << QString("XmlBrokenBase64");
QTest::newRow("Binary") << FileKey::FixedBinary << QString("Binary");
QTest::newRow("Hex") << FileKey::FixedBinaryHex << QString("Hex");
QTest::newRow("Hashed") << FileKey::Hashed << QString("Hashed");
QTest::addColumn<QString>("keyExt");
QTest::addColumn<bool>("fileKeyOk");
QTest::newRow("Xml") << FileKey::KeePass2XML << QString("key") << true;
QTest::newRow("XmlBrokenBase64") << FileKey::KeePass2XML << QString("key") << false;
QTest::newRow("XmlV2") << FileKey::KeePass2XMLv2 << QString("keyx") << true;
QTest::newRow("XmlV2HashFail") << FileKey::KeePass2XMLv2 << QString("keyx") << false;
QTest::newRow("XmlV2BrokenHex") << FileKey::KeePass2XMLv2 << QString("keyx") << false;
QTest::newRow("Binary") << FileKey::FixedBinary << QString("key") << true;
QTest::newRow("Hex") << FileKey::FixedBinaryHex << QString("key") << true;
QTest::newRow("Hashed") << FileKey::Hashed << QString("key") << true;
}
// clang-format on
@ -111,12 +129,12 @@ void TestKeys::testCreateFileKey()
QBuffer keyBuffer1;
keyBuffer1.open(QBuffer::ReadWrite);
FileKey::create(&keyBuffer1, 128);
FileKey::createRandom(&keyBuffer1, 128);
QCOMPARE(keyBuffer1.size(), 128);
QBuffer keyBuffer2;
keyBuffer2.open(QBuffer::ReadWrite);
FileKey::create(&keyBuffer2, 64);
FileKey::createRandom(&keyBuffer2, 64);
QCOMPARE(keyBuffer2.size(), 64);
}
@ -127,7 +145,7 @@ void TestKeys::testCreateAndOpenFileKey()
QBuffer keyBuffer;
keyBuffer.open(QBuffer::ReadWrite);
FileKey::create(&keyBuffer);
FileKey::createRandom(&keyBuffer);
keyBuffer.reset();
auto fileKey = QSharedPointer<FileKey>::create();
@ -166,7 +184,7 @@ void TestKeys::testFileKeyHash()
QBuffer keyBuffer;
keyBuffer.open(QBuffer::ReadWrite);
FileKey::create(&keyBuffer);
FileKey::createRandom(&keyBuffer);
CryptoHash cryptoHash(CryptoHash::Sha256);
cryptoHash.addData(keyBuffer.data());

Binary file not shown.

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<KeyFile xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Meta>
<Version>2.0</Version>
</Meta>
<Key>
<Data Hash="FE2949B8">
A7007945 D07D54BA 28DF6434 1B4500FC
9750DFB1 D36ADA2D 9C32DC19 4C7AB01B
</Data>
</Key>
</KeyFile>

Binary file not shown.

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<KeyFile xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Meta>
<Version>2.0</Version>
</Meta>
<Key>
<Data Hash="FE2949B8">
X7007945 D07D54BA 28DF6434 1B4500FC
9750DFB1 D36ADA2D 9C32DC19 4C7AB01B
</Data>
</Key>
</KeyFile>

Binary file not shown.

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<KeyFile xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Meta>
<Version>2.0</Version>
</Meta>
<Key>
<Data Hash="FE2949B9">
A7007945 D07D54BA 28DF6434 1B4500FC
9750DFB1 D36ADA2D 9C32DC19 4C7AB01B
</Data>
</Key>
</KeyFile>

View File

@ -29,14 +29,18 @@
#include <QDialogButtonBox>
#include <QLabel>
#include <QLineEdit>
#include <QListWidgetItem>
#include <QMimeData>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QRadioButton>
#include <QSignalSpy>
#include <QSpinBox>
#include <QTest>
#include <QTimer>
#include <QToolBar>
#include <QToolButton>
#include <QTreeWidgetItem>
#include "config-keepassx-tests.h"
#include "core/Bootstrap.h"
@ -142,6 +146,9 @@ void TestGui::init()
fileDialog()->setNextFileName(m_dbFilePath);
triggerAction("actionDatabaseOpen");
QApplication::processEvents();
m_dbWidget = m_tabWidget->currentDatabaseWidget();
auto* databaseOpenWidget = m_tabWidget->currentDatabaseWidget()->findChild<QWidget*>("databaseOpenWidget");
QVERIFY(databaseOpenWidget);
auto* editPassword = databaseOpenWidget->findChild<QLineEdit*>("editPassword");
@ -151,8 +158,10 @@ void TestGui::init()
QTest::keyClicks(editPassword, "a");
QTest::keyClick(editPassword, Qt::Key_Enter);
m_dbWidget = m_tabWidget->currentDatabaseWidget();
QTRY_VERIFY(!m_dbWidget->isLocked());
m_db = m_dbWidget->database();
QApplication::processEvents();
}
// Every test ends with closing the temp database without saving
@ -302,7 +311,7 @@ void TestGui::testCreateDatabase()
// check key and encryption
QCOMPARE(m_db->key()->keys().size(), 2);
QCOMPARE(m_db->kdf()->rounds(), 2);
QCOMPARE(m_db->kdf()->uuid(), KeePass2::KDF_ARGON2);
QCOMPARE(m_db->kdf()->uuid(), KeePass2::KDF_ARGON2D);
QCOMPARE(m_db->cipher(), KeePass2::CIPHER_AES256);
auto compositeKey = QSharedPointer<CompositeKey>::create();
compositeKey->addKey(QSharedPointer<PasswordKey>::create("test"));
@ -1146,24 +1155,45 @@ void TestGui::testEntryPlaceholders()
void TestGui::testDragAndDropEntry()
{
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
auto* groupView = m_dbWidget->findChild<GroupView*>("groupView");
QAbstractItemModel* groupModel = groupView->model();
auto entryView = m_dbWidget->findChild<EntryView*>("entryView");
auto groupView = m_dbWidget->findChild<GroupView*>("groupView");
auto groupModel = qobject_cast<GroupModel*>(groupView->model());
QModelIndex sourceIndex = entryView->model()->index(0, 1);
QModelIndex targetIndex = groupModel->index(0, 0, groupModel->index(0, 0));
QVERIFY(sourceIndex.isValid());
QVERIFY(targetIndex.isValid());
auto targetGroup = groupModel->groupFromIndex(targetIndex);
QMimeData mimeData;
QByteArray encoded;
QDataStream stream(&encoded, QIODevice::WriteOnly);
Entry* entry = entryView->entryFromIndex(sourceIndex);
auto entry = entryView->entryFromIndex(sourceIndex);
stream << entry->group()->database()->uuid() << entry->uuid();
mimeData.setData("application/x-keepassx-entry", encoded);
// Test Copy, UUID should change, history remain
QVERIFY(groupModel->dropMimeData(&mimeData, Qt::CopyAction, -1, 0, targetIndex));
// Find the copied entry
auto newEntry = targetGroup->findEntryByPath(entry->title());
QVERIFY(newEntry);
QVERIFY(entry->uuid() != newEntry->uuid());
QCOMPARE(entry->historyItems().count(), newEntry->historyItems().count());
encoded.clear();
entry = entryView->entryFromIndex(sourceIndex);
auto history = entry->historyItems().count();
auto uuid = entry->uuid();
stream << entry->group()->database()->uuid() << entry->uuid();
mimeData.setData("application/x-keepassx-entry", encoded);
// Test Move, entry pointer should remain the same
QCOMPARE(entry->group()->name(), QString("NewDatabase"));
QVERIFY(groupModel->dropMimeData(&mimeData, Qt::MoveAction, -1, 0, targetIndex));
QCOMPARE(entry->group()->name(), QString("General"));
QCOMPARE(entry->uuid(), uuid);
QCOMPARE(entry->historyItems().count(), history);
}
void TestGui::testDragAndDropGroup()
@ -1484,6 +1514,163 @@ void TestGui::testTrayRestoreHide()
trayIcon->activated(QSystemTrayIcon::DoubleClick);
QTRY_VERIFY(!m_mainWindow->isVisible());
// Ensure window is visible at the end
trayIcon->activated(QSystemTrayIcon::DoubleClick);
QTRY_VERIFY(m_mainWindow->isVisible());
}
void TestGui::testAutoType()
{
// Clear entries from root group to guarantee order
for (Entry* entry : m_db->rootGroup()->entries()) {
m_db->rootGroup()->removeEntry(entry);
}
Tools::wait(150);
// 1. Create an entry with Auto-Type disabled
// 1.a) Click the new entry button and set the title
auto* entryNewAction = m_mainWindow->findChild<QAction*>("actionEntryNew");
QVERIFY(entryNewAction->isEnabled());
auto* toolBar = m_mainWindow->findChild<QToolBar*>("toolBar");
QVERIFY(toolBar);
QWidget* entryNewWidget = toolBar->widgetForAction(entryNewAction);
QVERIFY(entryNewWidget->isVisible());
QVERIFY(entryNewWidget->isEnabled());
QTest::mouseClick(entryNewWidget, Qt::LeftButton);
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode);
auto* editEntryWidget = m_dbWidget->findChild<EditEntryWidget*>("editEntryWidget");
QVERIFY(editEntryWidget);
auto* titleEdit = editEntryWidget->findChild<QLineEdit*>("titleEdit");
QVERIFY(titleEdit);
QTest::keyClicks(titleEdit, "1. Entry With Disabled Auto-Type");
auto* usernameComboBox = editEntryWidget->findChild<QComboBox*>("usernameComboBox");
QVERIFY(usernameComboBox);
QTest::mouseClick(usernameComboBox, Qt::LeftButton);
QTest::keyClicks(usernameComboBox, "AutocompletionUsername");
// 1.b) Uncheck Auto-Type checkbox
editEntryWidget->setCurrentPage(3);
auto* enableAutoTypeButton = editEntryWidget->findChild<QCheckBox*>("enableButton");
QVERIFY(enableAutoTypeButton);
QVERIFY(enableAutoTypeButton->isVisible());
QVERIFY(enableAutoTypeButton->isEnabled());
enableAutoTypeButton->click();
QVERIFY(!enableAutoTypeButton->isChecked());
// 1.c) Save changes
editEntryWidget->setCurrentPage(0);
auto* editEntryWidgetButtonBox = editEntryWidget->findChild<QDialogButtonBox*>("buttonBox");
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
// 2. Create an entry with default/inherited Auto-Type sequence
// 2.a) Click the new entry button and set the title
QTest::mouseClick(entryNewWidget, Qt::LeftButton);
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode);
QTest::keyClicks(titleEdit, "2. Entry With Default Auto-Type Sequence");
QTest::mouseClick(usernameComboBox, Qt::LeftButton);
QTest::keyClicks(usernameComboBox, "AutocompletionUsername");
// 2.b) Confirm AutoType is enabled and default
editEntryWidget->setCurrentPage(3);
QVERIFY(enableAutoTypeButton->isChecked());
auto* inheritSequenceButton = editEntryWidget->findChild<QRadioButton*>("inheritSequenceButton");
QVERIFY(inheritSequenceButton->isChecked());
// 2.c) Save changes
editEntryWidget->setCurrentPage(0);
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
// 3. Create an entry with custom Auto-Type sequence
// 3.a) Click the new entry button and set the title
QTest::mouseClick(entryNewWidget, Qt::LeftButton);
QCOMPARE(m_dbWidget->currentMode(), DatabaseWidget::Mode::EditMode);
QTest::keyClicks(titleEdit, "3. Entry With Custom Auto-Type Sequence");
QTest::mouseClick(usernameComboBox, Qt::LeftButton);
QTest::keyClicks(usernameComboBox, "AutocompletionUsername");
// 3.b) Confirm AutoType is enabled and set custom sequence
editEntryWidget->setCurrentPage(3);
QVERIFY(enableAutoTypeButton->isChecked());
auto* customSequenceButton = editEntryWidget->findChild<QRadioButton*>("customSequenceButton");
QTest::mouseClick(customSequenceButton, Qt::LeftButton);
QVERIFY(customSequenceButton->isChecked());
QVERIFY(!inheritSequenceButton->isChecked());
auto* sequenceEdit = editEntryWidget->findChild<QLineEdit*>("sequenceEdit");
QVERIFY(sequenceEdit);
sequenceEdit->setFocus();
QTRY_VERIFY(sequenceEdit->hasFocus());
QTest::keyClicks(sequenceEdit, "{USERNAME}{TAB}{TAB}{PASSWORD}{ENTER}");
// 3.c) Save changes
editEntryWidget->setCurrentPage(0);
QTest::mouseClick(editEntryWidgetButtonBox->button(QDialogButtonBox::Ok), Qt::LeftButton);
QApplication::processEvents();
// Check total number of entries matches expected
auto* entryView = m_dbWidget->findChild<EntryView*>("entryView");
QVERIFY(entryView);
QTRY_COMPARE(entryView->model()->rowCount(), 3);
// Sort entries by title
entryView->sortByColumn(1, Qt::AscendingOrder);
// Select first entry
entryView->selectionModel()->clearSelection();
QModelIndex entryIndex = entryView->model()->index(0, 0);
entryView->selectionModel()->select(entryIndex, QItemSelectionModel::Rows | QItemSelectionModel::Select);
auto* entryPreviewWidget = m_dbWidget->findChild<EntryPreviewWidget*>("previewWidget");
QVERIFY(entryPreviewWidget->isVisible());
// Check that the Autotype tab in entry preview pane is disabled for entry with disabled Auto-Type
auto* entryAutotypeTab = entryPreviewWidget->findChild<QWidget*>("entryAutotypeTab");
QVERIFY(!entryAutotypeTab->isEnabled());
// Check that Auto-Type is disabled in the actual entry model as well
Entry* entry = entryView->entryFromIndex(entryIndex);
QVERIFY(!entry->autoTypeEnabled());
// Select second entry
entryView->selectionModel()->clearSelection();
entryIndex = entryView->model()->index(1, 0);
entryView->selectionModel()->select(entryIndex, QItemSelectionModel::Rows | QItemSelectionModel::Select);
QVERIFY(entryPreviewWidget->isVisible());
// Check that the Autotype tab in entry preview pane is enabled for entry with default Auto-Type sequence;
QVERIFY(entryAutotypeTab->isEnabled());
// Check that Auto-Type is enabled in the actual entry model as well
entry = entryView->entryFromIndex(entryIndex);
QVERIFY(entry->autoTypeEnabled());
// Select third entry
entryView->selectionModel()->clearSelection();
entryIndex = entryView->model()->index(2, 0);
entryView->selectionModel()->select(entryIndex, QItemSelectionModel::Rows | QItemSelectionModel::Select);
QVERIFY(entryPreviewWidget->isVisible());
// Check that the Autotype tab in entry preview pane is enabled for entry with custom Auto-Type sequence
QVERIFY(entryAutotypeTab->isEnabled());
// Check that Auto-Type is enabled in the actual entry model as well
entry = entryView->entryFromIndex(entryIndex);
QVERIFY(entry->autoTypeEnabled());
// De-select third entry
entryView->selectionModel()->clearSelection();
}
int TestGui::addCannedEntries()

View File

@ -68,6 +68,7 @@ private slots:
void testDatabaseLocking();
void testDragAndDropKdbxFiles();
void testSortGroups();
void testAutoType();
void testTrayRestoreHide();
private: