mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-02-05 01:15:33 -05:00
Merge branch 'master' of https://github.com/keepassx/keepassx
This commit is contained in:
commit
a115bbdc6f
@ -14,6 +14,6 @@ before_script: mkdir build && pushd build
|
|||||||
script:
|
script:
|
||||||
- cmake -DCMAKE_BUILD_TYPE=Debug -DWITH_GUI_TESTS=ON ..
|
- cmake -DCMAKE_BUILD_TYPE=Debug -DWITH_GUI_TESTS=ON ..
|
||||||
- make
|
- make
|
||||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then make test ARGS+="-E testgui"; fi
|
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then make test ARGS+="-E testgui --output-on-failure"; fi
|
||||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then xvfb-run -a --server-args="-screen 0 800x600x24" make test ARGS+="-R testgui"; fi
|
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then xvfb-run -a --server-args="-screen 0 800x600x24" make test ARGS+="-R testgui --output-on-failure"; fi
|
||||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then make test; fi
|
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then make test; fi
|
||||||
|
@ -149,9 +149,9 @@ elseif(APPLE)
|
|||||||
else()
|
else()
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
set(BIN_INSTALL_DIR "${CMAKE_INSTALL_FULL_BINDIR}")
|
set(BIN_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}")
|
||||||
set(PLUGIN_INSTALL_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/keepassx")
|
set(PLUGIN_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/keepassx")
|
||||||
set(DATA_INSTALL_DIR "${CMAKE_INSTALL_FULL_DATADIR}/keepassx")
|
set(DATA_INSTALL_DIR "${CMAKE_INSTALL_DATADIR}/keepassx")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WITH_TESTS)
|
if(WITH_TESTS)
|
||||||
|
1205
share/translations/keepassx_cs.ts
Normal file
1205
share/translations/keepassx_cs.ts
Normal file
File diff suppressed because it is too large
Load Diff
1202
share/translations/keepassx_da.ts
Normal file
1202
share/translations/keepassx_da.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -17,8 +17,8 @@
|
|||||||
<translation>Auto-Type - KeePassX</translation>
|
<translation>Auto-Type - KeePassX</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Couldn't find an entry that matches the window title.</source>
|
<source>Couldn't find an entry that matches the window title:</source>
|
||||||
<translation>Konnte dem Fenstertitel keinen passenden Eintrag zuordnen.</translation>
|
<translation>Konnte keinen Eintrag finden, welcher mit dem Fenstertitel übereinstimmt:</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
@ -138,7 +138,7 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Can't open key file</source>
|
<source>Can't open key file</source>
|
||||||
<translation>Schlüsseldatein kann nicht geöffnet werden</translation>
|
<translation>Schlüsseldatei kann nicht geöffnet werden</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>All files</source>
|
<source>All files</source>
|
||||||
@ -185,7 +185,7 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Max. history items:</source>
|
<source>Max. history items:</source>
|
||||||
<translation>Max Einträge im Verlauf:</translation>
|
<translation>Max. Einträge im Verlauf:</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Max. history size:</source>
|
<source>Max. history size:</source>
|
||||||
@ -739,6 +739,17 @@ Save changes?</source>
|
|||||||
<translation>Falscher Schlüssel oder die Datei ist beschädigt.</translation>
|
<translation>Falscher Schlüssel oder die Datei ist beschädigt.</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>Main</name>
|
||||||
|
<message>
|
||||||
|
<source>Fatal error while testing the cryptographic functions.</source>
|
||||||
|
<translation>Fataler Fehler beim Testen der kryptografischen Funktionen.</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>KeePassX - Error</source>
|
||||||
|
<translation>KeePassX - Fehler</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>MainWindow</name>
|
<name>MainWindow</name>
|
||||||
<message>
|
<message>
|
||||||
@ -897,6 +908,10 @@ Save changes?</source>
|
|||||||
<source>read-only</source>
|
<source>read-only</source>
|
||||||
<translation>Nur Lesezugriff</translation>
|
<translation>Nur Lesezugriff</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Toggle window</source>
|
||||||
|
<translation>Fenster zeigen/verstecken</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>PasswordGeneratorWidget</name>
|
<name>PasswordGeneratorWidget</name>
|
||||||
@ -1105,6 +1120,18 @@ Save changes?</source>
|
|||||||
<source>Use entry title to match windows for global auto-type</source>
|
<source>Use entry title to match windows for global auto-type</source>
|
||||||
<translation>Verwende den Eintragstitel für entsprechende Fenster für den globale Auto-Typ</translation>
|
<translation>Verwende den Eintragstitel für entsprechende Fenster für den globale Auto-Typ</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Language</source>
|
||||||
|
<translation>Sprache</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Show a system tray icon</source>
|
||||||
|
<translation>Taskleistensymbol anzeigen</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Hide window to system tray when minimized</source>
|
||||||
|
<translation>Fenster verstecken wenn minimiert</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>SettingsWidgetSecurity</name>
|
<name>SettingsWidgetSecurity</name>
|
||||||
|
@ -111,6 +111,15 @@
|
|||||||
<source>Different passwords supplied.</source>
|
<source>Different passwords supplied.</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Failed to set key file</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Failed to set %1 as the Key file:
|
||||||
|
%2</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>DatabaseOpenWidget</name>
|
<name>DatabaseOpenWidget</name>
|
||||||
@ -270,6 +279,31 @@ Save changes?</source>
|
|||||||
<source>locked</source>
|
<source>locked</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>The database you are trying to open is locked by another instance of KeePassX.
|
||||||
|
Do you want to open it anyway? Alternatively the database is opened read-only.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Lock database</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Can't lock the database as you are currently editing it.
|
||||||
|
Please press cancel to finish your changes or discard them.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>This database has never been saved.
|
||||||
|
You can save the database or stop locking it.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>This database has been modified.
|
||||||
|
Do you want to save the database before locking it?
|
||||||
|
Otherwise your changes are lost.</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>DatabaseWidget</name>
|
<name>DatabaseWidget</name>
|
||||||
@ -316,6 +350,14 @@ Save changes?</source>
|
|||||||
<source>Current group</source>
|
<source>Current group</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Error</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Unable to calculate master key</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>EditEntryWidget</name>
|
<name>EditEntryWidget</name>
|
||||||
@ -433,6 +475,10 @@ Save changes?</source>
|
|||||||
<source>Save</source>
|
<source>Save</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Open</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>EditEntryWidgetAutoType</name>
|
<name>EditEntryWidgetAutoType</name>
|
||||||
@ -584,6 +630,14 @@ Save changes?</source>
|
|||||||
<source>Auto-type</source>
|
<source>Auto-type</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Use default auto-type sequence of parent group</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Set default auto-type sequence</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>EditWidgetIcons</name>
|
<name>EditWidgetIcons</name>
|
||||||
@ -735,6 +789,10 @@ Save changes?</source>
|
|||||||
<source>Root</source>
|
<source>Root</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Unable to calculate master key</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>KeePass2Reader</name>
|
<name>KeePass2Reader</name>
|
||||||
@ -750,6 +808,10 @@ Save changes?</source>
|
|||||||
<source>Wrong key or database file is corrupt.</source>
|
<source>Wrong key or database file is corrupt.</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Unable to calculate master key</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>Main</name>
|
<name>Main</name>
|
||||||
@ -788,10 +850,6 @@ Save changes?</source>
|
|||||||
<source>Groups</source>
|
<source>Groups</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>Extras</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>View</source>
|
<source>View</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
@ -924,6 +982,10 @@ Save changes?</source>
|
|||||||
<source>Toggle window</source>
|
<source>Toggle window</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Tools</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>PasswordGeneratorWidget</name>
|
<name>PasswordGeneratorWidget</name>
|
||||||
@ -1104,10 +1166,6 @@ Save changes?</source>
|
|||||||
<source>Open previous databases on startup</source>
|
<source>Open previous databases on startup</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>Mark as modified on expanded state changes</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
<message>
|
||||||
<source>Automatically save on exit</source>
|
<source>Automatically save on exit</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
@ -1144,6 +1202,10 @@ Save changes?</source>
|
|||||||
<source>Hide window to system tray when minimized</source>
|
<source>Hide window to system tray when minimized</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Remember last key files</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>SettingsWidgetSecurity</name>
|
<name>SettingsWidgetSecurity</name>
|
||||||
@ -1174,14 +1236,6 @@ Save changes?</source>
|
|||||||
<source>Unlock database</source>
|
<source>Unlock database</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
|
||||||
<source>Error</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<source>Wrong key.</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>WelcomeWidget</name>
|
<name>WelcomeWidget</name>
|
||||||
|
1205
share/translations/keepassx_es.ts
Normal file
1205
share/translations/keepassx_es.ts
Normal file
File diff suppressed because it is too large
Load Diff
1205
share/translations/keepassx_fr.ts
Normal file
1205
share/translations/keepassx_fr.ts
Normal file
File diff suppressed because it is too large
Load Diff
1205
share/translations/keepassx_id.ts
Normal file
1205
share/translations/keepassx_id.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -17,8 +17,8 @@
|
|||||||
<translation>Auto-typen - KeePassX</translation>
|
<translation>Auto-typen - KeePassX</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Couldn't find an entry that matches the window title.</source>
|
<source>Couldn't find an entry that matches the window title:</source>
|
||||||
<translation>Kon geen element vinden dat overeenkomt met de venstertitel.</translation>
|
<translation>Kon geen element vinden dat overeenkomt met de venstertitel:</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
@ -740,6 +740,17 @@ Opslaan?</translation>
|
|||||||
<translation>Verkeerde sleutel of corrupte database.</translation>
|
<translation>Verkeerde sleutel of corrupte database.</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>Main</name>
|
||||||
|
<message>
|
||||||
|
<source>Fatal error while testing the cryptographic functions.</source>
|
||||||
|
<translation>Fatale fout bij het testen van de cryptografische functies.</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>KeePassX - Error</source>
|
||||||
|
<translation>KeePassX - Fout</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>MainWindow</name>
|
<name>MainWindow</name>
|
||||||
<message>
|
<message>
|
||||||
@ -898,6 +909,10 @@ Opslaan?</translation>
|
|||||||
<source>read-only</source>
|
<source>read-only</source>
|
||||||
<translation>alleen-lezen</translation>
|
<translation>alleen-lezen</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Toggle window</source>
|
||||||
|
<translation>Wissel venster</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>PasswordGeneratorWidget</name>
|
<name>PasswordGeneratorWidget</name>
|
||||||
@ -1106,6 +1121,18 @@ Opslaan?</translation>
|
|||||||
<source>Use entry title to match windows for global auto-type</source>
|
<source>Use entry title to match windows for global auto-type</source>
|
||||||
<translation>Gebruik naam van element als vensternaam voor auto-typen</translation>
|
<translation>Gebruik naam van element als vensternaam voor auto-typen</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Language</source>
|
||||||
|
<translation>Taal</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Show a system tray icon</source>
|
||||||
|
<translation>Toon een icoon in de systray</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Hide window to system tray when minimized</source>
|
||||||
|
<translation>Bij minimaliseren enkel icoon in systray tonen</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>SettingsWidgetSecurity</name>
|
<name>SettingsWidgetSecurity</name>
|
||||||
|
1205
share/translations/keepassx_ru.ts
Normal file
1205
share/translations/keepassx_ru.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -17,8 +17,8 @@
|
|||||||
<translation>Auto-skriv - KeePassX</translation>
|
<translation>Auto-skriv - KeePassX</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Couldn't find an entry that matches the window title.</source>
|
<source>Couldn't find an entry that matches the window title:</source>
|
||||||
<translation>Kunde inte hitta en post som matchar fönstertiteln.</translation>
|
<translation>Kunde inte hitta en post som matchar fönstertiteln:</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
@ -740,6 +740,17 @@ Spara ändringarna?</translation>
|
|||||||
<translation>Fel lösenord eller korrupt databas-fil</translation>
|
<translation>Fel lösenord eller korrupt databas-fil</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>Main</name>
|
||||||
|
<message>
|
||||||
|
<source>Fatal error while testing the cryptographic functions.</source>
|
||||||
|
<translation>Allvarligt fel vid testning av kryptografiska funktioner.</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>KeePassX - Error</source>
|
||||||
|
<translation>KeePassX - Fel</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>MainWindow</name>
|
<name>MainWindow</name>
|
||||||
<message>
|
<message>
|
||||||
@ -898,6 +909,10 @@ Spara ändringarna?</translation>
|
|||||||
<source>read-only</source>
|
<source>read-only</source>
|
||||||
<translation>läs bara</translation>
|
<translation>läs bara</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Toggle window</source>
|
||||||
|
<translation>Visa/dölj fönster</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>PasswordGeneratorWidget</name>
|
<name>PasswordGeneratorWidget</name>
|
||||||
@ -1106,6 +1121,18 @@ Spara ändringarna?</translation>
|
|||||||
<source>Use entry title to match windows for global auto-type</source>
|
<source>Use entry title to match windows for global auto-type</source>
|
||||||
<translation>Använda postens titel till matchning med fönster för globalt auto-skriv</translation>
|
<translation>Använda postens titel till matchning med fönster för globalt auto-skriv</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Language</source>
|
||||||
|
<translation>Språk</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Show a system tray icon</source>
|
||||||
|
<translation>Visa statusfält ikon</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Hide window to system tray when minimized</source>
|
||||||
|
<translation>Vid minimering, minimera fönstret till systemfältet</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>SettingsWidgetSecurity</name>
|
<name>SettingsWidgetSecurity</name>
|
||||||
|
1203
share/translations/keepassx_zh_TW.ts
Normal file
1203
share/translations/keepassx_zh_TW.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -4,5 +4,9 @@ BASEDIR=$(dirname $0)
|
|||||||
|
|
||||||
cd $BASEDIR/../..
|
cd $BASEDIR/../..
|
||||||
|
|
||||||
|
echo Updating source file
|
||||||
lupdate -no-ui-lines -disable-heuristic similartext -locations none -no-obsolete src -ts share/translations/keepassx_en.ts
|
lupdate -no-ui-lines -disable-heuristic similartext -locations none -no-obsolete src -ts share/translations/keepassx_en.ts
|
||||||
lupdate -no-ui-lines -disable-heuristic similartext -locations none -pluralonly src -ts share/translations/keepassx_en_plurals.ts
|
lupdate -no-ui-lines -disable-heuristic similartext -locations none -pluralonly src -ts share/translations/keepassx_en_plurals.ts
|
||||||
|
|
||||||
|
echo Pulling translations from Transifex
|
||||||
|
tx pull -a --minimum-perc=80
|
||||||
|
@ -43,6 +43,7 @@ set(keepassx_SOURCES
|
|||||||
core/ListDeleter.h
|
core/ListDeleter.h
|
||||||
core/Metadata.cpp
|
core/Metadata.cpp
|
||||||
core/PasswordGenerator.cpp
|
core/PasswordGenerator.cpp
|
||||||
|
core/qlockfile.cpp
|
||||||
core/qsavefile.cpp
|
core/qsavefile.cpp
|
||||||
core/qsavefile_p.h
|
core/qsavefile_p.h
|
||||||
core/SignalMultiplexer.cpp
|
core/SignalMultiplexer.cpp
|
||||||
@ -138,6 +139,18 @@ if(NOT GCRYPT_HAS_SALSA20)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(UNIX)
|
||||||
|
set(keepassx_SOURCES
|
||||||
|
${keepassx_SOURCES}
|
||||||
|
core/qlockfile_unix.cpp
|
||||||
|
)
|
||||||
|
elseif(MINGW)
|
||||||
|
set(keepassx_SOURCES
|
||||||
|
${keepassx_SOURCES}
|
||||||
|
core/qlockfile_win.cpp
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(keepassx_SOURCES_MAINEXE
|
set(keepassx_SOURCES_MAINEXE
|
||||||
main.cpp
|
main.cpp
|
||||||
)
|
)
|
||||||
|
@ -89,5 +89,7 @@ void AutoTypeExecutor::execDelay(AutoTypeDelay* action)
|
|||||||
|
|
||||||
void AutoTypeExecutor::execClearField(AutoTypeClearField* action)
|
void AutoTypeExecutor::execClearField(AutoTypeClearField* action)
|
||||||
{
|
{
|
||||||
|
Q_UNUSED(action);
|
||||||
|
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
}
|
}
|
||||||
|
@ -655,7 +655,7 @@ void AutoTypePlatformX11::SendKeyPressedEvent(KeySym keysym)
|
|||||||
int keycode;
|
int keycode;
|
||||||
|
|
||||||
if (keysym == NoSymbol) {
|
if (keysym == NoSymbol) {
|
||||||
qWarning("No such key: keysym=0x%lX", static_cast<long>(keysym));
|
qWarning("No such key: keysym=0x%lX", keysym);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -682,7 +682,7 @@ void AutoTypePlatformX11::SendKeyPressedEvent(KeySym keysym)
|
|||||||
/* determine keycode and mask for the given keysym */
|
/* determine keycode and mask for the given keysym */
|
||||||
keycode = GetKeycode(keysym, &wanted_mask);
|
keycode = GetKeycode(keysym, &wanted_mask);
|
||||||
if (keycode < 8 || keycode > 255) {
|
if (keycode < 8 || keycode > 255) {
|
||||||
qWarning("Unable to get valid keycode for key: keysym=0x%lX", static_cast<long>(keysym));
|
qWarning("Unable to get valid keycode for key: keysym=0x%lX", keysym);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
#define KEEPASSX_SOURCE_DIR "${CMAKE_SOURCE_DIR}"
|
#define KEEPASSX_SOURCE_DIR "${CMAKE_SOURCE_DIR}"
|
||||||
#define KEEPASSX_BINARY_DIR "${CMAKE_BINARY_DIR}"
|
#define KEEPASSX_BINARY_DIR "${CMAKE_BINARY_DIR}"
|
||||||
|
|
||||||
|
#define KEEPASSX_PREFIX_DIR "${CMAKE_INSTALL_PREFIX}"
|
||||||
#define KEEPASSX_PLUGIN_DIR "${PLUGIN_INSTALL_DIR}"
|
#define KEEPASSX_PLUGIN_DIR "${PLUGIN_INSTALL_DIR}"
|
||||||
|
|
||||||
#define KEEPASSX_DATA_DIR "${DATA_INSTALL_DIR}"
|
#define KEEPASSX_DATA_DIR "${DATA_INSTALL_DIR}"
|
||||||
|
|
||||||
#cmakedefine HAVE_PR_SET_DUMPABLE 1
|
#cmakedefine HAVE_PR_SET_DUMPABLE 1
|
||||||
|
@ -188,32 +188,51 @@ void Database::setCompressionAlgo(Database::CompressionAlgorithm algo)
|
|||||||
m_data.compressionAlgo = algo;
|
m_data.compressionAlgo = algo;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database::setTransformRounds(quint64 rounds)
|
bool Database::setTransformRounds(quint64 rounds)
|
||||||
{
|
{
|
||||||
if (m_data.transformRounds != rounds) {
|
if (m_data.transformRounds != rounds) {
|
||||||
|
quint64 oldRounds = m_data.transformRounds;
|
||||||
|
|
||||||
m_data.transformRounds = rounds;
|
m_data.transformRounds = rounds;
|
||||||
|
|
||||||
if (m_data.hasKey) {
|
if (m_data.hasKey) {
|
||||||
setKey(m_data.key);
|
if (!setKey(m_data.key)) {
|
||||||
|
m_data.transformRounds = oldRounds;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database::setKey(const CompositeKey& key, const QByteArray& transformSeed, bool updateChangedTime)
|
bool Database::setKey(const CompositeKey& key, const QByteArray& transformSeed,
|
||||||
|
bool updateChangedTime)
|
||||||
{
|
{
|
||||||
|
bool ok;
|
||||||
|
QString errorString;
|
||||||
|
|
||||||
|
QByteArray transformedMasterKey =
|
||||||
|
key.transform(transformSeed, transformRounds(), &ok, &errorString);
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
m_data.key = key;
|
m_data.key = key;
|
||||||
m_data.transformSeed = transformSeed;
|
m_data.transformSeed = transformSeed;
|
||||||
m_data.transformedMasterKey = key.transform(transformSeed, transformRounds());
|
m_data.transformedMasterKey = transformedMasterKey;
|
||||||
m_data.hasKey = true;
|
m_data.hasKey = true;
|
||||||
if (updateChangedTime) {
|
if (updateChangedTime) {
|
||||||
m_metadata->setMasterKeyChanged(Tools::currentDateTimeUtc());
|
m_metadata->setMasterKeyChanged(Tools::currentDateTimeUtc());
|
||||||
}
|
}
|
||||||
Q_EMIT modifiedImmediate();
|
Q_EMIT modifiedImmediate();
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database::setKey(const CompositeKey& key)
|
bool Database::setKey(const CompositeKey& key)
|
||||||
{
|
{
|
||||||
setKey(key, randomGen()->randomArray(32));
|
return setKey(key, randomGen()->randomArray(32));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Database::hasKey() const
|
bool Database::hasKey() const
|
||||||
|
@ -90,13 +90,14 @@ public:
|
|||||||
|
|
||||||
void setCipher(const Uuid& cipher);
|
void setCipher(const Uuid& cipher);
|
||||||
void setCompressionAlgo(Database::CompressionAlgorithm algo);
|
void setCompressionAlgo(Database::CompressionAlgorithm algo);
|
||||||
void setTransformRounds(quint64 rounds);
|
bool setTransformRounds(quint64 rounds);
|
||||||
void setKey(const CompositeKey& key, const QByteArray& transformSeed, bool updateChangedTime = true);
|
bool setKey(const CompositeKey& key, const QByteArray& transformSeed,
|
||||||
|
bool updateChangedTime = true);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the database key and generates a random transform seed.
|
* Sets the database key and generates a random transform seed.
|
||||||
*/
|
*/
|
||||||
void setKey(const CompositeKey& key);
|
bool setKey(const CompositeKey& key);
|
||||||
bool hasKey() const;
|
bool hasKey() const;
|
||||||
bool verifyKey(const CompositeKey& key) const;
|
bool verifyKey(const CompositeKey& key) const;
|
||||||
void recycleEntry(Entry* entry);
|
void recycleEntry(Entry* entry);
|
||||||
|
@ -49,13 +49,20 @@ QString FilePath::pluginPath(const QString& name)
|
|||||||
|
|
||||||
pluginPaths << QCoreApplication::applicationDirPath();
|
pluginPaths << QCoreApplication::applicationDirPath();
|
||||||
|
|
||||||
QString systemPluginDir = KEEPASSX_PLUGIN_DIR;
|
QString configuredPluginDir = KEEPASSX_PLUGIN_DIR;
|
||||||
if (systemPluginDir != ".") {
|
if (configuredPluginDir != ".") {
|
||||||
if (!QDir(systemPluginDir).isAbsolute()) {
|
if (QDir(configuredPluginDir).isAbsolute()) {
|
||||||
systemPluginDir = QCoreApplication::applicationDirPath() + "/../" + systemPluginDir;
|
pluginPaths << configuredPluginDir;
|
||||||
systemPluginDir = QDir(systemPluginDir).canonicalPath();
|
}
|
||||||
|
else {
|
||||||
|
QString relativePluginDir = QString("%1/../%2")
|
||||||
|
.arg(QCoreApplication::applicationDirPath(), configuredPluginDir);
|
||||||
|
pluginPaths << QDir(relativePluginDir).canonicalPath();
|
||||||
|
|
||||||
|
QString absolutePluginDir = QString("%1/%2")
|
||||||
|
.arg(KEEPASSX_PREFIX_DIR, configuredPluginDir);
|
||||||
|
pluginPaths << QDir(absolutePluginDir).canonicalPath();
|
||||||
}
|
}
|
||||||
pluginPaths << systemPluginDir;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList dirFilter;
|
QStringList dirFilter;
|
||||||
@ -164,6 +171,9 @@ QIcon FilePath::onOffIcon(const QString& category, const QString& name)
|
|||||||
|
|
||||||
FilePath::FilePath()
|
FilePath::FilePath()
|
||||||
{
|
{
|
||||||
|
const QString appDirPath = QCoreApplication::applicationDirPath();
|
||||||
|
bool isDataDirAbsolute = QDir::isAbsolutePath(KEEPASSX_DATA_DIR);
|
||||||
|
|
||||||
if (false) {
|
if (false) {
|
||||||
}
|
}
|
||||||
#ifdef QT_DEBUG
|
#ifdef QT_DEBUG
|
||||||
@ -171,17 +181,19 @@ FilePath::FilePath()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
|
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
|
||||||
else if (testSetDir(QCoreApplication::applicationDirPath() + "/../share/keepassx")) {
|
else if (isDataDirAbsolute && testSetDir(KEEPASSX_DATA_DIR)) {
|
||||||
}
|
}
|
||||||
else if (testSetDir(KEEPASSX_DATA_DIR)) {
|
else if (!isDataDirAbsolute && testSetDir(QString("%1/../%2").arg(appDirPath, KEEPASSX_DATA_DIR))) {
|
||||||
|
}
|
||||||
|
else if (!isDataDirAbsolute && testSetDir(QString("%1/%2").arg(KEEPASSX_PREFIX_DIR, KEEPASSX_DATA_DIR))) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef Q_OS_MAC
|
#ifdef Q_OS_MAC
|
||||||
else if (testSetDir(QCoreApplication::applicationDirPath() + "/../Resources")) {
|
else if (testSetDir(appDirPath + "/../Resources")) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
else if (testSetDir(QCoreApplication::applicationDirPath() + "/share")) {
|
else if (testSetDir(appDirPath + "/share")) {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -39,10 +39,12 @@
|
|||||||
|
|
||||||
#include "config-keepassx.h"
|
#include "config-keepassx.h"
|
||||||
|
|
||||||
|
#if defined(HAVE_RLIMIT_CORE)
|
||||||
|
#include <sys/resource.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(HAVE_PR_SET_DUMPABLE)
|
#if defined(HAVE_PR_SET_DUMPABLE)
|
||||||
#include <sys/prctl.h>
|
#include <sys/prctl.h>
|
||||||
#elif defined(HAVE_RLIMIT_CORE)
|
|
||||||
#include <sys/resource.h>
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_PT_DENY_ATTACH
|
#ifdef HAVE_PT_DENY_ATTACH
|
||||||
@ -222,21 +224,23 @@ QString platform()
|
|||||||
|
|
||||||
void disableCoreDumps()
|
void disableCoreDumps()
|
||||||
{
|
{
|
||||||
bool success = false;
|
// default to true
|
||||||
|
// there is no point in printing a warning if this is not implemented on the platform
|
||||||
|
bool success = true;
|
||||||
|
|
||||||
// prefer PR_SET_DUMPABLE since that also prevents ptrace
|
#if defined(HAVE_RLIMIT_CORE)
|
||||||
#if defined(HAVE_PR_SET_DUMPABLE)
|
|
||||||
success = (prctl(PR_SET_DUMPABLE, 0) == 0);
|
|
||||||
#elif defined(HAVE_RLIMIT_CORE)
|
|
||||||
struct rlimit limit;
|
struct rlimit limit;
|
||||||
limit.rlim_cur = 0;
|
limit.rlim_cur = 0;
|
||||||
limit.rlim_max = 0;
|
limit.rlim_max = 0;
|
||||||
success = (setrlimit(RLIMIT_CORE, &limit) == 0);
|
success = success && (setrlimit(RLIMIT_CORE, &limit) == 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(HAVE_PR_SET_DUMPABLE)
|
||||||
|
success = success && (prctl(PR_SET_DUMPABLE, 0) == 0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Mac OS X
|
// Mac OS X
|
||||||
#ifdef HAVE_PT_DENY_ATTACH
|
#ifdef HAVE_PT_DENY_ATTACH
|
||||||
// make sure setrlimit() and ptrace() succeeded
|
|
||||||
success = success && (ptrace(PT_DENY_ATTACH, 0, 0, 0) == 0);
|
success = success && (ptrace(PT_DENY_ATTACH, 0, 0, 0) == 0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
337
src/core/qlockfile.cpp
Normal file
337
src/core/qlockfile.cpp
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
|
||||||
|
** Contact: http://www.qt-project.org/legal
|
||||||
|
**
|
||||||
|
** This file is part of the QtCore module of the Qt Toolkit.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:LGPL21$
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and Digia. For licensing terms and
|
||||||
|
** conditions see http://qt.digia.com/licensing. For further information
|
||||||
|
** use the contact form at http://qt.digia.com/contact-us.
|
||||||
|
**
|
||||||
|
** GNU Lesser General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||||
|
** General Public License version 2.1 or version 3 as published by the Free
|
||||||
|
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
||||||
|
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
||||||
|
** following information to ensure the GNU Lesser General Public License
|
||||||
|
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
||||||
|
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||||
|
**
|
||||||
|
** In addition, as a special exception, Digia gives you certain additional
|
||||||
|
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||||
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "qlockfile.h"
|
||||||
|
#include "qlockfile_p.h"
|
||||||
|
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\class QLockFile
|
||||||
|
\inmodule QtCore
|
||||||
|
\brief The QLockFile class provides locking between processes using a file.
|
||||||
|
\since 5.1
|
||||||
|
|
||||||
|
A lock file can be used to prevent multiple processes from accessing concurrently
|
||||||
|
the same resource. For instance, a configuration file on disk, or a socket, a port,
|
||||||
|
a region of shared memory...
|
||||||
|
|
||||||
|
Serialization is only guaranteed if all processes that access the shared resource
|
||||||
|
use QLockFile, with the same file path.
|
||||||
|
|
||||||
|
QLockFile supports two use cases:
|
||||||
|
to protect a resource for a short-term operation (e.g. verifying if a configuration
|
||||||
|
file has changed before saving new settings), and for long-lived protection of a
|
||||||
|
resource (e.g. a document opened by a user in an editor) for an indefinite amount of time.
|
||||||
|
|
||||||
|
When protecting for a short-term operation, it is acceptable to call lock() and wait
|
||||||
|
until any running operation finishes.
|
||||||
|
When protecting a resource over a long time, however, the application should always
|
||||||
|
call setStaleLockTime(0) and then tryLock() with a short timeout, in order to
|
||||||
|
warn the user that the resource is locked.
|
||||||
|
|
||||||
|
If the process holding the lock crashes, the lock file stays on disk and can prevent
|
||||||
|
any other process from accessing the shared resource, ever. For this reason, QLockFile
|
||||||
|
tries to detect such a "stale" lock file, based on the process ID written into the file,
|
||||||
|
and (in case that process ID got reused meanwhile), on the last modification time of
|
||||||
|
the lock file (30s by default, for the use case of a short-lived operation).
|
||||||
|
If the lock file is found to be stale, it will be deleted.
|
||||||
|
|
||||||
|
For the use case of protecting a resource over a long time, you should therefore call
|
||||||
|
setStaleLockTime(0), and when tryLock() returns LockFailedError, inform the user
|
||||||
|
that the document is locked, possibly using getLockInfo() for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\enum QLockFile::LockError
|
||||||
|
|
||||||
|
This enum describes the result of the last call to lock() or tryLock().
|
||||||
|
|
||||||
|
\value NoError The lock was acquired successfully.
|
||||||
|
\value LockFailedError The lock could not be acquired because another process holds it.
|
||||||
|
\value PermissionError The lock file could not be created, for lack of permissions
|
||||||
|
in the parent directory.
|
||||||
|
\value UnknownError Another error happened, for instance a full partition
|
||||||
|
prevented writing out the lock file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Constructs a new lock file object.
|
||||||
|
The object is created in an unlocked state.
|
||||||
|
When calling lock() or tryLock(), a lock file named \a fileName will be created,
|
||||||
|
if it doesn't already exist.
|
||||||
|
|
||||||
|
\sa lock(), unlock()
|
||||||
|
*/
|
||||||
|
QLockFile::QLockFile(const QString &fileName)
|
||||||
|
: d_ptr(new QLockFilePrivate(fileName))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Destroys the lock file object.
|
||||||
|
If the lock was acquired, this will release the lock, by deleting the lock file.
|
||||||
|
*/
|
||||||
|
QLockFile::~QLockFile()
|
||||||
|
{
|
||||||
|
unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Sets \a staleLockTime to be the time in milliseconds after which
|
||||||
|
a lock file is considered stale.
|
||||||
|
The default value is 30000, i.e. 30 seconds.
|
||||||
|
If your application typically keeps the file locked for more than 30 seconds
|
||||||
|
(for instance while saving megabytes of data for 2 minutes), you should set
|
||||||
|
a bigger value using setStaleLockTime().
|
||||||
|
|
||||||
|
The value of \a staleLockTime is used by lock() and tryLock() in order
|
||||||
|
to determine when an existing lock file is considered stale, i.e. left over
|
||||||
|
by a crashed process. This is useful for the case where the PID got reused
|
||||||
|
meanwhile, so the only way to detect a stale lock file is by the fact that
|
||||||
|
it has been around for a long time.
|
||||||
|
|
||||||
|
\sa staleLockTime()
|
||||||
|
*/
|
||||||
|
void QLockFile::setStaleLockTime(int staleLockTime)
|
||||||
|
{
|
||||||
|
Q_D(QLockFile);
|
||||||
|
d->staleLockTime = staleLockTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the time in milliseconds after which
|
||||||
|
a lock file is considered stale.
|
||||||
|
|
||||||
|
\sa setStaleLockTime()
|
||||||
|
*/
|
||||||
|
int QLockFile::staleLockTime() const
|
||||||
|
{
|
||||||
|
Q_D(const QLockFile);
|
||||||
|
return d->staleLockTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns \c true if the lock was acquired by this QLockFile instance,
|
||||||
|
otherwise returns \c false.
|
||||||
|
|
||||||
|
\sa lock(), unlock(), tryLock()
|
||||||
|
*/
|
||||||
|
bool QLockFile::isLocked() const
|
||||||
|
{
|
||||||
|
Q_D(const QLockFile);
|
||||||
|
return d->isLocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Creates the lock file.
|
||||||
|
|
||||||
|
If another process (or another thread) has created the lock file already,
|
||||||
|
this function will block until that process (or thread) releases it.
|
||||||
|
|
||||||
|
Calling this function multiple times on the same lock from the same
|
||||||
|
thread without unlocking first is not allowed. This function will
|
||||||
|
\e dead-lock when the file is locked recursively.
|
||||||
|
|
||||||
|
Returns \c true if the lock was acquired, false if it could not be acquired
|
||||||
|
due to an unrecoverable error, such as no permissions in the parent directory.
|
||||||
|
|
||||||
|
\sa unlock(), tryLock()
|
||||||
|
*/
|
||||||
|
bool QLockFile::lock()
|
||||||
|
{
|
||||||
|
return tryLock(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Attempts to create the lock file. This function returns \c true if the
|
||||||
|
lock was obtained; otherwise it returns \c false. If another process (or
|
||||||
|
another thread) has created the lock file already, this function will
|
||||||
|
wait for at most \a timeout milliseconds for the lock file to become
|
||||||
|
available.
|
||||||
|
|
||||||
|
Note: Passing a negative number as the \a timeout is equivalent to
|
||||||
|
calling lock(), i.e. this function will wait forever until the lock
|
||||||
|
file can be locked if \a timeout is negative.
|
||||||
|
|
||||||
|
If the lock was obtained, it must be released with unlock()
|
||||||
|
before another process (or thread) can successfully lock it.
|
||||||
|
|
||||||
|
Calling this function multiple times on the same lock from the same
|
||||||
|
thread without unlocking first is not allowed, this function will
|
||||||
|
\e always return false when attempting to lock the file recursively.
|
||||||
|
|
||||||
|
\sa lock(), unlock()
|
||||||
|
*/
|
||||||
|
bool QLockFile::tryLock(int timeout)
|
||||||
|
{
|
||||||
|
Q_D(QLockFile);
|
||||||
|
QElapsedTimer timer;
|
||||||
|
if (timeout > 0)
|
||||||
|
timer.start();
|
||||||
|
int sleepTime = 100;
|
||||||
|
Q_FOREVER {
|
||||||
|
d->lockError = d->tryLock_sys();
|
||||||
|
switch (d->lockError) {
|
||||||
|
case NoError:
|
||||||
|
d->isLocked = true;
|
||||||
|
return true;
|
||||||
|
case PermissionError:
|
||||||
|
case UnknownError:
|
||||||
|
return false;
|
||||||
|
case LockFailedError:
|
||||||
|
if (!d->isLocked && d->isApparentlyStale()) {
|
||||||
|
// Stale lock from another thread/process
|
||||||
|
// Ensure two processes don't remove it at the same time
|
||||||
|
QLockFile rmlock(d->fileName + QLatin1String(".rmlock"));
|
||||||
|
if (rmlock.tryLock()) {
|
||||||
|
if (d->isApparentlyStale() && d->removeStaleLock())
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (timeout == 0 || (timeout > 0 && timer.hasExpired(timeout)))
|
||||||
|
return false;
|
||||||
|
QLockFileThread::msleep(sleepTime);
|
||||||
|
if (sleepTime < 5 * 1000)
|
||||||
|
sleepTime *= 2;
|
||||||
|
}
|
||||||
|
// not reached
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\fn void QLockFile::unlock()
|
||||||
|
Releases the lock, by deleting the lock file.
|
||||||
|
|
||||||
|
Calling unlock() without locking the file first, does nothing.
|
||||||
|
|
||||||
|
\sa lock(), tryLock()
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Retrieves information about the current owner of the lock file.
|
||||||
|
|
||||||
|
If tryLock() returns \c false, and error() returns LockFailedError,
|
||||||
|
this function can be called to find out more information about the existing
|
||||||
|
lock file:
|
||||||
|
\list
|
||||||
|
\li the PID of the application (returned in \a pid)
|
||||||
|
\li the \a hostname it's running on (useful in case of networked filesystems),
|
||||||
|
\li the name of the application which created it (returned in \a appname),
|
||||||
|
\endlist
|
||||||
|
|
||||||
|
Note that tryLock() automatically deleted the file if there is no
|
||||||
|
running application with this PID, so LockFailedError can only happen if there is
|
||||||
|
an application with this PID (it could be unrelated though).
|
||||||
|
|
||||||
|
This can be used to inform users about the existing lock file and give them
|
||||||
|
the choice to delete it. After removing the file using removeStaleLockFile(),
|
||||||
|
the application can call tryLock() again.
|
||||||
|
|
||||||
|
This function returns \c true if the information could be successfully retrieved, false
|
||||||
|
if the lock file doesn't exist or doesn't contain the expected data.
|
||||||
|
This can happen if the lock file was deleted between the time where tryLock() failed
|
||||||
|
and the call to this function. Simply call tryLock() again if this happens.
|
||||||
|
*/
|
||||||
|
bool QLockFile::getLockInfo(qint64 *pid, QString *hostname, QString *appname) const
|
||||||
|
{
|
||||||
|
Q_D(const QLockFile);
|
||||||
|
return d->getLockInfo(pid, hostname, appname);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QLockFilePrivate::getLockInfo(qint64 *pid, QString *hostname, QString *appname) const
|
||||||
|
{
|
||||||
|
QFile reader(fileName);
|
||||||
|
if (!reader.open(QIODevice::ReadOnly))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QByteArray pidLine = reader.readLine();
|
||||||
|
pidLine.chop(1);
|
||||||
|
QByteArray appNameLine = reader.readLine();
|
||||||
|
appNameLine.chop(1);
|
||||||
|
QByteArray hostNameLine = reader.readLine();
|
||||||
|
hostNameLine.chop(1);
|
||||||
|
if (pidLine.isEmpty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
qint64 thePid = pidLine.toLongLong();
|
||||||
|
if (pid)
|
||||||
|
*pid = thePid;
|
||||||
|
if (appname)
|
||||||
|
*appname = QString::fromUtf8(appNameLine);
|
||||||
|
if (hostname)
|
||||||
|
*hostname = QString::fromUtf8(hostNameLine);
|
||||||
|
return thePid > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Attempts to forcefully remove an existing lock file.
|
||||||
|
|
||||||
|
Calling this is not recommended when protecting a short-lived operation: QLockFile
|
||||||
|
already takes care of removing lock files after they are older than staleLockTime().
|
||||||
|
|
||||||
|
This method should only be called when protecting a resource for a long time, i.e.
|
||||||
|
with staleLockTime(0), and after tryLock() returned LockFailedError, and the user
|
||||||
|
agreed on removing the lock file.
|
||||||
|
|
||||||
|
Returns \c true on success, false if the lock file couldn't be removed. This happens
|
||||||
|
on Windows, when the application owning the lock is still running.
|
||||||
|
*/
|
||||||
|
bool QLockFile::removeStaleLockFile()
|
||||||
|
{
|
||||||
|
Q_D(QLockFile);
|
||||||
|
if (d->isLocked) {
|
||||||
|
qWarning("removeStaleLockFile can only be called when not holding the lock");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return d->removeStaleLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Returns the lock file error status.
|
||||||
|
|
||||||
|
If tryLock() returns \c false, this function can be called to find out
|
||||||
|
the reason why the locking failed.
|
||||||
|
*/
|
||||||
|
QLockFile::LockError QLockFile::error() const
|
||||||
|
{
|
||||||
|
Q_D(const QLockFile);
|
||||||
|
return d->lockError;
|
||||||
|
}
|
||||||
|
|
||||||
|
QT_END_NAMESPACE
|
79
src/core/qlockfile.h
Normal file
79
src/core/qlockfile.h
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
|
||||||
|
** Contact: http://www.qt-project.org/legal
|
||||||
|
**
|
||||||
|
** This file is part of the QtCore module of the Qt Toolkit.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:LGPL21$
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and Digia. For licensing terms and
|
||||||
|
** conditions see http://qt.digia.com/licensing. For further information
|
||||||
|
** use the contact form at http://qt.digia.com/contact-us.
|
||||||
|
**
|
||||||
|
** GNU Lesser General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||||
|
** General Public License version 2.1 or version 3 as published by the Free
|
||||||
|
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
||||||
|
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
||||||
|
** following information to ensure the GNU Lesser General Public License
|
||||||
|
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
||||||
|
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||||
|
**
|
||||||
|
** In addition, as a special exception, Digia gives you certain additional
|
||||||
|
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||||
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef QLOCKFILE_H
|
||||||
|
#define QLOCKFILE_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QScopedPointer>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
class QLockFilePrivate;
|
||||||
|
|
||||||
|
class QLockFile
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QLockFile(const QString &fileName);
|
||||||
|
~QLockFile();
|
||||||
|
|
||||||
|
bool lock();
|
||||||
|
bool tryLock(int timeout = 0);
|
||||||
|
void unlock();
|
||||||
|
|
||||||
|
void setStaleLockTime(int);
|
||||||
|
int staleLockTime() const;
|
||||||
|
|
||||||
|
bool isLocked() const;
|
||||||
|
bool getLockInfo(qint64 *pid, QString *hostname, QString *appname) const;
|
||||||
|
bool removeStaleLockFile();
|
||||||
|
|
||||||
|
enum LockError {
|
||||||
|
NoError = 0,
|
||||||
|
LockFailedError = 1,
|
||||||
|
PermissionError = 2,
|
||||||
|
UnknownError = 3
|
||||||
|
};
|
||||||
|
LockError error() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QScopedPointer<QLockFilePrivate> d_ptr;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Q_DECLARE_PRIVATE(QLockFile)
|
||||||
|
Q_DISABLE_COPY(QLockFile)
|
||||||
|
};
|
||||||
|
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
#endif // QLOCKFILE_H
|
104
src/core/qlockfile_p.h
Normal file
104
src/core/qlockfile_p.h
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
|
||||||
|
** Contact: http://www.qt-project.org/legal
|
||||||
|
**
|
||||||
|
** This file is part of the QtCore module of the Qt Toolkit.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:LGPL21$
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and Digia. For licensing terms and
|
||||||
|
** conditions see http://qt.digia.com/licensing. For further information
|
||||||
|
** use the contact form at http://qt.digia.com/contact-us.
|
||||||
|
**
|
||||||
|
** GNU Lesser General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||||
|
** General Public License version 2.1 or version 3 as published by the Free
|
||||||
|
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
||||||
|
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
||||||
|
** following information to ensure the GNU Lesser General Public License
|
||||||
|
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
||||||
|
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||||
|
**
|
||||||
|
** In addition, as a special exception, Digia gives you certain additional
|
||||||
|
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||||
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef QLOCKFILE_P_H
|
||||||
|
#define QLOCKFILE_P_H
|
||||||
|
|
||||||
|
//
|
||||||
|
// W A R N I N G
|
||||||
|
// -------------
|
||||||
|
//
|
||||||
|
// This file is not part of the Qt API. It exists purely as an
|
||||||
|
// implementation detail. This header file may change from version to
|
||||||
|
// version without notice, or even be removed.
|
||||||
|
//
|
||||||
|
// We mean it.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "qlockfile.h"
|
||||||
|
|
||||||
|
#include <QFile>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
#include <qt_windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
class QLockFileThread : public QThread
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void msleep(unsigned long msecs) { QThread::msleep(msecs); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class QLockFilePrivate
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QLockFilePrivate(const QString &fn)
|
||||||
|
: fileName(fn),
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
fileHandle(INVALID_HANDLE_VALUE),
|
||||||
|
#else
|
||||||
|
fileHandle(-1),
|
||||||
|
#endif
|
||||||
|
staleLockTime(30 * 1000), // 30 seconds
|
||||||
|
lockError(QLockFile::NoError),
|
||||||
|
isLocked(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
QLockFile::LockError tryLock_sys();
|
||||||
|
bool removeStaleLock();
|
||||||
|
bool getLockInfo(qint64 *pid, QString *hostname, QString *appname) const;
|
||||||
|
// Returns \c true if the lock belongs to dead PID, or is old.
|
||||||
|
// The attempt to delete it will tell us if it was really stale or not, though.
|
||||||
|
bool isApparentlyStale() const;
|
||||||
|
|
||||||
|
#ifdef Q_OS_UNIX
|
||||||
|
static int checkFcntlWorksAfterFlock();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QString fileName;
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
Qt::HANDLE fileHandle;
|
||||||
|
#else
|
||||||
|
int fileHandle;
|
||||||
|
#endif
|
||||||
|
int staleLockTime; // "int milliseconds" is big enough for 24 days
|
||||||
|
QLockFile::LockError lockError;
|
||||||
|
bool isLocked;
|
||||||
|
};
|
||||||
|
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
#endif /* QLOCKFILE_P_H */
|
199
src/core/qlockfile_unix.cpp
Normal file
199
src/core/qlockfile_unix.cpp
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
|
||||||
|
** Contact: http://www.qt-project.org/legal
|
||||||
|
**
|
||||||
|
** This file is part of the QtCore module of the Qt Toolkit.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:LGPL21$
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and Digia. For licensing terms and
|
||||||
|
** conditions see http://qt.digia.com/licensing. For further information
|
||||||
|
** use the contact form at http://qt.digia.com/contact-us.
|
||||||
|
**
|
||||||
|
** GNU Lesser General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||||
|
** General Public License version 2.1 or version 3 as published by the Free
|
||||||
|
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
||||||
|
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
||||||
|
** following information to ensure the GNU Lesser General Public License
|
||||||
|
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
||||||
|
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||||
|
**
|
||||||
|
** In addition, as a special exception, Digia gives you certain additional
|
||||||
|
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||||
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "qlockfile_p.h"
|
||||||
|
|
||||||
|
#include <QTemporaryFile>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
#include <sys/file.h> // flock
|
||||||
|
#include <sys/types.h> // kill
|
||||||
|
#include <signal.h> // kill
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
#define EINTR_LOOP(var, cmd) \
|
||||||
|
do { \
|
||||||
|
var = cmd; \
|
||||||
|
} while (var == -1 && errno == EINTR)
|
||||||
|
|
||||||
|
// don't call QT_OPEN or ::open
|
||||||
|
// call qt_safe_open
|
||||||
|
static inline int qt_safe_open(const char *pathname, int flags, mode_t mode = 0777)
|
||||||
|
{
|
||||||
|
#ifdef O_CLOEXEC
|
||||||
|
flags |= O_CLOEXEC;
|
||||||
|
#endif
|
||||||
|
int fd;
|
||||||
|
EINTR_LOOP(fd, ::open(pathname, flags, mode));
|
||||||
|
|
||||||
|
// unknown flags are ignored, so we have no way of verifying if
|
||||||
|
// O_CLOEXEC was accepted
|
||||||
|
if (fd != -1)
|
||||||
|
::fcntl(fd, F_SETFD, FD_CLOEXEC);
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline qint64 qt_safe_write(int fd, const void *data, qint64 len)
|
||||||
|
{
|
||||||
|
qint64 ret = 0;
|
||||||
|
EINTR_LOOP(ret, ::write(fd, data, len));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString localHostName() // from QHostInfo::localHostName()
|
||||||
|
{
|
||||||
|
char hostName[512];
|
||||||
|
if (gethostname(hostName, sizeof(hostName)) == -1)
|
||||||
|
return QString();
|
||||||
|
hostName[sizeof(hostName) - 1] = '\0';
|
||||||
|
return QString::fromLocal8Bit(hostName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ### merge into qt_safe_write?
|
||||||
|
static qint64 qt_write_loop(int fd, const char *data, qint64 len)
|
||||||
|
{
|
||||||
|
qint64 pos = 0;
|
||||||
|
while (pos < len) {
|
||||||
|
const qint64 ret = qt_safe_write(fd, data + pos, len - pos);
|
||||||
|
if (ret == -1) // e.g. partition full
|
||||||
|
return pos;
|
||||||
|
pos += ret;
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool setNativeLocks(int fd)
|
||||||
|
{
|
||||||
|
#if defined(LOCK_EX) && defined(LOCK_NB)
|
||||||
|
if (flock(fd, LOCK_EX | LOCK_NB) == -1) // other threads, and other processes on a local fs
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
struct flock flockData;
|
||||||
|
flockData.l_type = F_WRLCK;
|
||||||
|
flockData.l_whence = SEEK_SET;
|
||||||
|
flockData.l_start = 0;
|
||||||
|
flockData.l_len = 0; // 0 = entire file
|
||||||
|
flockData.l_pid = getpid();
|
||||||
|
if (fcntl(fd, F_SETLK, &flockData) == -1) // for networked filesystems
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QLockFile::LockError QLockFilePrivate::tryLock_sys()
|
||||||
|
{
|
||||||
|
// Assemble data, to write in a single call to write
|
||||||
|
// (otherwise we'd have to check every write call)
|
||||||
|
// Use operator% from the fast builder to avoid multiple memory allocations.
|
||||||
|
QByteArray fileData = QByteArray::number(QCoreApplication::applicationPid()) + '\n'
|
||||||
|
+ qAppName().toUtf8() + '\n'
|
||||||
|
+ localHostName().toUtf8() + '\n';
|
||||||
|
|
||||||
|
const QByteArray lockFileName = QFile::encodeName(fileName);
|
||||||
|
const int fd = qt_safe_open(lockFileName.constData(), O_WRONLY | O_CREAT | O_EXCL, 0644);
|
||||||
|
if (fd < 0) {
|
||||||
|
switch (errno) {
|
||||||
|
case EEXIST:
|
||||||
|
return QLockFile::LockFailedError;
|
||||||
|
case EACCES:
|
||||||
|
case EROFS:
|
||||||
|
return QLockFile::PermissionError;
|
||||||
|
default:
|
||||||
|
return QLockFile::UnknownError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Ensure nobody else can delete the file while we have it
|
||||||
|
if (!setNativeLocks(fd))
|
||||||
|
qWarning() << "setNativeLocks failed:" << strerror(errno);
|
||||||
|
|
||||||
|
if (qt_write_loop(fd, fileData.constData(), fileData.size()) < fileData.size()) {
|
||||||
|
close(fd);
|
||||||
|
if (!QFile::remove(fileName))
|
||||||
|
qWarning("QLockFile: Could not remove our own lock file %s.", qPrintable(fileName));
|
||||||
|
return QLockFile::UnknownError; // partition full
|
||||||
|
}
|
||||||
|
|
||||||
|
// We hold the lock, continue.
|
||||||
|
fileHandle = fd;
|
||||||
|
|
||||||
|
return QLockFile::NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QLockFilePrivate::removeStaleLock()
|
||||||
|
{
|
||||||
|
const QByteArray lockFileName = QFile::encodeName(fileName);
|
||||||
|
const int fd = qt_safe_open(lockFileName.constData(), O_WRONLY, 0644);
|
||||||
|
if (fd < 0) // gone already?
|
||||||
|
return false;
|
||||||
|
bool success = setNativeLocks(fd) && (::unlink(lockFileName) == 0);
|
||||||
|
close(fd);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QLockFilePrivate::isApparentlyStale() const
|
||||||
|
{
|
||||||
|
qint64 pid;
|
||||||
|
QString hostname, appname;
|
||||||
|
if (!getLockInfo(&pid, &hostname, &appname))
|
||||||
|
return false;
|
||||||
|
if (hostname.isEmpty() || hostname == localHostName()) {
|
||||||
|
if (::kill(pid, 0) == -1 && errno == ESRCH)
|
||||||
|
return true; // PID doesn't exist anymore
|
||||||
|
}
|
||||||
|
const qint64 age = QFileInfo(fileName).lastModified().msecsTo(QDateTime::currentDateTime());
|
||||||
|
return staleLockTime > 0 && age > staleLockTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QLockFile::unlock()
|
||||||
|
{
|
||||||
|
Q_D(QLockFile);
|
||||||
|
if (!d->isLocked)
|
||||||
|
return;
|
||||||
|
close(d->fileHandle);
|
||||||
|
d->fileHandle = -1;
|
||||||
|
if (!QFile::remove(d->fileName)) {
|
||||||
|
qWarning() << "Could not remove our own lock file" << d->fileName << "maybe permissions changed meanwhile?";
|
||||||
|
// This is bad because other users of this lock file will now have to wait for the stale-lock-timeout...
|
||||||
|
}
|
||||||
|
QFile::remove(d->fileName);
|
||||||
|
d->lockError = QLockFile::NoError;
|
||||||
|
d->isLocked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QT_END_NAMESPACE
|
178
src/core/qlockfile_win.cpp
Normal file
178
src/core/qlockfile_win.cpp
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
**
|
||||||
|
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
|
||||||
|
** Contact: http://www.qt.io/licensing/
|
||||||
|
**
|
||||||
|
** This file is part of the QtCore module of the Qt Toolkit.
|
||||||
|
**
|
||||||
|
** $QT_BEGIN_LICENSE:LGPL21$
|
||||||
|
** Commercial License Usage
|
||||||
|
** Licensees holding valid commercial Qt licenses may use this file in
|
||||||
|
** accordance with the commercial license agreement provided with the
|
||||||
|
** Software or, alternatively, in accordance with the terms contained in
|
||||||
|
** a written agreement between you and The Qt Company. For licensing terms
|
||||||
|
** and conditions see http://www.qt.io/terms-conditions. For further
|
||||||
|
** information use the contact form at http://www.qt.io/contact-us.
|
||||||
|
**
|
||||||
|
** GNU Lesser General Public License Usage
|
||||||
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||||
|
** General Public License version 2.1 or version 3 as published by the Free
|
||||||
|
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
||||||
|
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
||||||
|
** following information to ensure the GNU Lesser General Public License
|
||||||
|
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
||||||
|
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||||
|
**
|
||||||
|
** As a special exception, The Qt Company gives you certain additional
|
||||||
|
** rights. These rights are described in The Qt Company LGPL Exception
|
||||||
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||||
|
**
|
||||||
|
** $QT_END_LICENSE$
|
||||||
|
**
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef _UNICODE
|
||||||
|
#define _UNICODE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef UNICODE
|
||||||
|
#define UNICODE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "qlockfile_p.h"
|
||||||
|
|
||||||
|
#include <qt_windows.h>
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
static inline QByteArray localHostName()
|
||||||
|
{
|
||||||
|
return qgetenv("COMPUTERNAME");
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool fileExists(const wchar_t *fileName)
|
||||||
|
{
|
||||||
|
WIN32_FILE_ATTRIBUTE_DATA data;
|
||||||
|
return GetFileAttributesEx(fileName, GetFileExInfoStandard, &data);
|
||||||
|
}
|
||||||
|
|
||||||
|
QLockFile::LockError QLockFilePrivate::tryLock_sys()
|
||||||
|
{
|
||||||
|
const ushort* nativePath = QDir::toNativeSeparators(fileName).utf16();
|
||||||
|
// When writing, allow others to read.
|
||||||
|
// When reading, QFile will allow others to read and write, all good.
|
||||||
|
// Adding FILE_SHARE_DELETE would allow forceful deletion of stale files,
|
||||||
|
// but Windows doesn't allow recreating it while this handle is open anyway,
|
||||||
|
// so this would only create confusion (can't lock, but no lock file to read from).
|
||||||
|
const DWORD dwShareMode = FILE_SHARE_READ;
|
||||||
|
#ifndef Q_OS_WINRT
|
||||||
|
SECURITY_ATTRIBUTES securityAtts = { sizeof(SECURITY_ATTRIBUTES), NULL, FALSE };
|
||||||
|
HANDLE fh = CreateFile((const wchar_t*)nativePath,
|
||||||
|
GENERIC_WRITE,
|
||||||
|
dwShareMode,
|
||||||
|
&securityAtts,
|
||||||
|
CREATE_NEW, // error if already exists
|
||||||
|
FILE_ATTRIBUTE_NORMAL,
|
||||||
|
NULL);
|
||||||
|
#else // !Q_OS_WINRT
|
||||||
|
HANDLE fh = CreateFile2((const wchar_t*)nativePath,
|
||||||
|
GENERIC_WRITE,
|
||||||
|
dwShareMode,
|
||||||
|
CREATE_NEW, // error if already exists
|
||||||
|
NULL);
|
||||||
|
#endif // Q_OS_WINRT
|
||||||
|
if (fh == INVALID_HANDLE_VALUE) {
|
||||||
|
const DWORD lastError = GetLastError();
|
||||||
|
switch (lastError) {
|
||||||
|
case ERROR_SHARING_VIOLATION:
|
||||||
|
case ERROR_ALREADY_EXISTS:
|
||||||
|
case ERROR_FILE_EXISTS:
|
||||||
|
return QLockFile::LockFailedError;
|
||||||
|
case ERROR_ACCESS_DENIED:
|
||||||
|
// readonly file, or file still in use by another process.
|
||||||
|
// Assume the latter if the file exists, since we don't create it readonly.
|
||||||
|
return fileExists((const wchar_t*)nativePath)
|
||||||
|
? QLockFile::LockFailedError
|
||||||
|
: QLockFile::PermissionError;
|
||||||
|
default:
|
||||||
|
qWarning() << "Got unexpected locking error" << lastError;
|
||||||
|
return QLockFile::UnknownError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We hold the lock, continue.
|
||||||
|
fileHandle = fh;
|
||||||
|
// Assemble data, to write in a single call to write
|
||||||
|
// (otherwise we'd have to check every write call)
|
||||||
|
QByteArray fileData;
|
||||||
|
fileData += QByteArray::number(QCoreApplication::applicationPid());
|
||||||
|
fileData += '\n';
|
||||||
|
fileData += QCoreApplication::applicationName().toUtf8();
|
||||||
|
fileData += '\n';
|
||||||
|
fileData += localHostName();
|
||||||
|
fileData += '\n';
|
||||||
|
DWORD bytesWritten = 0;
|
||||||
|
QLockFile::LockError error = QLockFile::NoError;
|
||||||
|
if (!WriteFile(fh, fileData.constData(), fileData.size(), &bytesWritten, NULL) || !FlushFileBuffers(fh))
|
||||||
|
error = QLockFile::UnknownError; // partition full
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QLockFilePrivate::removeStaleLock()
|
||||||
|
{
|
||||||
|
// QFile::remove fails on Windows if the other process is still using the file, so it's not stale.
|
||||||
|
return QFile::remove(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QLockFilePrivate::isApparentlyStale() const
|
||||||
|
{
|
||||||
|
qint64 pid;
|
||||||
|
QString hostname, appname;
|
||||||
|
if (!getLockInfo(&pid, &hostname, &appname))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// On WinRT there seems to be no way of obtaining information about other
|
||||||
|
// processes due to sandboxing
|
||||||
|
#ifndef Q_OS_WINRT
|
||||||
|
if (hostname == QString::fromLocal8Bit(localHostName())) {
|
||||||
|
HANDLE procHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
|
||||||
|
if (!procHandle)
|
||||||
|
return true;
|
||||||
|
// We got a handle but check if process is still alive
|
||||||
|
DWORD dwR = ::WaitForSingleObject(procHandle, 0);
|
||||||
|
::CloseHandle(procHandle);
|
||||||
|
if (dwR == WAIT_TIMEOUT)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif // !Q_OS_WINRT
|
||||||
|
const qint64 age = QFileInfo(fileName).lastModified().msecsTo(QDateTime::currentDateTime());
|
||||||
|
return staleLockTime > 0 && age > staleLockTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QLockFile::unlock()
|
||||||
|
{
|
||||||
|
Q_D(QLockFile);
|
||||||
|
if (!d->isLocked)
|
||||||
|
return;
|
||||||
|
CloseHandle(d->fileHandle);
|
||||||
|
int attempts = 0;
|
||||||
|
static const int maxAttempts = 500; // 500ms
|
||||||
|
while (!QFile::remove(d->fileName) && ++attempts < maxAttempts) {
|
||||||
|
// Someone is reading the lock file right now (on Windows this prevents deleting it).
|
||||||
|
QLockFileThread::msleep(1);
|
||||||
|
}
|
||||||
|
if (attempts == maxAttempts) {
|
||||||
|
qWarning() << "Could not remove our own lock file" << d->fileName << ". Either other users of the lock file are reading it constantly for 500 ms, or we (no longer) have permissions to delete the file";
|
||||||
|
// This is bad because other users of this lock file will now have to wait for the stale-lock-timeout...
|
||||||
|
}
|
||||||
|
d->lockError = QLockFile::NoError;
|
||||||
|
d->isLocked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QT_END_NAMESPACE
|
@ -142,66 +142,181 @@ bool Crypto::checkAlgorithms()
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Crypto::selfTest()
|
bool Crypto::selfTest()
|
||||||
|
{
|
||||||
|
return testSha256() && testAes256Cbc() && testAes256Ecb() && testTwofish() && testSalsa20();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Crypto::raiseError(const QString& str)
|
||||||
|
{
|
||||||
|
m_errorStr = str;
|
||||||
|
qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Crypto::testSha256()
|
||||||
{
|
{
|
||||||
QByteArray sha256Test = CryptoHash::hash("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
|
QByteArray sha256Test = CryptoHash::hash("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
|
||||||
CryptoHash::Sha256);
|
CryptoHash::Sha256);
|
||||||
|
|
||||||
if (sha256Test != QByteArray::fromHex("248D6A61D20638B8E5C026930C3E6039A33CE45964FF2167F6ECEDD419DB06C1")) {
|
if (sha256Test != QByteArray::fromHex("248D6A61D20638B8E5C026930C3E6039A33CE45964FF2167F6ECEDD419DB06C1")) {
|
||||||
m_errorStr = "SHA-256 mismatch.";
|
raiseError("SHA-256 mismatch.");
|
||||||
qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Crypto::testAes256Cbc()
|
||||||
|
{
|
||||||
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
|
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
|
||||||
QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
|
QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
|
||||||
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
|
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
|
||||||
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
||||||
QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6");
|
QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6");
|
||||||
cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d"));
|
cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d"));
|
||||||
|
bool ok;
|
||||||
|
|
||||||
SymmetricCipher aes256Encrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt, key, iv);
|
SymmetricCipher aes256Encrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
|
||||||
if (aes256Encrypt.process(plainText) != cipherText) {
|
if (!aes256Encrypt.init(key, iv)) {
|
||||||
m_errorStr = "AES-256 encryption mismatch.";
|
raiseError(aes256Encrypt.errorString());
|
||||||
qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
|
return false;
|
||||||
|
}
|
||||||
|
QByteArray encryptedText = aes256Encrypt.process(plainText, &ok);
|
||||||
|
if (!ok) {
|
||||||
|
raiseError(aes256Encrypt.errorString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (encryptedText != cipherText) {
|
||||||
|
raiseError("AES-256 CBC encryption mismatch.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SymmetricCipher aes256Descrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt, key, iv);
|
SymmetricCipher aes256Descrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||||
if (aes256Descrypt.process(cipherText) != plainText) {
|
if (!aes256Descrypt.init(key, iv)) {
|
||||||
m_errorStr = "AES-256 decryption mismatch.";
|
raiseError(aes256Descrypt.errorString());
|
||||||
qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
QByteArray decryptedText = aes256Descrypt.process(cipherText, &ok);
|
||||||
// Twofish
|
if (!ok) {
|
||||||
cipherText = QByteArray::fromHex("e0227c3cc80f3cb1b2ed847cc6f57d3c");
|
raiseError(aes256Descrypt.errorString());
|
||||||
cipherText.append(QByteArray::fromHex("657b1e7960b30fb7c8d62e72ae37c3a0"));
|
|
||||||
|
|
||||||
SymmetricCipher twofishEncrypt(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Encrypt, key, iv);
|
|
||||||
if (twofishEncrypt.process(plainText) != cipherText) {
|
|
||||||
m_errorStr = "Twofish encryption mismatch.";
|
|
||||||
qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (decryptedText != plainText) {
|
||||||
SymmetricCipher twofishDecrypt(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Decrypt, key, iv);
|
raiseError("AES-256 CBC decryption mismatch.");
|
||||||
if (twofishDecrypt.process(cipherText) != plainText) {
|
return false;
|
||||||
m_errorStr = "Twofish decryption mismatch.";
|
}
|
||||||
qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
|
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray salsa20Key = QByteArray::fromHex("F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112");
|
bool Crypto::testAes256Ecb()
|
||||||
QByteArray salsa20iv = QByteArray::fromHex("0000000000000000");
|
{
|
||||||
QByteArray salsa20Plain = QByteArray::fromHex("00000000000000000000000000000000");
|
QByteArray key = QByteArray::fromHex("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
|
||||||
QByteArray salsa20Cipher = QByteArray::fromHex("B4C0AFA503BE7FC29A62058166D56F8F");
|
QByteArray iv = QByteArray::fromHex("00000000000000000000000000000000");
|
||||||
|
QByteArray plainText = QByteArray::fromHex("00112233445566778899AABBCCDDEEFF");
|
||||||
SymmetricCipher salsa20Stream(SymmetricCipher::Salsa20, SymmetricCipher::Stream,
|
plainText.append(QByteArray::fromHex("00112233445566778899AABBCCDDEEFF"));
|
||||||
SymmetricCipher::Encrypt, salsa20Key, salsa20iv);
|
QByteArray cipherText = QByteArray::fromHex("8EA2B7CA516745BFEAFC49904B496089");
|
||||||
|
cipherText.append(QByteArray::fromHex("8EA2B7CA516745BFEAFC49904B496089"));
|
||||||
if (salsa20Stream.process(salsa20Plain) != salsa20Cipher) {
|
bool ok;
|
||||||
m_errorStr = "Salsa20 stream cipher mismatch.";
|
|
||||||
qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
|
SymmetricCipher aes256Encrypt(SymmetricCipher::Aes256, SymmetricCipher::Ecb, SymmetricCipher::Encrypt);
|
||||||
|
if (!aes256Encrypt.init(key, iv)) {
|
||||||
|
raiseError(aes256Encrypt.errorString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QByteArray encryptedText = aes256Encrypt.process(plainText, &ok);
|
||||||
|
if (!ok) {
|
||||||
|
raiseError(aes256Encrypt.errorString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (encryptedText != cipherText) {
|
||||||
|
raiseError("AES-256 ECB encryption mismatch.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SymmetricCipher aes256Descrypt(SymmetricCipher::Aes256, SymmetricCipher::Ecb, SymmetricCipher::Decrypt);
|
||||||
|
if (!aes256Descrypt.init(key, iv)) {
|
||||||
|
raiseError(aes256Descrypt.errorString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QByteArray decryptedText = aes256Descrypt.process(cipherText, &ok);
|
||||||
|
if (!ok) {
|
||||||
|
raiseError(aes256Descrypt.errorString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (decryptedText != plainText) {
|
||||||
|
raiseError("AES-256 ECB decryption mismatch.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Crypto::testTwofish()
|
||||||
|
{
|
||||||
|
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
|
||||||
|
QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
|
||||||
|
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
|
||||||
|
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
||||||
|
QByteArray cipherText = QByteArray::fromHex("e0227c3cc80f3cb1b2ed847cc6f57d3c");
|
||||||
|
cipherText.append(QByteArray::fromHex("657b1e7960b30fb7c8d62e72ae37c3a0"));
|
||||||
|
bool ok;
|
||||||
|
|
||||||
|
SymmetricCipher twofishEncrypt(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
|
||||||
|
if (!twofishEncrypt.init(key, iv)) {
|
||||||
|
raiseError(twofishEncrypt.errorString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QByteArray encryptedText = twofishEncrypt.process(plainText, &ok);
|
||||||
|
if (!ok) {
|
||||||
|
raiseError(twofishEncrypt.errorString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (encryptedText != cipherText) {
|
||||||
|
raiseError("Twofish encryption mismatch.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SymmetricCipher twofishDecrypt(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||||
|
if (!twofishDecrypt.init(key, iv)) {
|
||||||
|
raiseError(twofishEncrypt.errorString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QByteArray decryptedText = twofishDecrypt.process(cipherText, &ok);
|
||||||
|
if (!ok) {
|
||||||
|
raiseError(twofishDecrypt.errorString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (decryptedText != plainText) {
|
||||||
|
raiseError("Twofish encryption mismatch.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Crypto::testSalsa20()
|
||||||
|
{
|
||||||
|
QByteArray salsa20Key = QByteArray::fromHex("F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112");
|
||||||
|
QByteArray salsa20iv = QByteArray::fromHex("0000000000000000");
|
||||||
|
QByteArray salsa20Plain = QByteArray::fromHex("00000000000000000000000000000000");
|
||||||
|
QByteArray salsa20Cipher = QByteArray::fromHex("B4C0AFA503BE7FC29A62058166D56F8F");
|
||||||
|
bool ok;
|
||||||
|
|
||||||
|
SymmetricCipher salsa20Stream(SymmetricCipher::Salsa20, SymmetricCipher::Stream,
|
||||||
|
SymmetricCipher::Encrypt);
|
||||||
|
if (!salsa20Stream.init(salsa20Key, salsa20iv)) {
|
||||||
|
raiseError(salsa20Stream.errorString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray salsaProcessed = salsa20Stream.process(salsa20Plain, &ok);
|
||||||
|
if (!ok) {
|
||||||
|
raiseError(salsa20Stream.errorString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (salsaProcessed != salsa20Cipher) {
|
||||||
|
raiseError("Salsa20 stream cipher mismatch.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +34,12 @@ private:
|
|||||||
Crypto();
|
Crypto();
|
||||||
static bool checkAlgorithms();
|
static bool checkAlgorithms();
|
||||||
static bool selfTest();
|
static bool selfTest();
|
||||||
|
static void raiseError(const QString& str);
|
||||||
|
static bool testSha256();
|
||||||
|
static bool testAes256Cbc();
|
||||||
|
static bool testAes256Ecb();
|
||||||
|
static bool testTwofish();
|
||||||
|
static bool testSalsa20();
|
||||||
|
|
||||||
static bool m_initalized;
|
static bool m_initalized;
|
||||||
static QString m_errorStr;
|
static QString m_errorStr;
|
||||||
|
@ -49,6 +49,7 @@ CryptoHash::CryptoHash(CryptoHash::Algorithm algo)
|
|||||||
|
|
||||||
gcry_error_t error = gcry_md_open(&d->ctx, algoGcrypt, 0);
|
gcry_error_t error = gcry_md_open(&d->ctx, algoGcrypt, 0);
|
||||||
Q_ASSERT(error == 0); // TODO: error handling
|
Q_ASSERT(error == 0); // TODO: error handling
|
||||||
|
Q_UNUSED(error);
|
||||||
|
|
||||||
d->hashLen = gcry_md_get_algo_dlen(algoGcrypt);
|
d->hashLen = gcry_md_get_algo_dlen(algoGcrypt);
|
||||||
}
|
}
|
||||||
|
@ -22,17 +22,39 @@
|
|||||||
#include "crypto/SymmetricCipherSalsa20.h"
|
#include "crypto/SymmetricCipherSalsa20.h"
|
||||||
|
|
||||||
SymmetricCipher::SymmetricCipher(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
SymmetricCipher::SymmetricCipher(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||||
SymmetricCipher::Direction direction, const QByteArray& key, const QByteArray& iv)
|
SymmetricCipher::Direction direction)
|
||||||
: m_backend(createBackend(algo, mode, direction))
|
: m_backend(createBackend(algo, mode, direction))
|
||||||
|
, m_initialized(false)
|
||||||
{
|
{
|
||||||
m_backend->setKey(key);
|
|
||||||
m_backend->setIv(iv);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SymmetricCipher::~SymmetricCipher()
|
SymmetricCipher::~SymmetricCipher()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SymmetricCipher::init(const QByteArray& key, const QByteArray& iv)
|
||||||
|
{
|
||||||
|
if (!m_backend->init()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_backend->setKey(key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_backend->setIv(iv)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_initialized = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SymmetricCipher::isInitalized() const
|
||||||
|
{
|
||||||
|
return m_initialized;
|
||||||
|
}
|
||||||
|
|
||||||
SymmetricCipherBackend* SymmetricCipher::createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
SymmetricCipherBackend* SymmetricCipher::createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||||
SymmetricCipher::Direction direction)
|
SymmetricCipher::Direction direction)
|
||||||
{
|
{
|
||||||
@ -55,12 +77,17 @@ SymmetricCipherBackend* SymmetricCipher::createBackend(SymmetricCipher::Algorith
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SymmetricCipher::reset()
|
bool SymmetricCipher::reset()
|
||||||
{
|
{
|
||||||
m_backend->reset();
|
return m_backend->reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
int SymmetricCipher::blockSize() const
|
int SymmetricCipher::blockSize() const
|
||||||
{
|
{
|
||||||
return m_backend->blockSize();
|
return m_backend->blockSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString SymmetricCipher::errorString() const
|
||||||
|
{
|
||||||
|
return m_backend->errorString();
|
||||||
|
}
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QScopedPointer>
|
#include <QScopedPointer>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
#include "core/Global.h"
|
#include "core/Global.h"
|
||||||
#include "crypto/SymmetricCipherBackend.h"
|
#include "crypto/SymmetricCipherBackend.h"
|
||||||
@ -48,30 +49,35 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
SymmetricCipher(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
SymmetricCipher(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||||
SymmetricCipher::Direction direction, const QByteArray& key, const QByteArray& iv);
|
SymmetricCipher::Direction direction);
|
||||||
~SymmetricCipher();
|
~SymmetricCipher();
|
||||||
|
|
||||||
inline QByteArray process(const QByteArray& data) {
|
bool init(const QByteArray& key, const QByteArray& iv);
|
||||||
return m_backend->process(data);
|
bool isInitalized() const;
|
||||||
|
|
||||||
|
inline QByteArray process(const QByteArray& data, bool* ok) {
|
||||||
|
return m_backend->process(data, ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void processInPlace(QByteArray& data) {
|
inline bool processInPlace(QByteArray& data) Q_REQUIRED_RESULT {
|
||||||
m_backend->processInPlace(data);
|
return m_backend->processInPlace(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void processInPlace(QByteArray& data, quint64 rounds) {
|
inline bool processInPlace(QByteArray& data, quint64 rounds) Q_REQUIRED_RESULT {
|
||||||
Q_ASSERT(rounds > 0);
|
Q_ASSERT(rounds > 0);
|
||||||
m_backend->processInPlace(data, rounds);
|
return m_backend->processInPlace(data, rounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset();
|
bool reset();
|
||||||
int blockSize() const;
|
int blockSize() const;
|
||||||
|
QString errorString() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static SymmetricCipherBackend* createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
static SymmetricCipherBackend* createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||||
SymmetricCipher::Direction direction);
|
SymmetricCipher::Direction direction);
|
||||||
|
|
||||||
const QScopedPointer<SymmetricCipherBackend> m_backend;
|
const QScopedPointer<SymmetricCipherBackend> m_backend;
|
||||||
|
bool m_initialized;
|
||||||
|
|
||||||
Q_DISABLE_COPY(SymmetricCipher)
|
Q_DISABLE_COPY(SymmetricCipher)
|
||||||
};
|
};
|
||||||
|
@ -24,15 +24,18 @@ class SymmetricCipherBackend
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual ~SymmetricCipherBackend() {}
|
virtual ~SymmetricCipherBackend() {}
|
||||||
virtual void setKey(const QByteArray& key) = 0;
|
virtual bool init() = 0;
|
||||||
virtual void setIv(const QByteArray& iv) = 0;
|
virtual bool setKey(const QByteArray& key) = 0;
|
||||||
|
virtual bool setIv(const QByteArray& iv) = 0;
|
||||||
|
|
||||||
virtual QByteArray process(const QByteArray& data) = 0;
|
virtual QByteArray process(const QByteArray& data, bool* ok) = 0;
|
||||||
virtual void processInPlace(QByteArray& data) = 0;
|
virtual bool processInPlace(QByteArray& data) Q_REQUIRED_RESULT = 0;
|
||||||
virtual void processInPlace(QByteArray& data, quint64 rounds) = 0;
|
virtual bool processInPlace(QByteArray& data, quint64 rounds) Q_REQUIRED_RESULT = 0;
|
||||||
|
|
||||||
virtual void reset() = 0;
|
virtual bool reset() = 0;
|
||||||
virtual int blockSize() const = 0;
|
virtual int blockSize() const = 0;
|
||||||
|
|
||||||
|
virtual QString errorString() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSX_SYMMETRICCIPHERBACKEND_H
|
#endif // KEEPASSX_SYMMETRICCIPHERBACKEND_H
|
||||||
|
@ -22,22 +22,12 @@
|
|||||||
|
|
||||||
SymmetricCipherGcrypt::SymmetricCipherGcrypt(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
SymmetricCipherGcrypt::SymmetricCipherGcrypt(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||||
SymmetricCipher::Direction direction)
|
SymmetricCipher::Direction direction)
|
||||||
: m_algo(gcryptAlgo(algo))
|
: m_ctx(Q_NULLPTR)
|
||||||
|
, m_algo(gcryptAlgo(algo))
|
||||||
, m_mode(gcryptMode(mode))
|
, m_mode(gcryptMode(mode))
|
||||||
, m_direction(direction)
|
, m_direction(direction)
|
||||||
, m_blockSize(-1)
|
, m_blockSize(-1)
|
||||||
{
|
{
|
||||||
Q_ASSERT(Crypto::initalized());
|
|
||||||
|
|
||||||
gcry_error_t error;
|
|
||||||
|
|
||||||
error = gcry_cipher_open(&m_ctx, m_algo, m_mode, 0);
|
|
||||||
Q_ASSERT(error == 0); // TODO: real error checking
|
|
||||||
|
|
||||||
size_t blockSizeT;
|
|
||||||
error = gcry_cipher_algo_info(m_algo, GCRYCTL_GET_BLKLEN, Q_NULLPTR, &blockSizeT);
|
|
||||||
Q_ASSERT(error == 0);
|
|
||||||
m_blockSize = blockSizeT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SymmetricCipherGcrypt::~SymmetricCipherGcrypt()
|
SymmetricCipherGcrypt::~SymmetricCipherGcrypt()
|
||||||
@ -83,21 +73,65 @@ int SymmetricCipherGcrypt::gcryptMode(SymmetricCipher::Mode mode)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SymmetricCipherGcrypt::setKey(const QByteArray& key)
|
void SymmetricCipherGcrypt::setErrorString(gcry_error_t err)
|
||||||
|
{
|
||||||
|
const char* gcryptError = gcry_strerror(err);
|
||||||
|
const char* gcryptErrorSource = gcry_strsource(err);
|
||||||
|
|
||||||
|
m_errorString = QString("%1/%2").arg(QString::fromLocal8Bit(gcryptErrorSource),
|
||||||
|
QString::fromLocal8Bit(gcryptError));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SymmetricCipherGcrypt::init()
|
||||||
|
{
|
||||||
|
Q_ASSERT(Crypto::initalized());
|
||||||
|
|
||||||
|
gcry_error_t error;
|
||||||
|
|
||||||
|
error = gcry_cipher_open(&m_ctx, m_algo, m_mode, 0);
|
||||||
|
if (error != 0) {
|
||||||
|
setErrorString(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t blockSizeT;
|
||||||
|
error = gcry_cipher_algo_info(m_algo, GCRYCTL_GET_BLKLEN, Q_NULLPTR, &blockSizeT);
|
||||||
|
if (error != 0) {
|
||||||
|
setErrorString(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_blockSize = blockSizeT;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SymmetricCipherGcrypt::setKey(const QByteArray& key)
|
||||||
{
|
{
|
||||||
m_key = key;
|
m_key = key;
|
||||||
gcry_error_t error = gcry_cipher_setkey(m_ctx, m_key.constData(), m_key.size());
|
gcry_error_t error = gcry_cipher_setkey(m_ctx, m_key.constData(), m_key.size());
|
||||||
Q_ASSERT(error == 0);
|
|
||||||
|
if (error != 0) {
|
||||||
|
setErrorString(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SymmetricCipherGcrypt::setIv(const QByteArray& iv)
|
bool SymmetricCipherGcrypt::setIv(const QByteArray& iv)
|
||||||
{
|
{
|
||||||
m_iv = iv;
|
m_iv = iv;
|
||||||
gcry_error_t error = gcry_cipher_setiv(m_ctx, m_iv.constData(), m_iv.size());
|
gcry_error_t error = gcry_cipher_setiv(m_ctx, m_iv.constData(), m_iv.size());
|
||||||
Q_ASSERT(error == 0);
|
|
||||||
|
if (error != 0) {
|
||||||
|
setErrorString(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray SymmetricCipherGcrypt::process(const QByteArray& data)
|
QByteArray SymmetricCipherGcrypt::process(const QByteArray& data, bool* ok)
|
||||||
{
|
{
|
||||||
// TODO: check block size
|
// TODO: check block size
|
||||||
|
|
||||||
@ -113,12 +147,16 @@ QByteArray SymmetricCipherGcrypt::process(const QByteArray& data)
|
|||||||
error = gcry_cipher_encrypt(m_ctx, result.data(), data.size(), data.constData(), data.size());
|
error = gcry_cipher_encrypt(m_ctx, result.data(), data.size(), data.constData(), data.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_ASSERT(error == 0);
|
if (error != 0) {
|
||||||
|
setErrorString(error);
|
||||||
|
*ok = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*ok = true;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SymmetricCipherGcrypt::processInPlace(QByteArray& data)
|
bool SymmetricCipherGcrypt::processInPlace(QByteArray& data)
|
||||||
{
|
{
|
||||||
// TODO: check block size
|
// TODO: check block size
|
||||||
|
|
||||||
@ -131,10 +169,15 @@ void SymmetricCipherGcrypt::processInPlace(QByteArray& data)
|
|||||||
error = gcry_cipher_encrypt(m_ctx, data.data(), data.size(), Q_NULLPTR, 0);
|
error = gcry_cipher_encrypt(m_ctx, data.data(), data.size(), Q_NULLPTR, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_ASSERT(error == 0);
|
if (error != 0) {
|
||||||
|
setErrorString(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SymmetricCipherGcrypt::processInPlace(QByteArray& data, quint64 rounds)
|
bool SymmetricCipherGcrypt::processInPlace(QByteArray& data, quint64 rounds)
|
||||||
{
|
{
|
||||||
// TODO: check block size
|
// TODO: check block size
|
||||||
|
|
||||||
@ -146,28 +189,52 @@ void SymmetricCipherGcrypt::processInPlace(QByteArray& data, quint64 rounds)
|
|||||||
if (m_direction == SymmetricCipher::Decrypt) {
|
if (m_direction == SymmetricCipher::Decrypt) {
|
||||||
for (quint64 i = 0; i != rounds; ++i) {
|
for (quint64 i = 0; i != rounds; ++i) {
|
||||||
error = gcry_cipher_decrypt(m_ctx, rawData, size, Q_NULLPTR, 0);
|
error = gcry_cipher_decrypt(m_ctx, rawData, size, Q_NULLPTR, 0);
|
||||||
Q_ASSERT(error == 0);
|
|
||||||
|
if (error != 0) {
|
||||||
|
setErrorString(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (quint64 i = 0; i != rounds; ++i) {
|
for (quint64 i = 0; i != rounds; ++i) {
|
||||||
error = gcry_cipher_encrypt(m_ctx, rawData, size, Q_NULLPTR, 0);
|
error = gcry_cipher_encrypt(m_ctx, rawData, size, Q_NULLPTR, 0);
|
||||||
Q_ASSERT(error == 0);
|
|
||||||
|
if (error != 0) {
|
||||||
|
setErrorString(error);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SymmetricCipherGcrypt::reset()
|
bool SymmetricCipherGcrypt::reset()
|
||||||
{
|
{
|
||||||
gcry_error_t error;
|
gcry_error_t error;
|
||||||
|
|
||||||
error = gcry_cipher_reset(m_ctx);
|
error = gcry_cipher_reset(m_ctx);
|
||||||
Q_ASSERT(error == 0);
|
if (error != 0) {
|
||||||
|
setErrorString(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
error = gcry_cipher_setiv(m_ctx, m_iv.constData(), m_iv.size());
|
error = gcry_cipher_setiv(m_ctx, m_iv.constData(), m_iv.size());
|
||||||
Q_ASSERT(error == 0);
|
if (error != 0) {
|
||||||
|
setErrorString(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SymmetricCipherGcrypt::blockSize() const
|
int SymmetricCipherGcrypt::blockSize() const
|
||||||
{
|
{
|
||||||
return m_blockSize;
|
return m_blockSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString SymmetricCipherGcrypt::errorString() const
|
||||||
|
{
|
||||||
|
return m_errorString;
|
||||||
|
}
|
||||||
|
@ -29,19 +29,24 @@ public:
|
|||||||
SymmetricCipherGcrypt(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
SymmetricCipherGcrypt(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||||
SymmetricCipher::Direction direction);
|
SymmetricCipher::Direction direction);
|
||||||
~SymmetricCipherGcrypt();
|
~SymmetricCipherGcrypt();
|
||||||
void setKey(const QByteArray& key);
|
|
||||||
void setIv(const QByteArray& iv);
|
|
||||||
|
|
||||||
QByteArray process(const QByteArray& data);
|
bool init();
|
||||||
void processInPlace(QByteArray& data);
|
bool setKey(const QByteArray& key);
|
||||||
void processInPlace(QByteArray& data, quint64 rounds);
|
bool setIv(const QByteArray& iv);
|
||||||
|
|
||||||
void reset();
|
QByteArray process(const QByteArray& data, bool* ok);
|
||||||
|
bool processInPlace(QByteArray& data) Q_REQUIRED_RESULT;
|
||||||
|
bool processInPlace(QByteArray& data, quint64 rounds) Q_REQUIRED_RESULT;
|
||||||
|
|
||||||
|
bool reset();
|
||||||
int blockSize() const;
|
int blockSize() const;
|
||||||
|
|
||||||
|
QString errorString() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static int gcryptAlgo(SymmetricCipher::Algorithm algo);
|
static int gcryptAlgo(SymmetricCipher::Algorithm algo);
|
||||||
static int gcryptMode(SymmetricCipher::Mode mode);
|
static int gcryptMode(SymmetricCipher::Mode mode);
|
||||||
|
void setErrorString(gcry_error_t err);
|
||||||
|
|
||||||
gcry_cipher_hd_t m_ctx;
|
gcry_cipher_hd_t m_ctx;
|
||||||
const int m_algo;
|
const int m_algo;
|
||||||
@ -50,6 +55,7 @@ private:
|
|||||||
QByteArray m_key;
|
QByteArray m_key;
|
||||||
QByteArray m_iv;
|
QByteArray m_iv;
|
||||||
int m_blockSize;
|
int m_blockSize;
|
||||||
|
QString m_errorString;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSX_SYMMETRICCIPHERGCRYPT_H
|
#endif // KEEPASSX_SYMMETRICCIPHERGCRYPT_H
|
||||||
|
@ -33,23 +33,32 @@ SymmetricCipherSalsa20::~SymmetricCipherSalsa20()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void SymmetricCipherSalsa20::setKey(const QByteArray& key)
|
bool SymmetricCipherSalsa20::init()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SymmetricCipherSalsa20::setKey(const QByteArray& key)
|
||||||
{
|
{
|
||||||
Q_ASSERT((key.size() == 16) || (key.size() == 32));
|
Q_ASSERT((key.size() == 16) || (key.size() == 32));
|
||||||
|
|
||||||
m_key = key;
|
m_key = key;
|
||||||
ECRYPT_keysetup(&m_ctx, reinterpret_cast<const u8*>(m_key.constData()), m_key.size()*8, 64);
|
ECRYPT_keysetup(&m_ctx, reinterpret_cast<const u8*>(m_key.constData()), m_key.size()*8, 64);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SymmetricCipherSalsa20::setIv(const QByteArray& iv)
|
bool SymmetricCipherSalsa20::setIv(const QByteArray& iv)
|
||||||
{
|
{
|
||||||
Q_ASSERT(iv.size() == 8);
|
Q_ASSERT(iv.size() == 8);
|
||||||
|
|
||||||
m_iv = iv;
|
m_iv = iv;
|
||||||
ECRYPT_ivsetup(&m_ctx, reinterpret_cast<const u8*>(m_iv.constData()));
|
ECRYPT_ivsetup(&m_ctx, reinterpret_cast<const u8*>(m_iv.constData()));
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray SymmetricCipherSalsa20::process(const QByteArray& data)
|
QByteArray SymmetricCipherSalsa20::process(const QByteArray& data, bool* ok)
|
||||||
{
|
{
|
||||||
Q_ASSERT((data.size() < blockSize()) || ((data.size() % blockSize()) == 0));
|
Q_ASSERT((data.size() < blockSize()) || ((data.size() % blockSize()) == 0));
|
||||||
|
|
||||||
@ -59,18 +68,21 @@ QByteArray SymmetricCipherSalsa20::process(const QByteArray& data)
|
|||||||
ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast<const u8*>(data.constData()),
|
ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast<const u8*>(data.constData()),
|
||||||
reinterpret_cast<u8*>(result.data()), data.size());
|
reinterpret_cast<u8*>(result.data()), data.size());
|
||||||
|
|
||||||
|
*ok = true;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SymmetricCipherSalsa20::processInPlace(QByteArray& data)
|
bool SymmetricCipherSalsa20::processInPlace(QByteArray& data)
|
||||||
{
|
{
|
||||||
Q_ASSERT((data.size() < blockSize()) || ((data.size() % blockSize()) == 0));
|
Q_ASSERT((data.size() < blockSize()) || ((data.size() % blockSize()) == 0));
|
||||||
|
|
||||||
ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast<const u8*>(data.constData()),
|
ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast<const u8*>(data.constData()),
|
||||||
reinterpret_cast<u8*>(data.data()), data.size());
|
reinterpret_cast<u8*>(data.data()), data.size());
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SymmetricCipherSalsa20::processInPlace(QByteArray& data, quint64 rounds)
|
bool SymmetricCipherSalsa20::processInPlace(QByteArray& data, quint64 rounds)
|
||||||
{
|
{
|
||||||
Q_ASSERT((data.size() < blockSize()) || ((data.size() % blockSize()) == 0));
|
Q_ASSERT((data.size() < blockSize()) || ((data.size() % blockSize()) == 0));
|
||||||
|
|
||||||
@ -78,14 +90,23 @@ void SymmetricCipherSalsa20::processInPlace(QByteArray& data, quint64 rounds)
|
|||||||
ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast<const u8*>(data.constData()),
|
ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast<const u8*>(data.constData()),
|
||||||
reinterpret_cast<u8*>(data.data()), data.size());
|
reinterpret_cast<u8*>(data.data()), data.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SymmetricCipherSalsa20::reset()
|
bool SymmetricCipherSalsa20::reset()
|
||||||
{
|
{
|
||||||
ECRYPT_ivsetup(&m_ctx, reinterpret_cast<const u8*>(m_iv.constData()));
|
ECRYPT_ivsetup(&m_ctx, reinterpret_cast<const u8*>(m_iv.constData()));
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SymmetricCipherSalsa20::blockSize() const
|
int SymmetricCipherSalsa20::blockSize() const
|
||||||
{
|
{
|
||||||
return 64;
|
return 64;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString SymmetricCipherSalsa20::errorString() const
|
||||||
|
{
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
@ -28,19 +28,22 @@ public:
|
|||||||
SymmetricCipherSalsa20(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
SymmetricCipherSalsa20(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||||
SymmetricCipher::Direction direction);
|
SymmetricCipher::Direction direction);
|
||||||
~SymmetricCipherSalsa20();
|
~SymmetricCipherSalsa20();
|
||||||
|
bool init();
|
||||||
void setAlgorithm(SymmetricCipher::Algorithm algo);
|
void setAlgorithm(SymmetricCipher::Algorithm algo);
|
||||||
void setMode(SymmetricCipher::Mode mode);
|
void setMode(SymmetricCipher::Mode mode);
|
||||||
void setDirection(SymmetricCipher::Direction direction);
|
void setDirection(SymmetricCipher::Direction direction);
|
||||||
void setKey(const QByteArray& key);
|
bool setKey(const QByteArray& key);
|
||||||
void setIv(const QByteArray& iv);
|
bool setIv(const QByteArray& iv);
|
||||||
|
|
||||||
QByteArray process(const QByteArray& data);
|
QByteArray process(const QByteArray& data, bool* ok);
|
||||||
void processInPlace(QByteArray& data);
|
bool processInPlace(QByteArray& data);
|
||||||
void processInPlace(QByteArray& data, quint64 rounds);
|
bool processInPlace(QByteArray& data, quint64 rounds);
|
||||||
|
|
||||||
void reset();
|
bool reset();
|
||||||
int blockSize() const;
|
int blockSize() const;
|
||||||
|
|
||||||
|
QString errorString() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ECRYPT_ctx m_ctx;
|
ECRYPT_ctx m_ctx;
|
||||||
QByteArray m_key;
|
QByteArray m_key;
|
||||||
|
@ -49,7 +49,12 @@ private:
|
|||||||
|
|
||||||
|
|
||||||
KeePass1Reader::KeePass1Reader()
|
KeePass1Reader::KeePass1Reader()
|
||||||
: m_error(false)
|
: m_db(Q_NULLPTR)
|
||||||
|
, m_tmpParent(Q_NULLPTR)
|
||||||
|
, m_device(Q_NULLPTR)
|
||||||
|
, m_encryptionFlags(0)
|
||||||
|
, m_transformRounds(0)
|
||||||
|
, m_error(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,14 +159,16 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
|
|||||||
raiseError("Invalid number of transform rounds");
|
raiseError("Invalid number of transform rounds");
|
||||||
return Q_NULLPTR;
|
return Q_NULLPTR;
|
||||||
}
|
}
|
||||||
m_db->setTransformRounds(m_transformRounds);
|
if (!m_db->setTransformRounds(m_transformRounds)) {
|
||||||
|
raiseError(tr("Unable to calculate master key"));
|
||||||
|
return Q_NULLPTR;
|
||||||
|
}
|
||||||
|
|
||||||
qint64 contentPos = m_device->pos();
|
qint64 contentPos = m_device->pos();
|
||||||
|
|
||||||
QScopedPointer<SymmetricCipherStream> cipherStream(testKeys(password, keyfileData, contentPos));
|
QScopedPointer<SymmetricCipherStream> cipherStream(testKeys(password, keyfileData, contentPos));
|
||||||
|
|
||||||
if (!cipherStream) {
|
if (!cipherStream) {
|
||||||
raiseError("Unable to create cipher stream");
|
|
||||||
return Q_NULLPTR;
|
return Q_NULLPTR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,7 +241,10 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
|
|||||||
key.addKey(newFileKey);
|
key.addKey(newFileKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
db->setKey(key);
|
if (!db->setKey(key)) {
|
||||||
|
raiseError(tr("Unable to calculate master key"));
|
||||||
|
return Q_NULLPTR;
|
||||||
|
}
|
||||||
|
|
||||||
return db.take();
|
return db.take();
|
||||||
}
|
}
|
||||||
@ -326,16 +336,26 @@ SymmetricCipherStream* KeePass1Reader::testKeys(const QString& password, const Q
|
|||||||
}
|
}
|
||||||
|
|
||||||
QByteArray finalKey = key(passwordData, keyfileData);
|
QByteArray finalKey = key(passwordData, keyfileData);
|
||||||
|
if (finalKey.isEmpty()) {
|
||||||
|
return Q_NULLPTR;
|
||||||
|
}
|
||||||
if (m_encryptionFlags & KeePass1::Rijndael) {
|
if (m_encryptionFlags & KeePass1::Rijndael) {
|
||||||
cipherStream.reset(new SymmetricCipherStream(m_device, SymmetricCipher::Aes256,
|
cipherStream.reset(new SymmetricCipherStream(m_device, SymmetricCipher::Aes256,
|
||||||
SymmetricCipher::Cbc, SymmetricCipher::Decrypt, finalKey, m_encryptionIV));
|
SymmetricCipher::Cbc, SymmetricCipher::Decrypt));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
cipherStream.reset(new SymmetricCipherStream(m_device, SymmetricCipher::Twofish,
|
cipherStream.reset(new SymmetricCipherStream(m_device, SymmetricCipher::Twofish,
|
||||||
SymmetricCipher::Cbc, SymmetricCipher::Decrypt, finalKey, m_encryptionIV));
|
SymmetricCipher::Cbc, SymmetricCipher::Decrypt));
|
||||||
}
|
}
|
||||||
|
|
||||||
cipherStream->open(QIODevice::ReadOnly);
|
if (!cipherStream->init(finalKey, m_encryptionIV)) {
|
||||||
|
raiseError(cipherStream->errorString());
|
||||||
|
return Q_NULLPTR;
|
||||||
|
}
|
||||||
|
if (!cipherStream->open(QIODevice::ReadOnly)) {
|
||||||
|
raiseError(cipherStream->errorString());
|
||||||
|
return Q_NULLPTR;
|
||||||
|
}
|
||||||
|
|
||||||
bool success = verifyKey(cipherStream.data());
|
bool success = verifyKey(cipherStream.data());
|
||||||
|
|
||||||
@ -372,9 +392,18 @@ QByteArray KeePass1Reader::key(const QByteArray& password, const QByteArray& key
|
|||||||
key.setPassword(password);
|
key.setPassword(password);
|
||||||
key.setKeyfileData(keyfileData);
|
key.setKeyfileData(keyfileData);
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
QString errorString;
|
||||||
|
QByteArray transformedKey = key.transform(m_transformSeed, m_transformRounds, &ok, &errorString);
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
raiseError(errorString);
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
CryptoHash hash(CryptoHash::Sha256);
|
CryptoHash hash(CryptoHash::Sha256);
|
||||||
hash.addData(m_masterSeed);
|
hash.addData(m_masterSeed);
|
||||||
hash.addData(key.transform(m_transformSeed, m_transformRounds));
|
hash.addData(transformedKey);
|
||||||
return hash.result();
|
return hash.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,14 +20,19 @@
|
|||||||
#include "crypto/CryptoHash.h"
|
#include "crypto/CryptoHash.h"
|
||||||
#include "format/KeePass2.h"
|
#include "format/KeePass2.h"
|
||||||
|
|
||||||
KeePass2RandomStream::KeePass2RandomStream(const QByteArray& key)
|
KeePass2RandomStream::KeePass2RandomStream()
|
||||||
: m_cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt,
|
: m_cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt)
|
||||||
CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV)
|
|
||||||
, m_offset(0)
|
, m_offset(0)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray KeePass2RandomStream::randomBytes(int size)
|
bool KeePass2RandomStream::init(const QByteArray& key)
|
||||||
|
{
|
||||||
|
return m_cipher.init(CryptoHash::hash(key, CryptoHash::Sha256),
|
||||||
|
KeePass2::INNER_STREAM_SALSA20_IV);
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray KeePass2RandomStream::randomBytes(int size, bool* ok)
|
||||||
{
|
{
|
||||||
QByteArray result;
|
QByteArray result;
|
||||||
|
|
||||||
@ -35,7 +40,10 @@ QByteArray KeePass2RandomStream::randomBytes(int size)
|
|||||||
|
|
||||||
while (bytesRemaining > 0) {
|
while (bytesRemaining > 0) {
|
||||||
if (m_buffer.size() == m_offset) {
|
if (m_buffer.size() == m_offset) {
|
||||||
loadBlock();
|
if (!loadBlock()) {
|
||||||
|
*ok = false;
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int bytesToCopy = qMin(bytesRemaining, m_buffer.size() - m_offset);
|
int bytesToCopy = qMin(bytesRemaining, m_buffer.size() - m_offset);
|
||||||
@ -44,12 +52,20 @@ QByteArray KeePass2RandomStream::randomBytes(int size)
|
|||||||
bytesRemaining -= bytesToCopy;
|
bytesRemaining -= bytesToCopy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*ok = true;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray KeePass2RandomStream::process(const QByteArray& data)
|
QByteArray KeePass2RandomStream::process(const QByteArray& data, bool* ok)
|
||||||
{
|
{
|
||||||
QByteArray randomData = randomBytes(data.size());
|
bool randomBytesOk;
|
||||||
|
|
||||||
|
QByteArray randomData = randomBytes(data.size(), &randomBytesOk);
|
||||||
|
if (!randomBytesOk) {
|
||||||
|
*ok = false;
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray result;
|
QByteArray result;
|
||||||
result.resize(data.size());
|
result.resize(data.size());
|
||||||
|
|
||||||
@ -57,23 +73,39 @@ QByteArray KeePass2RandomStream::process(const QByteArray& data)
|
|||||||
result[i] = data[i] ^ randomData[i];
|
result[i] = data[i] ^ randomData[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*ok = true;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2RandomStream::processInPlace(QByteArray& data)
|
bool KeePass2RandomStream::processInPlace(QByteArray& data)
|
||||||
{
|
{
|
||||||
QByteArray randomData = randomBytes(data.size());
|
bool ok;
|
||||||
|
QByteArray randomData = randomBytes(data.size(), &ok);
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < data.size(); i++) {
|
for (int i = 0; i < data.size(); i++) {
|
||||||
data[i] = data[i] ^ randomData[i];
|
data[i] = data[i] ^ randomData[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2RandomStream::loadBlock()
|
QString KeePass2RandomStream::errorString() const
|
||||||
|
{
|
||||||
|
return m_cipher.errorString();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KeePass2RandomStream::loadBlock()
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_offset == m_buffer.size());
|
Q_ASSERT(m_offset == m_buffer.size());
|
||||||
|
|
||||||
m_buffer.fill('\0', m_cipher.blockSize());
|
m_buffer.fill('\0', m_cipher.blockSize());
|
||||||
m_cipher.processInPlace(m_buffer);
|
if (!m_cipher.processInPlace(m_buffer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
m_offset = 0;
|
m_offset = 0;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -25,13 +25,15 @@
|
|||||||
class KeePass2RandomStream
|
class KeePass2RandomStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit KeePass2RandomStream(const QByteArray& key);
|
KeePass2RandomStream();
|
||||||
QByteArray randomBytes(int size);
|
bool init(const QByteArray& key);
|
||||||
QByteArray process(const QByteArray& data);
|
QByteArray randomBytes(int size, bool* ok);
|
||||||
void processInPlace(QByteArray& data);
|
QByteArray process(const QByteArray& data, bool* ok);
|
||||||
|
bool processInPlace(QByteArray& data) Q_REQUIRED_RESULT;
|
||||||
|
QString errorString() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void loadBlock();
|
bool loadBlock();
|
||||||
|
|
||||||
SymmetricCipher m_cipher;
|
SymmetricCipher m_cipher;
|
||||||
QByteArray m_buffer;
|
QByteArray m_buffer;
|
||||||
|
@ -33,8 +33,12 @@
|
|||||||
#include "streams/SymmetricCipherStream.h"
|
#include "streams/SymmetricCipherStream.h"
|
||||||
|
|
||||||
KeePass2Reader::KeePass2Reader()
|
KeePass2Reader::KeePass2Reader()
|
||||||
: m_error(false)
|
: m_device(Q_NULLPTR)
|
||||||
|
, m_headerStream(Q_NULLPTR)
|
||||||
|
, m_error(false)
|
||||||
|
, m_headerEnd(false)
|
||||||
, m_saveXml(false)
|
, m_saveXml(false)
|
||||||
|
, m_db(Q_NULLPTR)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,16 +100,26 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
|
|||||||
return Q_NULLPTR;
|
return Q_NULLPTR;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_db->setKey(key, m_transformSeed, false);
|
if (!m_db->setKey(key, m_transformSeed, false)) {
|
||||||
|
raiseError(tr("Unable to calculate master key"));
|
||||||
|
return Q_NULLPTR;
|
||||||
|
}
|
||||||
|
|
||||||
CryptoHash hash(CryptoHash::Sha256);
|
CryptoHash hash(CryptoHash::Sha256);
|
||||||
hash.addData(m_masterSeed);
|
hash.addData(m_masterSeed);
|
||||||
hash.addData(m_db->transformedMasterKey());
|
hash.addData(m_db->transformedMasterKey());
|
||||||
QByteArray finalKey = hash.result();
|
QByteArray finalKey = hash.result();
|
||||||
|
|
||||||
SymmetricCipherStream cipherStream(m_device, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
SymmetricCipherStream cipherStream(m_device, SymmetricCipher::Aes256,
|
||||||
SymmetricCipher::Decrypt, finalKey, m_encryptionIV);
|
SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||||
cipherStream.open(QIODevice::ReadOnly);
|
if (!cipherStream.init(finalKey, m_encryptionIV)) {
|
||||||
|
raiseError(cipherStream.errorString());
|
||||||
|
return Q_NULLPTR;
|
||||||
|
}
|
||||||
|
if (!cipherStream.open(QIODevice::ReadOnly)) {
|
||||||
|
raiseError(cipherStream.errorString());
|
||||||
|
return Q_NULLPTR;
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray realStart = cipherStream.read(32);
|
QByteArray realStart = cipherStream.read(32);
|
||||||
|
|
||||||
@ -115,7 +129,10 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
|
|||||||
}
|
}
|
||||||
|
|
||||||
HashedBlockStream hashedStream(&cipherStream);
|
HashedBlockStream hashedStream(&cipherStream);
|
||||||
hashedStream.open(QIODevice::ReadOnly);
|
if (!hashedStream.open(QIODevice::ReadOnly)) {
|
||||||
|
raiseError(hashedStream.errorString());
|
||||||
|
return Q_NULLPTR;
|
||||||
|
}
|
||||||
|
|
||||||
QIODevice* xmlDevice;
|
QIODevice* xmlDevice;
|
||||||
QScopedPointer<QtIOCompressor> ioCompressor;
|
QScopedPointer<QtIOCompressor> ioCompressor;
|
||||||
@ -126,11 +143,18 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
|
|||||||
else {
|
else {
|
||||||
ioCompressor.reset(new QtIOCompressor(&hashedStream));
|
ioCompressor.reset(new QtIOCompressor(&hashedStream));
|
||||||
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
|
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
|
||||||
ioCompressor->open(QIODevice::ReadOnly);
|
if (!ioCompressor->open(QIODevice::ReadOnly)) {
|
||||||
|
raiseError(ioCompressor->errorString());
|
||||||
|
return Q_NULLPTR;
|
||||||
|
}
|
||||||
xmlDevice = ioCompressor.data();
|
xmlDevice = ioCompressor.data();
|
||||||
}
|
}
|
||||||
|
|
||||||
KeePass2RandomStream randomStream(m_protectedStreamKey);
|
KeePass2RandomStream randomStream;
|
||||||
|
if (!randomStream.init(m_protectedStreamKey)) {
|
||||||
|
raiseError(randomStream.errorString());
|
||||||
|
return Q_NULLPTR;
|
||||||
|
}
|
||||||
|
|
||||||
QScopedPointer<QBuffer> buffer;
|
QScopedPointer<QBuffer> buffer;
|
||||||
|
|
||||||
@ -340,7 +364,9 @@ void KeePass2Reader::setTansformRounds(const QByteArray& data)
|
|||||||
raiseError("Invalid transform rounds size");
|
raiseError("Invalid transform rounds size");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
m_db->setTransformRounds(Endian::bytesToUInt64(data, KeePass2::BYTEORDER));
|
if (!m_db->setTransformRounds(Endian::bytesToUInt64(data, KeePass2::BYTEORDER))) {
|
||||||
|
raiseError(tr("Unable to calculate master key"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,13 +88,20 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
|
|||||||
CHECK_RETURN(writeData(header.data()));
|
CHECK_RETURN(writeData(header.data()));
|
||||||
|
|
||||||
SymmetricCipherStream cipherStream(device, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
SymmetricCipherStream cipherStream(device, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
||||||
SymmetricCipher::Encrypt, finalKey, encryptionIV);
|
SymmetricCipher::Encrypt);
|
||||||
cipherStream.open(QIODevice::WriteOnly);
|
cipherStream.init(finalKey, encryptionIV);
|
||||||
|
if (!cipherStream.open(QIODevice::WriteOnly)) {
|
||||||
|
raiseError(cipherStream.errorString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
m_device = &cipherStream;
|
m_device = &cipherStream;
|
||||||
CHECK_RETURN(writeData(startBytes));
|
CHECK_RETURN(writeData(startBytes));
|
||||||
|
|
||||||
HashedBlockStream hashedStream(&cipherStream);
|
HashedBlockStream hashedStream(&cipherStream);
|
||||||
hashedStream.open(QIODevice::WriteOnly);
|
if (!hashedStream.open(QIODevice::WriteOnly)) {
|
||||||
|
raiseError(hashedStream.errorString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
QScopedPointer<QtIOCompressor> ioCompressor;
|
QScopedPointer<QtIOCompressor> ioCompressor;
|
||||||
|
|
||||||
@ -104,14 +111,25 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
|
|||||||
else {
|
else {
|
||||||
ioCompressor.reset(new QtIOCompressor(&hashedStream));
|
ioCompressor.reset(new QtIOCompressor(&hashedStream));
|
||||||
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
|
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
|
||||||
ioCompressor->open(QIODevice::WriteOnly);
|
if (!ioCompressor->open(QIODevice::WriteOnly)) {
|
||||||
|
raiseError(ioCompressor->errorString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
m_device = ioCompressor.data();
|
m_device = ioCompressor.data();
|
||||||
}
|
}
|
||||||
|
|
||||||
KeePass2RandomStream randomStream(protectedStreamKey);
|
KeePass2RandomStream randomStream;
|
||||||
|
if (!randomStream.init(protectedStreamKey)) {
|
||||||
|
raiseError(randomStream.errorString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
KeePass2XmlWriter xmlWriter;
|
KeePass2XmlWriter xmlWriter;
|
||||||
xmlWriter.writeDatabase(m_device, db, &randomStream, headerHash);
|
xmlWriter.writeDatabase(m_device, db, &randomStream, headerHash);
|
||||||
|
|
||||||
|
if (xmlWriter.hasError()) {
|
||||||
|
raiseError(xmlWriter.errorString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool KeePass2Writer::writeData(const QByteArray& data)
|
bool KeePass2Writer::writeData(const QByteArray& data)
|
||||||
|
@ -34,6 +34,7 @@ KeePass2XmlReader::KeePass2XmlReader()
|
|||||||
: m_randomStream(Q_NULLPTR)
|
: m_randomStream(Q_NULLPTR)
|
||||||
, m_db(Q_NULLPTR)
|
, m_db(Q_NULLPTR)
|
||||||
, m_meta(Q_NULLPTR)
|
, m_meta(Q_NULLPTR)
|
||||||
|
, m_tmpParent(Q_NULLPTR)
|
||||||
, m_error(false)
|
, m_error(false)
|
||||||
, m_strictMode(false)
|
, m_strictMode(false)
|
||||||
{
|
{
|
||||||
@ -809,7 +810,16 @@ void KeePass2XmlReader::parseEntryString(Entry* entry)
|
|||||||
|
|
||||||
if (isProtected && !value.isEmpty()) {
|
if (isProtected && !value.isEmpty()) {
|
||||||
if (m_randomStream) {
|
if (m_randomStream) {
|
||||||
value = QString::fromUtf8(m_randomStream->process(QByteArray::fromBase64(value.toLatin1())));
|
QByteArray ciphertext = QByteArray::fromBase64(value.toLatin1());
|
||||||
|
bool ok;
|
||||||
|
QByteArray plaintext = m_randomStream->process(ciphertext, &ok);
|
||||||
|
if (!ok) {
|
||||||
|
value.clear();
|
||||||
|
raiseError(m_randomStream->errorString());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
value = QString::fromUtf8(plaintext);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
raiseError("Unable to decrypt entry string");
|
raiseError("Unable to decrypt entry string");
|
||||||
@ -868,7 +878,9 @@ QPair<QString, QString> KeePass2XmlReader::parseEntryBinary(Entry* entry)
|
|||||||
&& (attr.value("Protected") == "True");
|
&& (attr.value("Protected") == "True");
|
||||||
|
|
||||||
if (isProtected && !value.isEmpty()) {
|
if (isProtected && !value.isEmpty()) {
|
||||||
m_randomStream->processInPlace(value);
|
if (!m_randomStream->processInPlace(value)) {
|
||||||
|
raiseError(m_randomStream->errorString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ KeePass2XmlWriter::KeePass2XmlWriter()
|
|||||||
: m_db(Q_NULLPTR)
|
: m_db(Q_NULLPTR)
|
||||||
, m_meta(Q_NULLPTR)
|
, m_meta(Q_NULLPTR)
|
||||||
, m_randomStream(Q_NULLPTR)
|
, m_randomStream(Q_NULLPTR)
|
||||||
|
, m_error(false)
|
||||||
{
|
{
|
||||||
m_xml.setAutoFormatting(true);
|
m_xml.setAutoFormatting(true);
|
||||||
m_xml.setAutoFormattingIndent(-1); // 1 tab
|
m_xml.setAutoFormattingIndent(-1); // 1 tab
|
||||||
@ -65,6 +66,16 @@ void KeePass2XmlWriter::writeDatabase(const QString& filename, Database* db)
|
|||||||
writeDatabase(&file, db);
|
writeDatabase(&file, db);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool KeePass2XmlWriter::hasError()
|
||||||
|
{
|
||||||
|
return m_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString KeePass2XmlWriter::errorString()
|
||||||
|
{
|
||||||
|
return m_errorStr;
|
||||||
|
}
|
||||||
|
|
||||||
void KeePass2XmlWriter::generateIdMap()
|
void KeePass2XmlWriter::generateIdMap()
|
||||||
{
|
{
|
||||||
QList<Entry*> allEntries = m_db->rootGroup()->entriesRecursive(true);
|
QList<Entry*> allEntries = m_db->rootGroup()->entriesRecursive(true);
|
||||||
@ -340,7 +351,11 @@ void KeePass2XmlWriter::writeEntry(const Entry* entry)
|
|||||||
if (protect) {
|
if (protect) {
|
||||||
if (m_randomStream) {
|
if (m_randomStream) {
|
||||||
m_xml.writeAttribute("Protected", "True");
|
m_xml.writeAttribute("Protected", "True");
|
||||||
QByteArray rawData = m_randomStream->process(entry->attributes()->value(key).toUtf8());
|
bool ok;
|
||||||
|
QByteArray rawData = m_randomStream->process(entry->attributes()->value(key).toUtf8(), &ok);
|
||||||
|
if (!ok) {
|
||||||
|
raiseError(m_randomStream->errorString());
|
||||||
|
}
|
||||||
value = QString::fromLatin1(rawData.toBase64());
|
value = QString::fromLatin1(rawData.toBase64());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -527,3 +542,9 @@ QString KeePass2XmlWriter::colorPartToString(int value)
|
|||||||
|
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void KeePass2XmlWriter::raiseError(const QString& errorMessage)
|
||||||
|
{
|
||||||
|
m_error = true;
|
||||||
|
m_errorStr = errorMessage;
|
||||||
|
}
|
||||||
|
@ -39,7 +39,7 @@ public:
|
|||||||
void writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = Q_NULLPTR,
|
void writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = Q_NULLPTR,
|
||||||
const QByteArray& headerHash = QByteArray());
|
const QByteArray& headerHash = QByteArray());
|
||||||
void writeDatabase(const QString& filename, Database* db);
|
void writeDatabase(const QString& filename, Database* db);
|
||||||
bool error();
|
bool hasError();
|
||||||
QString errorString();
|
QString errorString();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -74,12 +74,16 @@ private:
|
|||||||
void writeTriState(const QString& qualifiedName, Group::TriState triState);
|
void writeTriState(const QString& qualifiedName, Group::TriState triState);
|
||||||
QString colorPartToString(int value);
|
QString colorPartToString(int value);
|
||||||
|
|
||||||
|
void raiseError(const QString& errorMessage);
|
||||||
|
|
||||||
QXmlStreamWriter m_xml;
|
QXmlStreamWriter m_xml;
|
||||||
Database* m_db;
|
Database* m_db;
|
||||||
Metadata* m_meta;
|
Metadata* m_meta;
|
||||||
KeePass2RandomStream* m_randomStream;
|
KeePass2RandomStream* m_randomStream;
|
||||||
QByteArray m_headerHash;
|
QByteArray m_headerHash;
|
||||||
QHash<QByteArray, int> m_idMap;
|
QHash<QByteArray, int> m_idMap;
|
||||||
|
bool m_error;
|
||||||
|
QString m_errorStr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSX_KEEPASS2XMLWRITER_H
|
#endif // KEEPASSX_KEEPASS2XMLWRITER_H
|
||||||
|
@ -120,7 +120,10 @@ void ChangeMasterKeyWidget::generateKey()
|
|||||||
FileKey fileKey;
|
FileKey fileKey;
|
||||||
QString errorMsg;
|
QString errorMsg;
|
||||||
if (!fileKey.load(m_ui->keyFileCombo->currentText(), &errorMsg)) {
|
if (!fileKey.load(m_ui->keyFileCombo->currentText(), &errorMsg)) {
|
||||||
// TODO: error handling
|
MessageBox::critical(this, tr("Failed to set key file"),
|
||||||
|
tr("Failed to set %1 as the Key file:\n%2")
|
||||||
|
.arg(m_ui->keyFileCombo->currentText(), errorMsg));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
m_key.addKey(fileKey);
|
m_key.addKey(fileKey);
|
||||||
}
|
}
|
||||||
|
@ -130,7 +130,10 @@ void DatabaseSettingsWidget::reject()
|
|||||||
void DatabaseSettingsWidget::transformRoundsBenchmark()
|
void DatabaseSettingsWidget::transformRoundsBenchmark()
|
||||||
{
|
{
|
||||||
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
|
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
|
||||||
m_ui->transformRoundsSpinBox->setValue(CompositeKey::transformKeyBenchmark(1000));
|
int rounds = CompositeKey::transformKeyBenchmark(1000);
|
||||||
|
if (rounds != -1) {
|
||||||
|
m_ui->transformRoundsSpinBox->setValue(rounds);
|
||||||
|
}
|
||||||
QApplication::restoreOverrideCursor();
|
QApplication::restoreOverrideCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
|
|
||||||
DatabaseManagerStruct::DatabaseManagerStruct()
|
DatabaseManagerStruct::DatabaseManagerStruct()
|
||||||
: dbWidget(Q_NULLPTR)
|
: dbWidget(Q_NULLPTR)
|
||||||
|
, lockFile(Q_NULLPTR)
|
||||||
, saveToFilename(false)
|
, saveToFilename(false)
|
||||||
, modified(false)
|
, modified(false)
|
||||||
, readOnly(false)
|
, readOnly(false)
|
||||||
@ -142,8 +143,35 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
|
|||||||
}
|
}
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
|
QLockFile* lockFile = new QLockFile(QString("%1/.%2.lock").arg(fileInfo.canonicalPath(), fileInfo.fileName()));
|
||||||
|
lockFile->setStaleLockTime(0);
|
||||||
|
|
||||||
|
if (!dbStruct.readOnly && !lockFile->tryLock()) {
|
||||||
|
// for now silently ignore if we can't create a lock file
|
||||||
|
// due to lack of permissions
|
||||||
|
if (lockFile->error() != QLockFile::PermissionError) {
|
||||||
|
QMessageBox::StandardButton result = MessageBox::question(this, tr("Open database"),
|
||||||
|
tr("The database you are trying to open is locked by another instance of KeePassX.\n"
|
||||||
|
"Do you want to open it anyway? Alternatively the database is opened read-only."),
|
||||||
|
QMessageBox::Yes | QMessageBox::No);
|
||||||
|
|
||||||
|
if (result == QMessageBox::No) {
|
||||||
|
dbStruct.readOnly = true;
|
||||||
|
delete lockFile;
|
||||||
|
lockFile = Q_NULLPTR;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// take over the lock file if possible
|
||||||
|
if (lockFile->removeStaleLockFile()) {
|
||||||
|
lockFile->tryLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Database* db = new Database();
|
Database* db = new Database();
|
||||||
dbStruct.dbWidget = new DatabaseWidget(db, this);
|
dbStruct.dbWidget = new DatabaseWidget(db, this);
|
||||||
|
dbStruct.lockFile = lockFile;
|
||||||
dbStruct.saveToFilename = !dbStruct.readOnly;
|
dbStruct.saveToFilename = !dbStruct.readOnly;
|
||||||
|
|
||||||
dbStruct.filePath = fileInfo.absoluteFilePath();
|
dbStruct.filePath = fileInfo.absoluteFilePath();
|
||||||
@ -238,6 +266,7 @@ void DatabaseTabWidget::deleteDatabase(Database* db)
|
|||||||
removeTab(index);
|
removeTab(index);
|
||||||
toggleTabbar();
|
toggleTabbar();
|
||||||
m_dbList.remove(db);
|
m_dbList.remove(db);
|
||||||
|
delete dbStruct.lockFile;
|
||||||
delete dbStruct.dbWidget;
|
delete dbStruct.dbWidget;
|
||||||
delete db;
|
delete db;
|
||||||
|
|
||||||
@ -311,6 +340,11 @@ bool DatabaseTabWidget::saveDatabaseAs(Database* db)
|
|||||||
dbStruct.canonicalFilePath = fileInfo.canonicalFilePath();
|
dbStruct.canonicalFilePath = fileInfo.canonicalFilePath();
|
||||||
dbStruct.fileName = fileInfo.fileName();
|
dbStruct.fileName = fileInfo.fileName();
|
||||||
dbStruct.dbWidget->updateFilename(dbStruct.filePath);
|
dbStruct.dbWidget->updateFilename(dbStruct.filePath);
|
||||||
|
QString lockFileName = QString("%1/.%2.lock")
|
||||||
|
.arg(fileInfo.canonicalPath(), fileInfo.fileName());
|
||||||
|
dbStruct.lockFile = new QLockFile(lockFileName);
|
||||||
|
dbStruct.lockFile->setStaleLockTime(0);
|
||||||
|
dbStruct.lockFile->tryLock();
|
||||||
updateTabName(db);
|
updateTabName(db);
|
||||||
updateLastDatabases(dbStruct.filePath);
|
updateLastDatabases(dbStruct.filePath);
|
||||||
return true;
|
return true;
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QTabWidget>
|
#include <QTabWidget>
|
||||||
|
|
||||||
|
#include "core/qlockfile.h"
|
||||||
#include "format/KeePass2Writer.h"
|
#include "format/KeePass2Writer.h"
|
||||||
#include "gui/DatabaseWidget.h"
|
#include "gui/DatabaseWidget.h"
|
||||||
|
|
||||||
@ -34,6 +35,7 @@ struct DatabaseManagerStruct
|
|||||||
DatabaseManagerStruct();
|
DatabaseManagerStruct();
|
||||||
|
|
||||||
DatabaseWidget* dbWidget;
|
DatabaseWidget* dbWidget;
|
||||||
|
QLockFile* lockFile;
|
||||||
QString filePath;
|
QString filePath;
|
||||||
QString canonicalFilePath;
|
QString canonicalFilePath;
|
||||||
QString fileName;
|
QString fileName;
|
||||||
|
@ -599,8 +599,13 @@ void DatabaseWidget::updateMasterKey(bool accepted)
|
|||||||
{
|
{
|
||||||
if (accepted) {
|
if (accepted) {
|
||||||
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
|
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
|
||||||
m_db->setKey(m_changeMasterKeyWidget->newMasterKey());
|
bool result = m_db->setKey(m_changeMasterKeyWidget->newMasterKey());
|
||||||
QApplication::restoreOverrideCursor();
|
QApplication::restoreOverrideCursor();
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
MessageBox::critical(this, tr("Error"), tr("Unable to calculate master key"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (!m_db->hasKey()) {
|
else if (!m_db->hasKey()) {
|
||||||
Q_EMIT closeRequest();
|
Q_EMIT closeRequest();
|
||||||
@ -906,3 +911,53 @@ bool DatabaseWidget::isGroupSelected() const
|
|||||||
{
|
{
|
||||||
return m_groupView->currentGroup() != Q_NULLPTR;
|
return m_groupView->currentGroup() != Q_NULLPTR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DatabaseWidget::currentEntryHasTitle()
|
||||||
|
{
|
||||||
|
Entry* currentEntry = m_entryView->currentEntry();
|
||||||
|
if (!currentEntry) {
|
||||||
|
Q_ASSERT(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !currentEntry->title().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseWidget::currentEntryHasUsername()
|
||||||
|
{
|
||||||
|
Entry* currentEntry = m_entryView->currentEntry();
|
||||||
|
if (!currentEntry) {
|
||||||
|
Q_ASSERT(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !currentEntry->username().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseWidget::currentEntryHasPassword()
|
||||||
|
{
|
||||||
|
Entry* currentEntry = m_entryView->currentEntry();
|
||||||
|
if (!currentEntry) {
|
||||||
|
Q_ASSERT(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !currentEntry->password().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseWidget::currentEntryHasUrl()
|
||||||
|
{
|
||||||
|
Entry* currentEntry = m_entryView->currentEntry();
|
||||||
|
if (!currentEntry) {
|
||||||
|
Q_ASSERT(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !currentEntry->url().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseWidget::currentEntryHasNotes()
|
||||||
|
{
|
||||||
|
Entry* currentEntry = m_entryView->currentEntry();
|
||||||
|
if (!currentEntry) {
|
||||||
|
Q_ASSERT(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !currentEntry->notes().isEmpty();
|
||||||
|
}
|
||||||
|
@ -80,6 +80,11 @@ public:
|
|||||||
QList<int> entryHeaderViewSizes() const;
|
QList<int> entryHeaderViewSizes() const;
|
||||||
void setEntryViewHeaderSizes(const QList<int>& sizes);
|
void setEntryViewHeaderSizes(const QList<int>& sizes);
|
||||||
void clearAllWidgets();
|
void clearAllWidgets();
|
||||||
|
bool currentEntryHasTitle();
|
||||||
|
bool currentEntryHasUsername();
|
||||||
|
bool currentEntryHasPassword();
|
||||||
|
bool currentEntryHasUrl();
|
||||||
|
bool currentEntryHasNotes();
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void closeRequest();
|
void closeRequest();
|
||||||
|
@ -323,14 +323,14 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
|
|||||||
m_ui->actionEntryClone->setEnabled(singleEntrySelected && !inSearch);
|
m_ui->actionEntryClone->setEnabled(singleEntrySelected && !inSearch);
|
||||||
m_ui->actionEntryEdit->setEnabled(singleEntrySelected);
|
m_ui->actionEntryEdit->setEnabled(singleEntrySelected);
|
||||||
m_ui->actionEntryDelete->setEnabled(entriesSelected);
|
m_ui->actionEntryDelete->setEnabled(entriesSelected);
|
||||||
m_ui->actionEntryCopyTitle->setEnabled(singleEntrySelected);
|
m_ui->actionEntryCopyTitle->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTitle());
|
||||||
m_ui->actionEntryCopyUsername->setEnabled(singleEntrySelected);
|
m_ui->actionEntryCopyUsername->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUsername());
|
||||||
m_ui->actionEntryCopyPassword->setEnabled(singleEntrySelected);
|
m_ui->actionEntryCopyPassword->setEnabled(singleEntrySelected && dbWidget->currentEntryHasPassword());
|
||||||
m_ui->actionEntryCopyURL->setEnabled(singleEntrySelected);
|
m_ui->actionEntryCopyURL->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl());
|
||||||
m_ui->actionEntryCopyNotes->setEnabled(singleEntrySelected);
|
m_ui->actionEntryCopyNotes->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl());
|
||||||
m_ui->menuEntryCopyAttribute->setEnabled(singleEntrySelected);
|
m_ui->menuEntryCopyAttribute->setEnabled(singleEntrySelected);
|
||||||
m_ui->actionEntryAutoType->setEnabled(singleEntrySelected);
|
m_ui->actionEntryAutoType->setEnabled(singleEntrySelected);
|
||||||
m_ui->actionEntryOpenUrl->setEnabled(singleEntrySelected);
|
m_ui->actionEntryOpenUrl->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl());
|
||||||
m_ui->actionGroupNew->setEnabled(groupSelected);
|
m_ui->actionGroupNew->setEnabled(groupSelected);
|
||||||
m_ui->actionGroupEdit->setEnabled(groupSelected);
|
m_ui->actionGroupEdit->setEnabled(groupSelected);
|
||||||
m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup());
|
m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup());
|
||||||
|
@ -38,6 +38,8 @@ EditGroupWidget::EditGroupWidget(QWidget* parent)
|
|||||||
add(tr("Properties"), m_editWidgetProperties);
|
add(tr("Properties"), m_editWidgetProperties);
|
||||||
|
|
||||||
connect(m_mainUi->expireCheck, SIGNAL(toggled(bool)), m_mainUi->expireDatePicker, SLOT(setEnabled(bool)));
|
connect(m_mainUi->expireCheck, SIGNAL(toggled(bool)), m_mainUi->expireDatePicker, SLOT(setEnabled(bool)));
|
||||||
|
connect(m_mainUi->autoTypeSequenceCustomRadio, SIGNAL(toggled(bool)),
|
||||||
|
m_mainUi->autoTypeSequenceCustomEdit, SLOT(setEnabled(bool)));
|
||||||
|
|
||||||
connect(this, SIGNAL(accepted()), SLOT(save()));
|
connect(this, SIGNAL(accepted()), SLOT(save()));
|
||||||
connect(this, SIGNAL(rejected()), SLOT(cancel()));
|
connect(this, SIGNAL(rejected()), SLOT(cancel()));
|
||||||
@ -74,6 +76,13 @@ void EditGroupWidget::loadGroup(Group* group, bool create, Database* database)
|
|||||||
m_mainUi->expireDatePicker->setDateTime(group->timeInfo().expiryTime().toLocalTime());
|
m_mainUi->expireDatePicker->setDateTime(group->timeInfo().expiryTime().toLocalTime());
|
||||||
m_mainUi->searchComboBox->setCurrentIndex(indexFromTriState(group->searchingEnabled()));
|
m_mainUi->searchComboBox->setCurrentIndex(indexFromTriState(group->searchingEnabled()));
|
||||||
m_mainUi->autotypeComboBox->setCurrentIndex(indexFromTriState(group->autoTypeEnabled()));
|
m_mainUi->autotypeComboBox->setCurrentIndex(indexFromTriState(group->autoTypeEnabled()));
|
||||||
|
if (group->defaultAutoTypeSequence().isEmpty()) {
|
||||||
|
m_mainUi->autoTypeSequenceInherit->setChecked(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_mainUi->autoTypeSequenceCustomRadio->setChecked(true);
|
||||||
|
}
|
||||||
|
m_mainUi->autoTypeSequenceCustomEdit->setText(group->defaultAutoTypeSequence());
|
||||||
|
|
||||||
IconStruct iconStruct;
|
IconStruct iconStruct;
|
||||||
iconStruct.uuid = group->iconUuid();
|
iconStruct.uuid = group->iconUuid();
|
||||||
@ -97,6 +106,13 @@ void EditGroupWidget::save()
|
|||||||
m_group->setSearchingEnabled(triStateFromIndex(m_mainUi->searchComboBox->currentIndex()));
|
m_group->setSearchingEnabled(triStateFromIndex(m_mainUi->searchComboBox->currentIndex()));
|
||||||
m_group->setAutoTypeEnabled(triStateFromIndex(m_mainUi->autotypeComboBox->currentIndex()));
|
m_group->setAutoTypeEnabled(triStateFromIndex(m_mainUi->autotypeComboBox->currentIndex()));
|
||||||
|
|
||||||
|
if (m_mainUi->autoTypeSequenceInherit->isChecked()) {
|
||||||
|
m_group->setDefaultAutoTypeSequence(QString());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_group->setDefaultAutoTypeSequence(m_mainUi->autoTypeSequenceCustomEdit->text());
|
||||||
|
}
|
||||||
|
|
||||||
IconStruct iconStruct = m_editGroupWidgetIcons->save();
|
IconStruct iconStruct = m_editGroupWidgetIcons->save();
|
||||||
|
|
||||||
if (iconStruct.number < 0) {
|
if (iconStruct.number < 0) {
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>676</width>
|
<width>676</width>
|
||||||
<height>334</height>
|
<height>356</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
@ -36,6 +36,13 @@
|
|||||||
<item row="1" column="1">
|
<item row="1" column="1">
|
||||||
<widget class="QPlainTextEdit" name="editNotes"/>
|
<widget class="QPlainTextEdit" name="editNotes"/>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QCheckBox" name="expireCheck">
|
||||||
|
<property name="text">
|
||||||
|
<string>Expires</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="2" column="1">
|
<item row="2" column="1">
|
||||||
<widget class="QDateTimeEdit" name="expireDatePicker">
|
<widget class="QDateTimeEdit" name="expireDatePicker">
|
||||||
<property name="enabled">
|
<property name="enabled">
|
||||||
@ -46,13 +53,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QCheckBox" name="expireCheck">
|
|
||||||
<property name="text">
|
|
||||||
<string>Expires</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="0">
|
<item row="3" column="0">
|
||||||
<widget class="QLabel" name="searchLabel">
|
<widget class="QLabel" name="searchLabel">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -73,6 +73,47 @@
|
|||||||
<item row="4" column="1">
|
<item row="4" column="1">
|
||||||
<widget class="QComboBox" name="autotypeComboBox"/>
|
<widget class="QComboBox" name="autotypeComboBox"/>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
|
<widget class="QRadioButton" name="autoTypeSequenceInherit">
|
||||||
|
<property name="text">
|
||||||
|
<string>Use default auto-type sequence of parent group</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="1">
|
||||||
|
<widget class="QRadioButton" name="autoTypeSequenceCustomRadio">
|
||||||
|
<property name="text">
|
||||||
|
<string>Set default auto-type sequence</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="1">
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeType">
|
||||||
|
<enum>QSizePolicy::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>1</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="autoTypeSequenceCustomEdit">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
@ -81,34 +81,62 @@ QByteArray CompositeKey::rawKey() const
|
|||||||
return cryptoHash.result();
|
return cryptoHash.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray CompositeKey::transform(const QByteArray& seed, quint64 rounds) const
|
QByteArray CompositeKey::transform(const QByteArray& seed, quint64 rounds,
|
||||||
|
bool* ok, QString* errorString) const
|
||||||
{
|
{
|
||||||
Q_ASSERT(seed.size() == 32);
|
Q_ASSERT(seed.size() == 32);
|
||||||
Q_ASSERT(rounds > 0);
|
Q_ASSERT(rounds > 0);
|
||||||
|
|
||||||
|
bool okLeft;
|
||||||
|
QString errorStringLeft;
|
||||||
|
bool okRight;
|
||||||
|
QString errorStringRight;
|
||||||
|
|
||||||
QByteArray key = rawKey();
|
QByteArray key = rawKey();
|
||||||
|
|
||||||
QFuture<QByteArray> future = QtConcurrent::run(transformKeyRaw, key.left(16), seed, rounds);
|
QFuture<QByteArray> future = QtConcurrent::run(transformKeyRaw, key.left(16), seed, rounds, &okLeft, &errorStringLeft);
|
||||||
QByteArray result2 = transformKeyRaw(key.right(16), seed, rounds);
|
QByteArray result2 = transformKeyRaw(key.right(16), seed, rounds, &okRight, &errorStringRight);
|
||||||
|
|
||||||
QByteArray transformed;
|
QByteArray transformed;
|
||||||
transformed.append(future.result());
|
transformed.append(future.result());
|
||||||
transformed.append(result2);
|
transformed.append(result2);
|
||||||
|
|
||||||
|
*ok = (okLeft && okRight);
|
||||||
|
|
||||||
|
if (!okLeft) {
|
||||||
|
*errorString = errorStringLeft;
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!okRight) {
|
||||||
|
*errorString = errorStringRight;
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
return CryptoHash::hash(transformed, CryptoHash::Sha256);
|
return CryptoHash::hash(transformed, CryptoHash::Sha256);
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray CompositeKey::transformKeyRaw(const QByteArray& key, const QByteArray& seed,
|
QByteArray CompositeKey::transformKeyRaw(const QByteArray& key, const QByteArray& seed,
|
||||||
quint64 rounds)
|
quint64 rounds, bool* ok, QString* errorString)
|
||||||
{
|
{
|
||||||
QByteArray iv(16, 0);
|
QByteArray iv(16, 0);
|
||||||
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb,
|
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb,
|
||||||
SymmetricCipher::Encrypt, seed, iv);
|
SymmetricCipher::Encrypt);
|
||||||
|
if (!cipher.init(seed, iv)) {
|
||||||
|
*ok = false;
|
||||||
|
*errorString = cipher.errorString();
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray result = key;
|
QByteArray result = key;
|
||||||
|
|
||||||
cipher.processInPlace(result, rounds);
|
if (!cipher.processInPlace(result, rounds)) {
|
||||||
|
*ok = false;
|
||||||
|
*errorString = cipher.errorString();
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
*ok = true;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,13 +179,17 @@ void TransformKeyBenchmarkThread::run()
|
|||||||
QByteArray iv(16, 0);
|
QByteArray iv(16, 0);
|
||||||
|
|
||||||
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb,
|
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb,
|
||||||
SymmetricCipher::Encrypt, seed, iv);
|
SymmetricCipher::Encrypt);
|
||||||
|
cipher.init(seed, iv);
|
||||||
|
|
||||||
QTime t;
|
QTime t;
|
||||||
t.start();
|
t.start();
|
||||||
|
|
||||||
do {
|
do {
|
||||||
cipher.processInPlace(key, 100);
|
if (!cipher.processInPlace(key, 100)) {
|
||||||
|
m_rounds = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
m_rounds += 100;
|
m_rounds += 100;
|
||||||
} while (t.elapsed() < m_msec);
|
} while (t.elapsed() < m_msec);
|
||||||
}
|
}
|
||||||
|
@ -34,14 +34,15 @@ public:
|
|||||||
CompositeKey& operator=(const CompositeKey& key);
|
CompositeKey& operator=(const CompositeKey& key);
|
||||||
|
|
||||||
QByteArray rawKey() const;
|
QByteArray rawKey() const;
|
||||||
QByteArray transform(const QByteArray& seed, quint64 rounds) const;
|
QByteArray transform(const QByteArray& seed, quint64 rounds,
|
||||||
|
bool* ok, QString* errorString) const;
|
||||||
void addKey(const Key& key);
|
void addKey(const Key& key);
|
||||||
|
|
||||||
static int transformKeyBenchmark(int msec);
|
static int transformKeyBenchmark(int msec);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static QByteArray transformKeyRaw(const QByteArray& key, const QByteArray& seed,
|
static QByteArray transformKeyRaw(const QByteArray& key, const QByteArray& seed,
|
||||||
quint64 rounds);
|
quint64 rounds, bool* ok, QString* errorString);
|
||||||
|
|
||||||
QList<Key*> m_keys;
|
QList<Key*> m_keys;
|
||||||
};
|
};
|
||||||
|
@ -150,6 +150,7 @@ bool HashedBlockStream::readHashedBlock()
|
|||||||
if (m_blockSize == 0) {
|
if (m_blockSize == 0) {
|
||||||
if (hash.count('\0') != 32) {
|
if (hash.count('\0') != 32) {
|
||||||
m_error = true;
|
m_error = true;
|
||||||
|
setErrorString("Invalid hash of final block.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,6 +167,7 @@ bool HashedBlockStream::readHashedBlock()
|
|||||||
|
|
||||||
if (hash != CryptoHash::hash(m_buffer, CryptoHash::Sha256)) {
|
if (hash != CryptoHash::hash(m_buffer, CryptoHash::Sha256)) {
|
||||||
m_error = true;
|
m_error = true;
|
||||||
|
setErrorString("Mismatch between hash and data.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,6 +215,7 @@ bool HashedBlockStream::writeHashedBlock()
|
|||||||
{
|
{
|
||||||
if (!Endian::writeInt32(m_blockIndex, m_baseDevice, ByteOrder)) {
|
if (!Endian::writeInt32(m_blockIndex, m_baseDevice, ByteOrder)) {
|
||||||
m_error = true;
|
m_error = true;
|
||||||
|
setErrorString(m_baseDevice->errorString());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
m_blockIndex++;
|
m_blockIndex++;
|
||||||
@ -227,17 +230,20 @@ bool HashedBlockStream::writeHashedBlock()
|
|||||||
|
|
||||||
if (m_baseDevice->write(hash) != hash.size()) {
|
if (m_baseDevice->write(hash) != hash.size()) {
|
||||||
m_error = true;
|
m_error = true;
|
||||||
|
setErrorString(m_baseDevice->errorString());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Endian::writeInt32(m_buffer.size(), m_baseDevice, ByteOrder)) {
|
if (!Endian::writeInt32(m_buffer.size(), m_baseDevice, ByteOrder)) {
|
||||||
m_error = true;
|
m_error = true;
|
||||||
|
setErrorString(m_baseDevice->errorString());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_buffer.isEmpty()) {
|
if (!m_buffer.isEmpty()) {
|
||||||
if (m_baseDevice->write(m_buffer) != m_buffer.size()) {
|
if (m_baseDevice->write(m_buffer) != m_buffer.size()) {
|
||||||
m_error = true;
|
m_error = true;
|
||||||
|
setErrorString(m_baseDevice->errorString());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,11 +34,6 @@ bool LayeredStream::isSequential() const
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString LayeredStream::errorString() const
|
|
||||||
{
|
|
||||||
return m_baseDevice->errorString();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool LayeredStream::open(QIODevice::OpenMode mode)
|
bool LayeredStream::open(QIODevice::OpenMode mode)
|
||||||
{
|
{
|
||||||
if (isOpen()) {
|
if (isOpen()) {
|
||||||
|
@ -31,7 +31,6 @@ public:
|
|||||||
virtual ~LayeredStream();
|
virtual ~LayeredStream();
|
||||||
|
|
||||||
bool isSequential() const Q_DECL_OVERRIDE;
|
bool isSequential() const Q_DECL_OVERRIDE;
|
||||||
virtual QString errorString() const;
|
|
||||||
bool open(QIODevice::OpenMode mode) Q_DECL_OVERRIDE;
|
bool open(QIODevice::OpenMode mode) Q_DECL_OVERRIDE;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -41,6 +41,10 @@ QByteArray StoreDataStream::storedData() const
|
|||||||
qint64 StoreDataStream::readData(char* data, qint64 maxSize)
|
qint64 StoreDataStream::readData(char* data, qint64 maxSize)
|
||||||
{
|
{
|
||||||
qint64 bytesRead = LayeredStream::readData(data, maxSize);
|
qint64 bytesRead = LayeredStream::readData(data, maxSize);
|
||||||
|
if (bytesRead == -1) {
|
||||||
|
setErrorString(m_baseDevice->errorString());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
m_storedData.append(data, bytesRead);
|
m_storedData.append(data, bytesRead);
|
||||||
|
|
||||||
|
@ -18,13 +18,13 @@
|
|||||||
#include "SymmetricCipherStream.h"
|
#include "SymmetricCipherStream.h"
|
||||||
|
|
||||||
SymmetricCipherStream::SymmetricCipherStream(QIODevice* baseDevice, SymmetricCipher::Algorithm algo,
|
SymmetricCipherStream::SymmetricCipherStream(QIODevice* baseDevice, SymmetricCipher::Algorithm algo,
|
||||||
SymmetricCipher::Mode mode, SymmetricCipher::Direction direction,
|
SymmetricCipher::Mode mode, SymmetricCipher::Direction direction)
|
||||||
const QByteArray& key, const QByteArray& iv)
|
|
||||||
: LayeredStream(baseDevice)
|
: LayeredStream(baseDevice)
|
||||||
, m_cipher(new SymmetricCipher(algo, mode, direction, key, iv))
|
, m_cipher(new SymmetricCipher(algo, mode, direction))
|
||||||
, m_bufferPos(0)
|
, m_bufferPos(0)
|
||||||
, m_bufferFilling(false)
|
, m_bufferFilling(false)
|
||||||
, m_error(false)
|
, m_error(false)
|
||||||
|
, m_isInitalized(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,6 +33,25 @@ SymmetricCipherStream::~SymmetricCipherStream()
|
|||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SymmetricCipherStream::init(const QByteArray& key, const QByteArray& iv)
|
||||||
|
{
|
||||||
|
m_isInitalized = m_cipher->init(key, iv);
|
||||||
|
if (!m_isInitalized) {
|
||||||
|
setErrorString(m_cipher->errorString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_isInitalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SymmetricCipherStream::open(QIODevice::OpenMode mode)
|
||||||
|
{
|
||||||
|
if (!m_isInitalized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return LayeredStream::open(mode);
|
||||||
|
}
|
||||||
|
|
||||||
bool SymmetricCipherStream::reset()
|
bool SymmetricCipherStream::reset()
|
||||||
{
|
{
|
||||||
if (isWritable()) {
|
if (isWritable()) {
|
||||||
@ -108,7 +127,11 @@ bool SymmetricCipherStream::readBlock()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
m_cipher->processInPlace(m_buffer);
|
if (!m_cipher->processInPlace(m_buffer)) {
|
||||||
|
m_error = true;
|
||||||
|
setErrorString(m_cipher->errorString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
m_bufferPos = 0;
|
m_bufferPos = 0;
|
||||||
m_bufferFilling = false;
|
m_bufferFilling = false;
|
||||||
|
|
||||||
@ -125,6 +148,7 @@ bool SymmetricCipherStream::readBlock()
|
|||||||
else if (padLength > m_cipher->blockSize()) {
|
else if (padLength > m_cipher->blockSize()) {
|
||||||
// invalid padding
|
// invalid padding
|
||||||
m_error = true;
|
m_error = true;
|
||||||
|
setErrorString("Invalid padding.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -187,11 +211,15 @@ bool SymmetricCipherStream::writeBlock(bool lastBlock)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_cipher->processInPlace(m_buffer);
|
if (!m_cipher->processInPlace(m_buffer)) {
|
||||||
|
m_error = true;
|
||||||
|
setErrorString(m_cipher->errorString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (m_baseDevice->write(m_buffer) != m_buffer.size()) {
|
if (m_baseDevice->write(m_buffer) != m_buffer.size()) {
|
||||||
m_error = true;
|
m_error = true;
|
||||||
// TODO: copy error string
|
setErrorString(m_cipher->errorString());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -29,11 +29,13 @@ class SymmetricCipherStream : public LayeredStream
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SymmetricCipherStream(QIODevice* baseDevice, SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
SymmetricCipherStream(QIODevice* baseDevice, SymmetricCipher::Algorithm algo,
|
||||||
SymmetricCipher::Direction direction, const QByteArray& key, const QByteArray& iv);
|
SymmetricCipher::Mode mode, SymmetricCipher::Direction direction);
|
||||||
~SymmetricCipherStream();
|
~SymmetricCipherStream();
|
||||||
bool reset();
|
bool init(const QByteArray& key, const QByteArray& iv);
|
||||||
void close();
|
bool open(QIODevice::OpenMode mode) Q_DECL_OVERRIDE;
|
||||||
|
bool reset() Q_DECL_OVERRIDE;
|
||||||
|
void close() Q_DECL_OVERRIDE;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
qint64 readData(char* data, qint64 maxSize) Q_DECL_OVERRIDE;
|
qint64 readData(char* data, qint64 maxSize) Q_DECL_OVERRIDE;
|
||||||
@ -48,6 +50,7 @@ private:
|
|||||||
int m_bufferPos;
|
int m_bufferPos;
|
||||||
bool m_bufferFilling;
|
bool m_bufferFilling;
|
||||||
bool m_error;
|
bool m_error;
|
||||||
|
bool m_isInitalized;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // KEEPASSX_SYMMETRICCIPHERSTREAM_H
|
#endif // KEEPASSX_SYMMETRICCIPHERSTREAM_H
|
||||||
|
@ -36,15 +36,15 @@ void TestHashedBlockStream::testWriteRead()
|
|||||||
QByteArray data = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
|
QByteArray data = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
|
||||||
|
|
||||||
QBuffer buffer;
|
QBuffer buffer;
|
||||||
buffer.open(QIODevice::ReadWrite);
|
QVERIFY(buffer.open(QIODevice::ReadWrite));
|
||||||
|
|
||||||
HashedBlockStream writer(&buffer, 16);
|
HashedBlockStream writer(&buffer, 16);
|
||||||
writer.open(QIODevice::WriteOnly);
|
QVERIFY(writer.open(QIODevice::WriteOnly));
|
||||||
|
|
||||||
HashedBlockStream reader(&buffer);
|
HashedBlockStream reader(&buffer);
|
||||||
reader.open(QIODevice::ReadOnly);
|
QVERIFY(reader.open(QIODevice::ReadOnly));
|
||||||
|
|
||||||
writer.write(data.left(16));
|
QCOMPARE(writer.write(data.left(16)), qint64(16));
|
||||||
QVERIFY(writer.reset());
|
QVERIFY(writer.reset());
|
||||||
buffer.reset();
|
buffer.reset();
|
||||||
QCOMPARE(reader.read(17), data.left(16));
|
QCOMPARE(reader.read(17), data.left(16));
|
||||||
@ -52,7 +52,7 @@ void TestHashedBlockStream::testWriteRead()
|
|||||||
buffer.reset();
|
buffer.reset();
|
||||||
buffer.buffer().clear();
|
buffer.buffer().clear();
|
||||||
|
|
||||||
writer.write(data.left(10));
|
QCOMPARE(writer.write(data.left(10)), qint64(10));
|
||||||
QVERIFY(writer.reset());
|
QVERIFY(writer.reset());
|
||||||
buffer.reset();
|
buffer.reset();
|
||||||
QCOMPARE(reader.read(5), data.left(5));
|
QCOMPARE(reader.read(5), data.left(5));
|
||||||
@ -62,7 +62,7 @@ void TestHashedBlockStream::testWriteRead()
|
|||||||
buffer.reset();
|
buffer.reset();
|
||||||
buffer.buffer().clear();
|
buffer.buffer().clear();
|
||||||
|
|
||||||
writer.write(data.left(20));
|
QCOMPARE(writer.write(data.left(20)), qint64(20));
|
||||||
QVERIFY(writer.reset());
|
QVERIFY(writer.reset());
|
||||||
buffer.reset();
|
buffer.reset();
|
||||||
QCOMPARE(reader.read(20), data.left(20));
|
QCOMPARE(reader.read(20), data.left(20));
|
||||||
|
@ -39,8 +39,8 @@ void TestKeePass2RandomStream::test()
|
|||||||
const int Size = 128;
|
const int Size = 128;
|
||||||
|
|
||||||
|
|
||||||
SymmetricCipher cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt,
|
SymmetricCipher cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt);
|
||||||
CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV);
|
QVERIFY(cipher.init(CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV));
|
||||||
|
|
||||||
const QByteArray data(QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c5"
|
const QByteArray data(QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c5"
|
||||||
"2b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6"
|
"2b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6"
|
||||||
@ -49,7 +49,7 @@ void TestKeePass2RandomStream::test()
|
|||||||
|
|
||||||
QByteArray cipherPad;
|
QByteArray cipherPad;
|
||||||
cipherPad.fill('\0', Size);
|
cipherPad.fill('\0', Size);
|
||||||
cipher.processInPlace(cipherPad);
|
QVERIFY(cipher.processInPlace(cipherPad));
|
||||||
|
|
||||||
QByteArray cipherData;
|
QByteArray cipherData;
|
||||||
cipherData.resize(Size);
|
cipherData.resize(Size);
|
||||||
@ -59,20 +59,27 @@ void TestKeePass2RandomStream::test()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
KeePass2RandomStream randomStream(key);
|
KeePass2RandomStream randomStream;
|
||||||
|
bool ok;
|
||||||
|
QVERIFY(randomStream.init(key));
|
||||||
QByteArray randomStreamData;
|
QByteArray randomStreamData;
|
||||||
randomStreamData.append(randomStream.process(data.mid(0, 7)));
|
randomStreamData.append(randomStream.process(data.mid(0, 7), &ok));
|
||||||
randomStreamData.append(randomStream.process(data.mid(7, 1)));
|
QVERIFY(ok);
|
||||||
|
randomStreamData.append(randomStream.process(data.mid(7, 1), &ok));
|
||||||
|
QVERIFY(ok);
|
||||||
QByteArray tmpData = data.mid(8, 12);
|
QByteArray tmpData = data.mid(8, 12);
|
||||||
randomStream.processInPlace(tmpData);
|
QVERIFY(randomStream.processInPlace(tmpData));
|
||||||
randomStreamData.append(tmpData);
|
randomStreamData.append(tmpData);
|
||||||
randomStreamData.append(randomStream.process(data.mid(20, 44)));
|
randomStreamData.append(randomStream.process(data.mid(20, 44), &ok));
|
||||||
randomStreamData.append(randomStream.process(data.mid(64, 64)));
|
QVERIFY(ok);
|
||||||
|
randomStreamData.append(randomStream.process(data.mid(64, 64), &ok));
|
||||||
|
QVERIFY(ok);
|
||||||
|
|
||||||
|
|
||||||
SymmetricCipher cipherEncrypt(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt,
|
SymmetricCipher cipherEncrypt(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt);
|
||||||
CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV);
|
QVERIFY(cipherEncrypt.init(CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV));
|
||||||
QByteArray cipherDataEncrypt = cipherEncrypt.process(data);
|
QByteArray cipherDataEncrypt = cipherEncrypt.process(data, &ok);
|
||||||
|
QVERIFY(ok);
|
||||||
|
|
||||||
|
|
||||||
QCOMPARE(randomStreamData.size(), Size);
|
QCOMPARE(randomStreamData.size(), Size);
|
||||||
|
@ -43,6 +43,8 @@ void TestKeys::testComposite()
|
|||||||
CompositeKey* compositeKey1 = new CompositeKey();
|
CompositeKey* compositeKey1 = new CompositeKey();
|
||||||
PasswordKey* passwordKey1 = new PasswordKey();
|
PasswordKey* passwordKey1 = new PasswordKey();
|
||||||
PasswordKey* passwordKey2 = new PasswordKey("test");
|
PasswordKey* passwordKey2 = new PasswordKey("test");
|
||||||
|
bool ok;
|
||||||
|
QString errorString;
|
||||||
|
|
||||||
// make sure that addKey() creates a copy of the keys
|
// make sure that addKey() creates a copy of the keys
|
||||||
compositeKey1->addKey(*passwordKey1);
|
compositeKey1->addKey(*passwordKey1);
|
||||||
@ -50,13 +52,15 @@ void TestKeys::testComposite()
|
|||||||
delete passwordKey1;
|
delete passwordKey1;
|
||||||
delete passwordKey2;
|
delete passwordKey2;
|
||||||
|
|
||||||
QByteArray transformed = compositeKey1->transform(QByteArray(32, '\0'), 1);
|
QByteArray transformed = compositeKey1->transform(QByteArray(32, '\0'), 1, &ok, &errorString);
|
||||||
|
QVERIFY(ok);
|
||||||
QCOMPARE(transformed.size(), 32);
|
QCOMPARE(transformed.size(), 32);
|
||||||
|
|
||||||
// make sure the subkeys are copied
|
// make sure the subkeys are copied
|
||||||
CompositeKey* compositeKey2 = compositeKey1->clone();
|
CompositeKey* compositeKey2 = compositeKey1->clone();
|
||||||
delete compositeKey1;
|
delete compositeKey1;
|
||||||
QCOMPARE(compositeKey2->transform(QByteArray(32, '\0'), 1), transformed);
|
QCOMPARE(compositeKey2->transform(QByteArray(32, '\0'), 1, &ok, &errorString), transformed);
|
||||||
|
QVERIFY(ok);
|
||||||
delete compositeKey2;
|
delete compositeKey2;
|
||||||
|
|
||||||
CompositeKey* compositeKey3 = new CompositeKey();
|
CompositeKey* compositeKey3 = new CompositeKey();
|
||||||
@ -130,7 +134,7 @@ void TestKeys::testCreateFileKey()
|
|||||||
compositeKey.addKey(fileKey);
|
compositeKey.addKey(fileKey);
|
||||||
|
|
||||||
Database* dbOrg = new Database();
|
Database* dbOrg = new Database();
|
||||||
dbOrg->setKey(compositeKey);
|
QVERIFY(dbOrg->setKey(compositeKey));
|
||||||
dbOrg->metadata()->setName(dbName);
|
dbOrg->metadata()->setName(dbName);
|
||||||
|
|
||||||
QBuffer dbBuffer;
|
QBuffer dbBuffer;
|
||||||
@ -182,7 +186,10 @@ void TestKeys::benchmarkTransformKey()
|
|||||||
|
|
||||||
QByteArray seed(32, '\x4B');
|
QByteArray seed(32, '\x4B');
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
QString errorString;
|
||||||
|
|
||||||
QBENCHMARK {
|
QBENCHMARK {
|
||||||
compositeKey.transform(seed, 1e6);
|
compositeKey.transform(seed, 1e6, &ok, &errorString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,24 +42,27 @@ void TestSymmetricCipher::testAes256CbcEncryption()
|
|||||||
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
||||||
QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6");
|
QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6");
|
||||||
cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d"));
|
cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d"));
|
||||||
|
bool ok;
|
||||||
|
|
||||||
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt,
|
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
|
||||||
key, iv);
|
QVERIFY(cipher.init(key, iv));
|
||||||
QCOMPARE(cipher.blockSize(), 16);
|
QCOMPARE(cipher.blockSize(), 16);
|
||||||
|
|
||||||
QCOMPARE(cipher.process(plainText),
|
QCOMPARE(cipher.process(plainText, &ok),
|
||||||
cipherText);
|
cipherText);
|
||||||
|
QVERIFY(ok);
|
||||||
|
|
||||||
QBuffer buffer;
|
QBuffer buffer;
|
||||||
SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
||||||
SymmetricCipher::Encrypt, key, iv);
|
SymmetricCipher::Encrypt);
|
||||||
|
QVERIFY(stream.init(key, iv));
|
||||||
buffer.open(QIODevice::WriteOnly);
|
buffer.open(QIODevice::WriteOnly);
|
||||||
stream.open(QIODevice::WriteOnly);
|
QVERIFY(stream.open(QIODevice::WriteOnly));
|
||||||
QVERIFY(stream.reset());
|
QVERIFY(stream.reset());
|
||||||
|
|
||||||
buffer.reset();
|
buffer.reset();
|
||||||
buffer.buffer().clear();
|
buffer.buffer().clear();
|
||||||
stream.write(plainText.left(16));
|
QCOMPARE(stream.write(plainText.left(16)), qint64(16));
|
||||||
QCOMPARE(buffer.data(), cipherText.left(16));
|
QCOMPARE(buffer.data(), cipherText.left(16));
|
||||||
QVERIFY(stream.reset());
|
QVERIFY(stream.reset());
|
||||||
// make sure padding is written
|
// make sure padding is written
|
||||||
@ -67,13 +70,13 @@ void TestSymmetricCipher::testAes256CbcEncryption()
|
|||||||
|
|
||||||
buffer.reset();
|
buffer.reset();
|
||||||
buffer.buffer().clear();
|
buffer.buffer().clear();
|
||||||
stream.write(plainText.left(10));
|
QCOMPARE(stream.write(plainText.left(10)), qint64(10));
|
||||||
QVERIFY(buffer.data().isEmpty());
|
QVERIFY(buffer.data().isEmpty());
|
||||||
|
|
||||||
QVERIFY(stream.reset());
|
QVERIFY(stream.reset());
|
||||||
buffer.reset();
|
buffer.reset();
|
||||||
buffer.buffer().clear();
|
buffer.buffer().clear();
|
||||||
stream.write(plainText.left(10));
|
QCOMPARE(stream.write(plainText.left(10)), qint64(10));
|
||||||
stream.close();
|
stream.close();
|
||||||
QCOMPARE(buffer.data().size(), 16);
|
QCOMPARE(buffer.data().size(), 16);
|
||||||
}
|
}
|
||||||
@ -86,33 +89,37 @@ void TestSymmetricCipher::testAes256CbcDecryption()
|
|||||||
cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d"));
|
cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d"));
|
||||||
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
|
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
|
||||||
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
||||||
|
bool ok;
|
||||||
|
|
||||||
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt, key, iv);
|
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||||
|
QVERIFY(cipher.init(key, iv));
|
||||||
QCOMPARE(cipher.blockSize(), 16);
|
QCOMPARE(cipher.blockSize(), 16);
|
||||||
|
|
||||||
QCOMPARE(cipher.process(cipherText),
|
QCOMPARE(cipher.process(cipherText, &ok),
|
||||||
plainText);
|
plainText);
|
||||||
|
QVERIFY(ok);
|
||||||
|
|
||||||
// padded with 16 0x16 bytes
|
// padded with 16 0x16 bytes
|
||||||
QByteArray cipherTextPadded = cipherText + QByteArray::fromHex("3a3aa5e0213db1a9901f9036cf5102d2");
|
QByteArray cipherTextPadded = cipherText + QByteArray::fromHex("3a3aa5e0213db1a9901f9036cf5102d2");
|
||||||
QBuffer buffer(&cipherTextPadded);
|
QBuffer buffer(&cipherTextPadded);
|
||||||
SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
||||||
SymmetricCipher::Decrypt, key, iv);
|
SymmetricCipher::Decrypt);
|
||||||
|
QVERIFY(stream.init(key, iv));
|
||||||
buffer.open(QIODevice::ReadOnly);
|
buffer.open(QIODevice::ReadOnly);
|
||||||
stream.open(QIODevice::ReadOnly);
|
QVERIFY(stream.open(QIODevice::ReadOnly));
|
||||||
|
|
||||||
QCOMPARE(stream.read(10),
|
QCOMPARE(stream.read(10),
|
||||||
plainText.left(10));
|
plainText.left(10));
|
||||||
buffer.reset();
|
buffer.reset();
|
||||||
stream.reset();
|
QVERIFY(stream.reset());
|
||||||
QCOMPARE(stream.read(20),
|
QCOMPARE(stream.read(20),
|
||||||
plainText.left(20));
|
plainText.left(20));
|
||||||
buffer.reset();
|
buffer.reset();
|
||||||
stream.reset();
|
QVERIFY(stream.reset());
|
||||||
QCOMPARE(stream.read(16),
|
QCOMPARE(stream.read(16),
|
||||||
plainText.left(16));
|
plainText.left(16));
|
||||||
buffer.reset();
|
buffer.reset();
|
||||||
stream.reset();
|
QVERIFY(stream.reset());
|
||||||
QCOMPARE(stream.read(100),
|
QCOMPARE(stream.read(100),
|
||||||
plainText);
|
plainText);
|
||||||
}
|
}
|
||||||
@ -123,16 +130,20 @@ void TestSymmetricCipher::testSalsa20()
|
|||||||
|
|
||||||
QByteArray key = QByteArray::fromHex("F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112");
|
QByteArray key = QByteArray::fromHex("F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112");
|
||||||
QByteArray iv = QByteArray::fromHex("0000000000000000");
|
QByteArray iv = QByteArray::fromHex("0000000000000000");
|
||||||
|
bool ok;
|
||||||
|
|
||||||
SymmetricCipher cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt, key, iv);
|
SymmetricCipher cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt);
|
||||||
|
QVERIFY(cipher.init(key, iv));
|
||||||
|
|
||||||
QByteArray cipherTextA;
|
QByteArray cipherTextA;
|
||||||
for (int i = 0; i < 8; i++) {
|
for (int i = 0; i < 8; i++) {
|
||||||
cipherTextA.append(cipher.process(QByteArray(64, '\0')));
|
cipherTextA.append(cipher.process(QByteArray(64, '\0'), &ok));
|
||||||
|
QVERIFY(ok);
|
||||||
}
|
}
|
||||||
cipher.reset();
|
cipher.reset();
|
||||||
|
|
||||||
QByteArray cipherTextB = cipher.process(QByteArray(512, '\0'));
|
QByteArray cipherTextB = cipher.process(QByteArray(512, '\0'), &ok);
|
||||||
|
QVERIFY(ok);
|
||||||
cipher.reset();
|
cipher.reset();
|
||||||
|
|
||||||
QByteArray expectedCipherText1;
|
QByteArray expectedCipherText1;
|
||||||
@ -180,7 +191,8 @@ void TestSymmetricCipher::testPadding()
|
|||||||
buffer.open(QIODevice::ReadWrite);
|
buffer.open(QIODevice::ReadWrite);
|
||||||
|
|
||||||
SymmetricCipherStream streamEnc(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
SymmetricCipherStream streamEnc(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
||||||
SymmetricCipher::Encrypt, key, iv);
|
SymmetricCipher::Encrypt);
|
||||||
|
QVERIFY(streamEnc.init(key, iv));
|
||||||
streamEnc.open(QIODevice::WriteOnly);
|
streamEnc.open(QIODevice::WriteOnly);
|
||||||
streamEnc.write(plainText);
|
streamEnc.write(plainText);
|
||||||
streamEnc.close();
|
streamEnc.close();
|
||||||
@ -189,7 +201,8 @@ void TestSymmetricCipher::testPadding()
|
|||||||
QCOMPARE(buffer.buffer().size(), 16);
|
QCOMPARE(buffer.buffer().size(), 16);
|
||||||
|
|
||||||
SymmetricCipherStream streamDec(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
SymmetricCipherStream streamDec(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
||||||
SymmetricCipher::Decrypt, key, iv);
|
SymmetricCipher::Decrypt);
|
||||||
|
QVERIFY(streamDec.init(key, iv));
|
||||||
streamDec.open(QIODevice::ReadOnly);
|
streamDec.open(QIODevice::ReadOnly);
|
||||||
QByteArray decrypted = streamDec.readAll();
|
QByteArray decrypted = streamDec.readAll();
|
||||||
QCOMPARE(decrypted, plainText);
|
QCOMPARE(decrypted, plainText);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user