mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-01-26 14:36:07 -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:
|
||||
- cmake -DCMAKE_BUILD_TYPE=Debug -DWITH_GUI_TESTS=ON ..
|
||||
- make
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then make test ARGS+="-E testgui"; 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 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 --output-on-failure"; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then make test; fi
|
||||
|
@ -149,9 +149,9 @@ elseif(APPLE)
|
||||
else()
|
||||
include(GNUInstallDirs)
|
||||
|
||||
set(BIN_INSTALL_DIR "${CMAKE_INSTALL_FULL_BINDIR}")
|
||||
set(PLUGIN_INSTALL_DIR "${CMAKE_INSTALL_FULL_LIBDIR}/keepassx")
|
||||
set(DATA_INSTALL_DIR "${CMAKE_INSTALL_FULL_DATADIR}/keepassx")
|
||||
set(BIN_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}")
|
||||
set(PLUGIN_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/keepassx")
|
||||
set(DATA_INSTALL_DIR "${CMAKE_INSTALL_DATADIR}/keepassx")
|
||||
endif()
|
||||
|
||||
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>
|
||||
</message>
|
||||
<message>
|
||||
<source>Couldn't find an entry that matches the window title.</source>
|
||||
<translation>Konnte dem Fenstertitel keinen passenden Eintrag zuordnen.</translation>
|
||||
<source>Couldn't find an entry that matches the window title:</source>
|
||||
<translation>Konnte keinen Eintrag finden, welcher mit dem Fenstertitel übereinstimmt:</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -138,7 +138,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<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>
|
||||
<source>All files</source>
|
||||
@ -185,7 +185,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Max. history items:</source>
|
||||
<translation>Max Einträge im Verlauf:</translation>
|
||||
<translation>Max. Einträge im Verlauf:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Max. history size:</source>
|
||||
@ -739,6 +739,17 @@ Save changes?</source>
|
||||
<translation>Falscher Schlüssel oder die Datei ist beschädigt.</translation>
|
||||
</message>
|
||||
</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>
|
||||
<name>MainWindow</name>
|
||||
<message>
|
||||
@ -897,6 +908,10 @@ Save changes?</source>
|
||||
<source>read-only</source>
|
||||
<translation>Nur Lesezugriff</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Toggle window</source>
|
||||
<translation>Fenster zeigen/verstecken</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PasswordGeneratorWidget</name>
|
||||
@ -1105,6 +1120,18 @@ Save changes?</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>
|
||||
</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>
|
||||
<name>SettingsWidgetSecurity</name>
|
||||
|
@ -111,6 +111,15 @@
|
||||
<source>Different passwords supplied.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</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>
|
||||
<name>DatabaseOpenWidget</name>
|
||||
@ -270,6 +279,31 @@ Save changes?</source>
|
||||
<source>locked</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</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>
|
||||
<name>DatabaseWidget</name>
|
||||
@ -316,6 +350,14 @@ Save changes?</source>
|
||||
<source>Current group</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</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>
|
||||
<name>EditEntryWidget</name>
|
||||
@ -433,6 +475,10 @@ Save changes?</source>
|
||||
<source>Save</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Open</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>EditEntryWidgetAutoType</name>
|
||||
@ -584,6 +630,14 @@ Save changes?</source>
|
||||
<source>Auto-type</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</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>
|
||||
<name>EditWidgetIcons</name>
|
||||
@ -735,6 +789,10 @@ Save changes?</source>
|
||||
<source>Root</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unable to calculate master key</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>KeePass2Reader</name>
|
||||
@ -750,6 +808,10 @@ Save changes?</source>
|
||||
<source>Wrong key or database file is corrupt.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unable to calculate master key</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Main</name>
|
||||
@ -788,10 +850,6 @@ Save changes?</source>
|
||||
<source>Groups</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Extras</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>View</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -924,6 +982,10 @@ Save changes?</source>
|
||||
<source>Toggle window</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Tools</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PasswordGeneratorWidget</name>
|
||||
@ -1104,10 +1166,6 @@ Save changes?</source>
|
||||
<source>Open previous databases on startup</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Mark as modified on expanded state changes</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Automatically save on exit</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@ -1144,6 +1202,10 @@ Save changes?</source>
|
||||
<source>Hide window to system tray when minimized</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remember last key files</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsWidgetSecurity</name>
|
||||
@ -1174,14 +1236,6 @@ Save changes?</source>
|
||||
<source>Unlock database</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Wrong key.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<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>
|
||||
</message>
|
||||
<message>
|
||||
<source>Couldn't find an entry that matches the window title.</source>
|
||||
<translation>Kon geen element vinden dat overeenkomt met de venstertitel.</translation>
|
||||
<source>Couldn't find an entry that matches the window title:</source>
|
||||
<translation>Kon geen element vinden dat overeenkomt met de venstertitel:</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -740,6 +740,17 @@ Opslaan?</translation>
|
||||
<translation>Verkeerde sleutel of corrupte database.</translation>
|
||||
</message>
|
||||
</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>
|
||||
<name>MainWindow</name>
|
||||
<message>
|
||||
@ -898,6 +909,10 @@ Opslaan?</translation>
|
||||
<source>read-only</source>
|
||||
<translation>alleen-lezen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Toggle window</source>
|
||||
<translation>Wissel venster</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PasswordGeneratorWidget</name>
|
||||
@ -1106,6 +1121,18 @@ Opslaan?</translation>
|
||||
<source>Use entry title to match windows for global auto-type</source>
|
||||
<translation>Gebruik naam van element als vensternaam voor auto-typen</translation>
|
||||
</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>
|
||||
<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>
|
||||
</message>
|
||||
<message>
|
||||
<source>Couldn't find an entry that matches the window title.</source>
|
||||
<translation>Kunde inte hitta en post som matchar fönstertiteln.</translation>
|
||||
<source>Couldn't find an entry that matches the window title:</source>
|
||||
<translation>Kunde inte hitta en post som matchar fönstertiteln:</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -740,6 +740,17 @@ Spara ändringarna?</translation>
|
||||
<translation>Fel lösenord eller korrupt databas-fil</translation>
|
||||
</message>
|
||||
</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>
|
||||
<name>MainWindow</name>
|
||||
<message>
|
||||
@ -898,6 +909,10 @@ Spara ändringarna?</translation>
|
||||
<source>read-only</source>
|
||||
<translation>läs bara</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Toggle window</source>
|
||||
<translation>Visa/dölj fönster</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PasswordGeneratorWidget</name>
|
||||
@ -1106,6 +1121,18 @@ Spara ändringarna?</translation>
|
||||
<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>
|
||||
</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>
|
||||
<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/../..
|
||||
|
||||
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 -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/Metadata.cpp
|
||||
core/PasswordGenerator.cpp
|
||||
core/qlockfile.cpp
|
||||
core/qsavefile.cpp
|
||||
core/qsavefile_p.h
|
||||
core/SignalMultiplexer.cpp
|
||||
@ -138,6 +139,18 @@ if(NOT GCRYPT_HAS_SALSA20)
|
||||
)
|
||||
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
|
||||
main.cpp
|
||||
)
|
||||
|
@ -89,5 +89,7 @@ void AutoTypeExecutor::execDelay(AutoTypeDelay* action)
|
||||
|
||||
void AutoTypeExecutor::execClearField(AutoTypeClearField* action)
|
||||
{
|
||||
Q_UNUSED(action);
|
||||
|
||||
// TODO: implement
|
||||
}
|
||||
|
@ -655,7 +655,7 @@ void AutoTypePlatformX11::SendKeyPressedEvent(KeySym keysym)
|
||||
int keycode;
|
||||
|
||||
if (keysym == NoSymbol) {
|
||||
qWarning("No such key: keysym=0x%lX", static_cast<long>(keysym));
|
||||
qWarning("No such key: keysym=0x%lX", keysym);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -682,7 +682,7 @@ void AutoTypePlatformX11::SendKeyPressedEvent(KeySym keysym)
|
||||
/* determine keycode and mask for the given keysym */
|
||||
keycode = GetKeycode(keysym, &wanted_mask);
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -8,8 +8,8 @@
|
||||
#define KEEPASSX_SOURCE_DIR "${CMAKE_SOURCE_DIR}"
|
||||
#define KEEPASSX_BINARY_DIR "${CMAKE_BINARY_DIR}"
|
||||
|
||||
#define KEEPASSX_PREFIX_DIR "${CMAKE_INSTALL_PREFIX}"
|
||||
#define KEEPASSX_PLUGIN_DIR "${PLUGIN_INSTALL_DIR}"
|
||||
|
||||
#define KEEPASSX_DATA_DIR "${DATA_INSTALL_DIR}"
|
||||
|
||||
#cmakedefine HAVE_PR_SET_DUMPABLE 1
|
||||
|
@ -188,32 +188,51 @@ void Database::setCompressionAlgo(Database::CompressionAlgorithm algo)
|
||||
m_data.compressionAlgo = algo;
|
||||
}
|
||||
|
||||
void Database::setTransformRounds(quint64 rounds)
|
||||
bool Database::setTransformRounds(quint64 rounds)
|
||||
{
|
||||
if (m_data.transformRounds != rounds) {
|
||||
quint64 oldRounds = m_data.transformRounds;
|
||||
|
||||
m_data.transformRounds = rounds;
|
||||
|
||||
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.transformSeed = transformSeed;
|
||||
m_data.transformedMasterKey = key.transform(transformSeed, transformRounds());
|
||||
m_data.transformedMasterKey = transformedMasterKey;
|
||||
m_data.hasKey = true;
|
||||
if (updateChangedTime) {
|
||||
m_metadata->setMasterKeyChanged(Tools::currentDateTimeUtc());
|
||||
}
|
||||
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
|
||||
|
@ -90,13 +90,14 @@ public:
|
||||
|
||||
void setCipher(const Uuid& cipher);
|
||||
void setCompressionAlgo(Database::CompressionAlgorithm algo);
|
||||
void setTransformRounds(quint64 rounds);
|
||||
void setKey(const CompositeKey& key, const QByteArray& transformSeed, bool updateChangedTime = true);
|
||||
bool setTransformRounds(quint64 rounds);
|
||||
bool setKey(const CompositeKey& key, const QByteArray& transformSeed,
|
||||
bool updateChangedTime = true);
|
||||
|
||||
/**
|
||||
* Sets the database key and generates a random transform seed.
|
||||
*/
|
||||
void setKey(const CompositeKey& key);
|
||||
bool setKey(const CompositeKey& key);
|
||||
bool hasKey() const;
|
||||
bool verifyKey(const CompositeKey& key) const;
|
||||
void recycleEntry(Entry* entry);
|
||||
|
@ -49,13 +49,20 @@ QString FilePath::pluginPath(const QString& name)
|
||||
|
||||
pluginPaths << QCoreApplication::applicationDirPath();
|
||||
|
||||
QString systemPluginDir = KEEPASSX_PLUGIN_DIR;
|
||||
if (systemPluginDir != ".") {
|
||||
if (!QDir(systemPluginDir).isAbsolute()) {
|
||||
systemPluginDir = QCoreApplication::applicationDirPath() + "/../" + systemPluginDir;
|
||||
systemPluginDir = QDir(systemPluginDir).canonicalPath();
|
||||
QString configuredPluginDir = KEEPASSX_PLUGIN_DIR;
|
||||
if (configuredPluginDir != ".") {
|
||||
if (QDir(configuredPluginDir).isAbsolute()) {
|
||||
pluginPaths << configuredPluginDir;
|
||||
}
|
||||
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;
|
||||
@ -164,6 +171,9 @@ QIcon FilePath::onOffIcon(const QString& category, const QString& name)
|
||||
|
||||
FilePath::FilePath()
|
||||
{
|
||||
const QString appDirPath = QCoreApplication::applicationDirPath();
|
||||
bool isDataDirAbsolute = QDir::isAbsolutePath(KEEPASSX_DATA_DIR);
|
||||
|
||||
if (false) {
|
||||
}
|
||||
#ifdef QT_DEBUG
|
||||
@ -171,17 +181,19 @@ FilePath::FilePath()
|
||||
}
|
||||
#endif
|
||||
#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
|
||||
#ifdef Q_OS_MAC
|
||||
else if (testSetDir(QCoreApplication::applicationDirPath() + "/../Resources")) {
|
||||
else if (testSetDir(appDirPath + "/../Resources")) {
|
||||
}
|
||||
#endif
|
||||
#ifdef Q_OS_WIN
|
||||
else if (testSetDir(QCoreApplication::applicationDirPath() + "/share")) {
|
||||
else if (testSetDir(appDirPath + "/share")) {
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -39,10 +39,12 @@
|
||||
|
||||
#include "config-keepassx.h"
|
||||
|
||||
#if defined(HAVE_RLIMIT_CORE)
|
||||
#include <sys/resource.h>
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_PR_SET_DUMPABLE)
|
||||
#include <sys/prctl.h>
|
||||
#elif defined(HAVE_RLIMIT_CORE)
|
||||
#include <sys/resource.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_PT_DENY_ATTACH
|
||||
@ -222,21 +224,23 @@ QString platform()
|
||||
|
||||
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_PR_SET_DUMPABLE)
|
||||
success = (prctl(PR_SET_DUMPABLE, 0) == 0);
|
||||
#elif defined(HAVE_RLIMIT_CORE)
|
||||
#if defined(HAVE_RLIMIT_CORE)
|
||||
struct rlimit limit;
|
||||
limit.rlim_cur = 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
|
||||
|
||||
// Mac OS X
|
||||
#ifdef HAVE_PT_DENY_ATTACH
|
||||
// make sure setrlimit() and ptrace() succeeded
|
||||
success = success && (ptrace(PT_DENY_ATTACH, 0, 0, 0) == 0);
|
||||
#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()
|
||||
{
|
||||
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",
|
||||
CryptoHash::Sha256);
|
||||
|
||||
if (sha256Test != QByteArray::fromHex("248D6A61D20638B8E5C026930C3E6039A33CE45964FF2167F6ECEDD419DB06C1")) {
|
||||
m_errorStr = "SHA-256 mismatch.";
|
||||
qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
|
||||
raiseError("SHA-256 mismatch.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Crypto::testAes256Cbc()
|
||||
{
|
||||
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
|
||||
QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
|
||||
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
|
||||
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
||||
QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6");
|
||||
cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d"));
|
||||
bool ok;
|
||||
|
||||
SymmetricCipher aes256Encrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt, key, iv);
|
||||
if (aes256Encrypt.process(plainText) != cipherText) {
|
||||
m_errorStr = "AES-256 encryption mismatch.";
|
||||
qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
|
||||
SymmetricCipher aes256Encrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, 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 CBC encryption mismatch.");
|
||||
return false;
|
||||
}
|
||||
|
||||
SymmetricCipher aes256Descrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt, key, iv);
|
||||
if (aes256Descrypt.process(cipherText) != plainText) {
|
||||
m_errorStr = "AES-256 decryption mismatch.";
|
||||
qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
|
||||
SymmetricCipher aes256Descrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||
if (!aes256Descrypt.init(key, iv)) {
|
||||
raiseError(aes256Descrypt.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Twofish
|
||||
cipherText = QByteArray::fromHex("e0227c3cc80f3cb1b2ed847cc6f57d3c");
|
||||
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));
|
||||
QByteArray decryptedText = aes256Descrypt.process(cipherText, &ok);
|
||||
if (!ok) {
|
||||
raiseError(aes256Descrypt.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
SymmetricCipher twofishDecrypt(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Decrypt, key, iv);
|
||||
if (twofishDecrypt.process(cipherText) != plainText) {
|
||||
m_errorStr = "Twofish decryption mismatch.";
|
||||
qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray salsa20Key = QByteArray::fromHex("F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112");
|
||||
QByteArray salsa20iv = QByteArray::fromHex("0000000000000000");
|
||||
QByteArray salsa20Plain = QByteArray::fromHex("00000000000000000000000000000000");
|
||||
QByteArray salsa20Cipher = QByteArray::fromHex("B4C0AFA503BE7FC29A62058166D56F8F");
|
||||
|
||||
SymmetricCipher salsa20Stream(SymmetricCipher::Salsa20, SymmetricCipher::Stream,
|
||||
SymmetricCipher::Encrypt, salsa20Key, salsa20iv);
|
||||
|
||||
if (salsa20Stream.process(salsa20Plain) != salsa20Cipher) {
|
||||
m_errorStr = "Salsa20 stream cipher mismatch.";
|
||||
qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
|
||||
if (decryptedText != plainText) {
|
||||
raiseError("AES-256 CBC decryption mismatch.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Crypto::testAes256Ecb()
|
||||
{
|
||||
QByteArray key = QByteArray::fromHex("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
|
||||
QByteArray iv = QByteArray::fromHex("00000000000000000000000000000000");
|
||||
QByteArray plainText = QByteArray::fromHex("00112233445566778899AABBCCDDEEFF");
|
||||
plainText.append(QByteArray::fromHex("00112233445566778899AABBCCDDEEFF"));
|
||||
QByteArray cipherText = QByteArray::fromHex("8EA2B7CA516745BFEAFC49904B496089");
|
||||
cipherText.append(QByteArray::fromHex("8EA2B7CA516745BFEAFC49904B496089"));
|
||||
bool ok;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,12 @@ private:
|
||||
Crypto();
|
||||
static bool checkAlgorithms();
|
||||
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 QString m_errorStr;
|
||||
|
@ -49,6 +49,7 @@ CryptoHash::CryptoHash(CryptoHash::Algorithm algo)
|
||||
|
||||
gcry_error_t error = gcry_md_open(&d->ctx, algoGcrypt, 0);
|
||||
Q_ASSERT(error == 0); // TODO: error handling
|
||||
Q_UNUSED(error);
|
||||
|
||||
d->hashLen = gcry_md_get_algo_dlen(algoGcrypt);
|
||||
}
|
||||
|
@ -22,17 +22,39 @@
|
||||
#include "crypto/SymmetricCipherSalsa20.h"
|
||||
|
||||
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_initialized(false)
|
||||
{
|
||||
m_backend->setKey(key);
|
||||
m_backend->setIv(iv);
|
||||
}
|
||||
|
||||
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,
|
||||
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
|
||||
{
|
||||
return m_backend->blockSize();
|
||||
}
|
||||
|
||||
QString SymmetricCipher::errorString() const
|
||||
{
|
||||
return m_backend->errorString();
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QScopedPointer>
|
||||
#include <QString>
|
||||
|
||||
#include "core/Global.h"
|
||||
#include "crypto/SymmetricCipherBackend.h"
|
||||
@ -48,30 +49,35 @@ public:
|
||||
};
|
||||
|
||||
SymmetricCipher(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction, const QByteArray& key, const QByteArray& iv);
|
||||
SymmetricCipher::Direction direction);
|
||||
~SymmetricCipher();
|
||||
|
||||
inline QByteArray process(const QByteArray& data) {
|
||||
return m_backend->process(data);
|
||||
bool init(const QByteArray& key, const QByteArray& iv);
|
||||
bool isInitalized() const;
|
||||
|
||||
inline QByteArray process(const QByteArray& data, bool* ok) {
|
||||
return m_backend->process(data, ok);
|
||||
}
|
||||
|
||||
inline void processInPlace(QByteArray& data) {
|
||||
m_backend->processInPlace(data);
|
||||
inline bool processInPlace(QByteArray& data) Q_REQUIRED_RESULT {
|
||||
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);
|
||||
m_backend->processInPlace(data, rounds);
|
||||
return m_backend->processInPlace(data, rounds);
|
||||
}
|
||||
|
||||
void reset();
|
||||
bool reset();
|
||||
int blockSize() const;
|
||||
QString errorString() const;
|
||||
|
||||
private:
|
||||
static SymmetricCipherBackend* createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction);
|
||||
|
||||
const QScopedPointer<SymmetricCipherBackend> m_backend;
|
||||
bool m_initialized;
|
||||
|
||||
Q_DISABLE_COPY(SymmetricCipher)
|
||||
};
|
||||
|
@ -24,15 +24,18 @@ class SymmetricCipherBackend
|
||||
{
|
||||
public:
|
||||
virtual ~SymmetricCipherBackend() {}
|
||||
virtual void setKey(const QByteArray& key) = 0;
|
||||
virtual void setIv(const QByteArray& iv) = 0;
|
||||
virtual bool init() = 0;
|
||||
virtual bool setKey(const QByteArray& key) = 0;
|
||||
virtual bool setIv(const QByteArray& iv) = 0;
|
||||
|
||||
virtual QByteArray process(const QByteArray& data) = 0;
|
||||
virtual void processInPlace(QByteArray& data) = 0;
|
||||
virtual void processInPlace(QByteArray& data, quint64 rounds) = 0;
|
||||
virtual QByteArray process(const QByteArray& data, bool* ok) = 0;
|
||||
virtual bool processInPlace(QByteArray& data) Q_REQUIRED_RESULT = 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 QString errorString() const = 0;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_SYMMETRICCIPHERBACKEND_H
|
||||
|
@ -22,22 +22,12 @@
|
||||
|
||||
SymmetricCipherGcrypt::SymmetricCipherGcrypt(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction)
|
||||
: m_algo(gcryptAlgo(algo))
|
||||
: m_ctx(Q_NULLPTR)
|
||||
, m_algo(gcryptAlgo(algo))
|
||||
, m_mode(gcryptMode(mode))
|
||||
, m_direction(direction)
|
||||
, 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()
|
||||
@ -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;
|
||||
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;
|
||||
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
|
||||
|
||||
@ -113,12 +147,16 @@ QByteArray SymmetricCipherGcrypt::process(const QByteArray& data)
|
||||
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;
|
||||
}
|
||||
|
||||
void SymmetricCipherGcrypt::processInPlace(QByteArray& data)
|
||||
bool SymmetricCipherGcrypt::processInPlace(QByteArray& data)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@ -146,28 +189,52 @@ void SymmetricCipherGcrypt::processInPlace(QByteArray& data, quint64 rounds)
|
||||
if (m_direction == SymmetricCipher::Decrypt) {
|
||||
for (quint64 i = 0; i != rounds; ++i) {
|
||||
error = gcry_cipher_decrypt(m_ctx, rawData, size, Q_NULLPTR, 0);
|
||||
Q_ASSERT(error == 0);
|
||||
|
||||
if (error != 0) {
|
||||
setErrorString(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (quint64 i = 0; i != rounds; ++i) {
|
||||
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;
|
||||
|
||||
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());
|
||||
Q_ASSERT(error == 0);
|
||||
if (error != 0) {
|
||||
setErrorString(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int SymmetricCipherGcrypt::blockSize() const
|
||||
{
|
||||
return m_blockSize;
|
||||
}
|
||||
|
||||
QString SymmetricCipherGcrypt::errorString() const
|
||||
{
|
||||
return m_errorString;
|
||||
}
|
||||
|
@ -29,19 +29,24 @@ public:
|
||||
SymmetricCipherGcrypt(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction);
|
||||
~SymmetricCipherGcrypt();
|
||||
void setKey(const QByteArray& key);
|
||||
void setIv(const QByteArray& iv);
|
||||
|
||||
QByteArray process(const QByteArray& data);
|
||||
void processInPlace(QByteArray& data);
|
||||
void processInPlace(QByteArray& data, quint64 rounds);
|
||||
bool init();
|
||||
bool setKey(const QByteArray& key);
|
||||
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;
|
||||
|
||||
QString errorString() const;
|
||||
|
||||
private:
|
||||
static int gcryptAlgo(SymmetricCipher::Algorithm algo);
|
||||
static int gcryptMode(SymmetricCipher::Mode mode);
|
||||
void setErrorString(gcry_error_t err);
|
||||
|
||||
gcry_cipher_hd_t m_ctx;
|
||||
const int m_algo;
|
||||
@ -50,6 +55,7 @@ private:
|
||||
QByteArray m_key;
|
||||
QByteArray m_iv;
|
||||
int m_blockSize;
|
||||
QString m_errorString;
|
||||
};
|
||||
|
||||
#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));
|
||||
|
||||
m_key = key;
|
||||
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);
|
||||
|
||||
m_iv = iv;
|
||||
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));
|
||||
|
||||
@ -59,18 +68,21 @@ QByteArray SymmetricCipherSalsa20::process(const QByteArray& data)
|
||||
ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast<const u8*>(data.constData()),
|
||||
reinterpret_cast<u8*>(result.data()), data.size());
|
||||
|
||||
*ok = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
void SymmetricCipherSalsa20::processInPlace(QByteArray& data)
|
||||
bool SymmetricCipherSalsa20::processInPlace(QByteArray& data)
|
||||
{
|
||||
Q_ASSERT((data.size() < blockSize()) || ((data.size() % blockSize()) == 0));
|
||||
|
||||
ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast<const u8*>(data.constData()),
|
||||
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));
|
||||
|
||||
@ -78,14 +90,23 @@ void SymmetricCipherSalsa20::processInPlace(QByteArray& data, quint64 rounds)
|
||||
ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast<const u8*>(data.constData()),
|
||||
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()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int SymmetricCipherSalsa20::blockSize() const
|
||||
{
|
||||
return 64;
|
||||
}
|
||||
|
||||
QString SymmetricCipherSalsa20::errorString() const
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
@ -28,19 +28,22 @@ public:
|
||||
SymmetricCipherSalsa20(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction);
|
||||
~SymmetricCipherSalsa20();
|
||||
bool init();
|
||||
void setAlgorithm(SymmetricCipher::Algorithm algo);
|
||||
void setMode(SymmetricCipher::Mode mode);
|
||||
void setDirection(SymmetricCipher::Direction direction);
|
||||
void setKey(const QByteArray& key);
|
||||
void setIv(const QByteArray& iv);
|
||||
bool setKey(const QByteArray& key);
|
||||
bool setIv(const QByteArray& iv);
|
||||
|
||||
QByteArray process(const QByteArray& data);
|
||||
void processInPlace(QByteArray& data);
|
||||
void processInPlace(QByteArray& data, quint64 rounds);
|
||||
QByteArray process(const QByteArray& data, bool* ok);
|
||||
bool processInPlace(QByteArray& data);
|
||||
bool processInPlace(QByteArray& data, quint64 rounds);
|
||||
|
||||
void reset();
|
||||
bool reset();
|
||||
int blockSize() const;
|
||||
|
||||
QString errorString() const;
|
||||
|
||||
private:
|
||||
ECRYPT_ctx m_ctx;
|
||||
QByteArray m_key;
|
||||
|
@ -49,7 +49,12 @@ private:
|
||||
|
||||
|
||||
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");
|
||||
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();
|
||||
|
||||
QScopedPointer<SymmetricCipherStream> cipherStream(testKeys(password, keyfileData, contentPos));
|
||||
|
||||
if (!cipherStream) {
|
||||
raiseError("Unable to create cipher stream");
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
@ -234,7 +241,10 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
|
||||
key.addKey(newFileKey);
|
||||
}
|
||||
|
||||
db->setKey(key);
|
||||
if (!db->setKey(key)) {
|
||||
raiseError(tr("Unable to calculate master key"));
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
return db.take();
|
||||
}
|
||||
@ -326,16 +336,26 @@ SymmetricCipherStream* KeePass1Reader::testKeys(const QString& password, const Q
|
||||
}
|
||||
|
||||
QByteArray finalKey = key(passwordData, keyfileData);
|
||||
if (finalKey.isEmpty()) {
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
if (m_encryptionFlags & KeePass1::Rijndael) {
|
||||
cipherStream.reset(new SymmetricCipherStream(m_device, SymmetricCipher::Aes256,
|
||||
SymmetricCipher::Cbc, SymmetricCipher::Decrypt, finalKey, m_encryptionIV));
|
||||
SymmetricCipher::Cbc, SymmetricCipher::Decrypt));
|
||||
}
|
||||
else {
|
||||
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());
|
||||
|
||||
@ -372,9 +392,18 @@ QByteArray KeePass1Reader::key(const QByteArray& password, const QByteArray& key
|
||||
key.setPassword(password);
|
||||
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);
|
||||
hash.addData(m_masterSeed);
|
||||
hash.addData(key.transform(m_transformSeed, m_transformRounds));
|
||||
hash.addData(transformedKey);
|
||||
return hash.result();
|
||||
}
|
||||
|
||||
|
@ -20,14 +20,19 @@
|
||||
#include "crypto/CryptoHash.h"
|
||||
#include "format/KeePass2.h"
|
||||
|
||||
KeePass2RandomStream::KeePass2RandomStream(const QByteArray& key)
|
||||
: m_cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt,
|
||||
CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV)
|
||||
KeePass2RandomStream::KeePass2RandomStream()
|
||||
: m_cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt)
|
||||
, 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;
|
||||
|
||||
@ -35,7 +40,10 @@ QByteArray KeePass2RandomStream::randomBytes(int size)
|
||||
|
||||
while (bytesRemaining > 0) {
|
||||
if (m_buffer.size() == m_offset) {
|
||||
loadBlock();
|
||||
if (!loadBlock()) {
|
||||
*ok = false;
|
||||
return QByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
int bytesToCopy = qMin(bytesRemaining, m_buffer.size() - m_offset);
|
||||
@ -44,12 +52,20 @@ QByteArray KeePass2RandomStream::randomBytes(int size)
|
||||
bytesRemaining -= bytesToCopy;
|
||||
}
|
||||
|
||||
*ok = true;
|
||||
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;
|
||||
result.resize(data.size());
|
||||
|
||||
@ -57,23 +73,39 @@ QByteArray KeePass2RandomStream::process(const QByteArray& data)
|
||||
result[i] = data[i] ^ randomData[i];
|
||||
}
|
||||
|
||||
*ok = true;
|
||||
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++) {
|
||||
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());
|
||||
|
||||
m_buffer.fill('\0', m_cipher.blockSize());
|
||||
m_cipher.processInPlace(m_buffer);
|
||||
if (!m_cipher.processInPlace(m_buffer)) {
|
||||
return false;
|
||||
}
|
||||
m_offset = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -25,13 +25,15 @@
|
||||
class KeePass2RandomStream
|
||||
{
|
||||
public:
|
||||
explicit KeePass2RandomStream(const QByteArray& key);
|
||||
QByteArray randomBytes(int size);
|
||||
QByteArray process(const QByteArray& data);
|
||||
void processInPlace(QByteArray& data);
|
||||
KeePass2RandomStream();
|
||||
bool init(const QByteArray& key);
|
||||
QByteArray randomBytes(int size, bool* ok);
|
||||
QByteArray process(const QByteArray& data, bool* ok);
|
||||
bool processInPlace(QByteArray& data) Q_REQUIRED_RESULT;
|
||||
QString errorString() const;
|
||||
|
||||
private:
|
||||
void loadBlock();
|
||||
bool loadBlock();
|
||||
|
||||
SymmetricCipher m_cipher;
|
||||
QByteArray m_buffer;
|
||||
|
@ -33,8 +33,12 @@
|
||||
#include "streams/SymmetricCipherStream.h"
|
||||
|
||||
KeePass2Reader::KeePass2Reader()
|
||||
: m_error(false)
|
||||
: m_device(Q_NULLPTR)
|
||||
, m_headerStream(Q_NULLPTR)
|
||||
, m_error(false)
|
||||
, m_headerEnd(false)
|
||||
, m_saveXml(false)
|
||||
, m_db(Q_NULLPTR)
|
||||
{
|
||||
}
|
||||
|
||||
@ -96,16 +100,26 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
|
||||
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);
|
||||
hash.addData(m_masterSeed);
|
||||
hash.addData(m_db->transformedMasterKey());
|
||||
QByteArray finalKey = hash.result();
|
||||
|
||||
SymmetricCipherStream cipherStream(m_device, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
||||
SymmetricCipher::Decrypt, finalKey, m_encryptionIV);
|
||||
cipherStream.open(QIODevice::ReadOnly);
|
||||
SymmetricCipherStream cipherStream(m_device, SymmetricCipher::Aes256,
|
||||
SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||
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);
|
||||
|
||||
@ -115,7 +129,10 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
|
||||
}
|
||||
|
||||
HashedBlockStream hashedStream(&cipherStream);
|
||||
hashedStream.open(QIODevice::ReadOnly);
|
||||
if (!hashedStream.open(QIODevice::ReadOnly)) {
|
||||
raiseError(hashedStream.errorString());
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
QIODevice* xmlDevice;
|
||||
QScopedPointer<QtIOCompressor> ioCompressor;
|
||||
@ -126,11 +143,18 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
|
||||
else {
|
||||
ioCompressor.reset(new QtIOCompressor(&hashedStream));
|
||||
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
|
||||
ioCompressor->open(QIODevice::ReadOnly);
|
||||
if (!ioCompressor->open(QIODevice::ReadOnly)) {
|
||||
raiseError(ioCompressor->errorString());
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
xmlDevice = ioCompressor.data();
|
||||
}
|
||||
|
||||
KeePass2RandomStream randomStream(m_protectedStreamKey);
|
||||
KeePass2RandomStream randomStream;
|
||||
if (!randomStream.init(m_protectedStreamKey)) {
|
||||
raiseError(randomStream.errorString());
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
QScopedPointer<QBuffer> buffer;
|
||||
|
||||
@ -340,7 +364,9 @@ void KeePass2Reader::setTansformRounds(const QByteArray& data)
|
||||
raiseError("Invalid transform rounds size");
|
||||
}
|
||||
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()));
|
||||
|
||||
SymmetricCipherStream cipherStream(device, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
||||
SymmetricCipher::Encrypt, finalKey, encryptionIV);
|
||||
cipherStream.open(QIODevice::WriteOnly);
|
||||
SymmetricCipher::Encrypt);
|
||||
cipherStream.init(finalKey, encryptionIV);
|
||||
if (!cipherStream.open(QIODevice::WriteOnly)) {
|
||||
raiseError(cipherStream.errorString());
|
||||
return;
|
||||
}
|
||||
m_device = &cipherStream;
|
||||
CHECK_RETURN(writeData(startBytes));
|
||||
|
||||
HashedBlockStream hashedStream(&cipherStream);
|
||||
hashedStream.open(QIODevice::WriteOnly);
|
||||
if (!hashedStream.open(QIODevice::WriteOnly)) {
|
||||
raiseError(hashedStream.errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
QScopedPointer<QtIOCompressor> ioCompressor;
|
||||
|
||||
@ -104,14 +111,25 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
|
||||
else {
|
||||
ioCompressor.reset(new QtIOCompressor(&hashedStream));
|
||||
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
|
||||
ioCompressor->open(QIODevice::WriteOnly);
|
||||
if (!ioCompressor->open(QIODevice::WriteOnly)) {
|
||||
raiseError(ioCompressor->errorString());
|
||||
return;
|
||||
}
|
||||
m_device = ioCompressor.data();
|
||||
}
|
||||
|
||||
KeePass2RandomStream randomStream(protectedStreamKey);
|
||||
KeePass2RandomStream randomStream;
|
||||
if (!randomStream.init(protectedStreamKey)) {
|
||||
raiseError(randomStream.errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
KeePass2XmlWriter xmlWriter;
|
||||
xmlWriter.writeDatabase(m_device, db, &randomStream, headerHash);
|
||||
|
||||
if (xmlWriter.hasError()) {
|
||||
raiseError(xmlWriter.errorString());
|
||||
}
|
||||
}
|
||||
|
||||
bool KeePass2Writer::writeData(const QByteArray& data)
|
||||
|
@ -34,6 +34,7 @@ KeePass2XmlReader::KeePass2XmlReader()
|
||||
: m_randomStream(Q_NULLPTR)
|
||||
, m_db(Q_NULLPTR)
|
||||
, m_meta(Q_NULLPTR)
|
||||
, m_tmpParent(Q_NULLPTR)
|
||||
, m_error(false)
|
||||
, m_strictMode(false)
|
||||
{
|
||||
@ -809,7 +810,16 @@ void KeePass2XmlReader::parseEntryString(Entry* entry)
|
||||
|
||||
if (isProtected && !value.isEmpty()) {
|
||||
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 {
|
||||
raiseError("Unable to decrypt entry string");
|
||||
@ -868,7 +878,9 @@ QPair<QString, QString> KeePass2XmlReader::parseEntryBinary(Entry* entry)
|
||||
&& (attr.value("Protected") == "True");
|
||||
|
||||
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_meta(Q_NULLPTR)
|
||||
, m_randomStream(Q_NULLPTR)
|
||||
, m_error(false)
|
||||
{
|
||||
m_xml.setAutoFormatting(true);
|
||||
m_xml.setAutoFormattingIndent(-1); // 1 tab
|
||||
@ -65,6 +66,16 @@ void KeePass2XmlWriter::writeDatabase(const QString& filename, Database* db)
|
||||
writeDatabase(&file, db);
|
||||
}
|
||||
|
||||
bool KeePass2XmlWriter::hasError()
|
||||
{
|
||||
return m_error;
|
||||
}
|
||||
|
||||
QString KeePass2XmlWriter::errorString()
|
||||
{
|
||||
return m_errorStr;
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::generateIdMap()
|
||||
{
|
||||
QList<Entry*> allEntries = m_db->rootGroup()->entriesRecursive(true);
|
||||
@ -340,7 +351,11 @@ void KeePass2XmlWriter::writeEntry(const Entry* entry)
|
||||
if (protect) {
|
||||
if (m_randomStream) {
|
||||
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());
|
||||
}
|
||||
else {
|
||||
@ -527,3 +542,9 @@ QString KeePass2XmlWriter::colorPartToString(int value)
|
||||
|
||||
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,
|
||||
const QByteArray& headerHash = QByteArray());
|
||||
void writeDatabase(const QString& filename, Database* db);
|
||||
bool error();
|
||||
bool hasError();
|
||||
QString errorString();
|
||||
|
||||
private:
|
||||
@ -74,12 +74,16 @@ private:
|
||||
void writeTriState(const QString& qualifiedName, Group::TriState triState);
|
||||
QString colorPartToString(int value);
|
||||
|
||||
void raiseError(const QString& errorMessage);
|
||||
|
||||
QXmlStreamWriter m_xml;
|
||||
Database* m_db;
|
||||
Metadata* m_meta;
|
||||
KeePass2RandomStream* m_randomStream;
|
||||
QByteArray m_headerHash;
|
||||
QHash<QByteArray, int> m_idMap;
|
||||
bool m_error;
|
||||
QString m_errorStr;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_KEEPASS2XMLWRITER_H
|
||||
|
@ -120,7 +120,10 @@ void ChangeMasterKeyWidget::generateKey()
|
||||
FileKey fileKey;
|
||||
QString 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);
|
||||
}
|
||||
|
@ -130,7 +130,10 @@ void DatabaseSettingsWidget::reject()
|
||||
void DatabaseSettingsWidget::transformRoundsBenchmark()
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,7 @@
|
||||
|
||||
DatabaseManagerStruct::DatabaseManagerStruct()
|
||||
: dbWidget(Q_NULLPTR)
|
||||
, lockFile(Q_NULLPTR)
|
||||
, saveToFilename(false)
|
||||
, modified(false)
|
||||
, readOnly(false)
|
||||
@ -142,8 +143,35 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
|
||||
}
|
||||
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();
|
||||
dbStruct.dbWidget = new DatabaseWidget(db, this);
|
||||
dbStruct.lockFile = lockFile;
|
||||
dbStruct.saveToFilename = !dbStruct.readOnly;
|
||||
|
||||
dbStruct.filePath = fileInfo.absoluteFilePath();
|
||||
@ -238,6 +266,7 @@ void DatabaseTabWidget::deleteDatabase(Database* db)
|
||||
removeTab(index);
|
||||
toggleTabbar();
|
||||
m_dbList.remove(db);
|
||||
delete dbStruct.lockFile;
|
||||
delete dbStruct.dbWidget;
|
||||
delete db;
|
||||
|
||||
@ -311,6 +340,11 @@ bool DatabaseTabWidget::saveDatabaseAs(Database* db)
|
||||
dbStruct.canonicalFilePath = fileInfo.canonicalFilePath();
|
||||
dbStruct.fileName = fileInfo.fileName();
|
||||
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);
|
||||
updateLastDatabases(dbStruct.filePath);
|
||||
return true;
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include <QHash>
|
||||
#include <QTabWidget>
|
||||
|
||||
#include "core/qlockfile.h"
|
||||
#include "format/KeePass2Writer.h"
|
||||
#include "gui/DatabaseWidget.h"
|
||||
|
||||
@ -34,6 +35,7 @@ struct DatabaseManagerStruct
|
||||
DatabaseManagerStruct();
|
||||
|
||||
DatabaseWidget* dbWidget;
|
||||
QLockFile* lockFile;
|
||||
QString filePath;
|
||||
QString canonicalFilePath;
|
||||
QString fileName;
|
||||
|
@ -599,8 +599,13 @@ void DatabaseWidget::updateMasterKey(bool accepted)
|
||||
{
|
||||
if (accepted) {
|
||||
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
|
||||
m_db->setKey(m_changeMasterKeyWidget->newMasterKey());
|
||||
bool result = m_db->setKey(m_changeMasterKeyWidget->newMasterKey());
|
||||
QApplication::restoreOverrideCursor();
|
||||
|
||||
if (!result) {
|
||||
MessageBox::critical(this, tr("Error"), tr("Unable to calculate master key"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (!m_db->hasKey()) {
|
||||
Q_EMIT closeRequest();
|
||||
@ -906,3 +911,53 @@ bool DatabaseWidget::isGroupSelected() const
|
||||
{
|
||||
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;
|
||||
void setEntryViewHeaderSizes(const QList<int>& sizes);
|
||||
void clearAllWidgets();
|
||||
bool currentEntryHasTitle();
|
||||
bool currentEntryHasUsername();
|
||||
bool currentEntryHasPassword();
|
||||
bool currentEntryHasUrl();
|
||||
bool currentEntryHasNotes();
|
||||
|
||||
Q_SIGNALS:
|
||||
void closeRequest();
|
||||
|
@ -323,14 +323,14 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
|
||||
m_ui->actionEntryClone->setEnabled(singleEntrySelected && !inSearch);
|
||||
m_ui->actionEntryEdit->setEnabled(singleEntrySelected);
|
||||
m_ui->actionEntryDelete->setEnabled(entriesSelected);
|
||||
m_ui->actionEntryCopyTitle->setEnabled(singleEntrySelected);
|
||||
m_ui->actionEntryCopyUsername->setEnabled(singleEntrySelected);
|
||||
m_ui->actionEntryCopyPassword->setEnabled(singleEntrySelected);
|
||||
m_ui->actionEntryCopyURL->setEnabled(singleEntrySelected);
|
||||
m_ui->actionEntryCopyNotes->setEnabled(singleEntrySelected);
|
||||
m_ui->actionEntryCopyTitle->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTitle());
|
||||
m_ui->actionEntryCopyUsername->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUsername());
|
||||
m_ui->actionEntryCopyPassword->setEnabled(singleEntrySelected && dbWidget->currentEntryHasPassword());
|
||||
m_ui->actionEntryCopyURL->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl());
|
||||
m_ui->actionEntryCopyNotes->setEnabled(singleEntrySelected && dbWidget->currentEntryHasUrl());
|
||||
m_ui->menuEntryCopyAttribute->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->actionGroupEdit->setEnabled(groupSelected);
|
||||
m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGroup());
|
||||
|
@ -38,6 +38,8 @@ EditGroupWidget::EditGroupWidget(QWidget* parent)
|
||||
add(tr("Properties"), m_editWidgetProperties);
|
||||
|
||||
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(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->searchComboBox->setCurrentIndex(indexFromTriState(group->searchingEnabled()));
|
||||
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.uuid = group->iconUuid();
|
||||
@ -97,6 +106,13 @@ void EditGroupWidget::save()
|
||||
m_group->setSearchingEnabled(triStateFromIndex(m_mainUi->searchComboBox->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();
|
||||
|
||||
if (iconStruct.number < 0) {
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>676</width>
|
||||
<height>334</height>
|
||||
<height>356</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
@ -36,6 +36,13 @@
|
||||
<item row="1" column="1">
|
||||
<widget class="QPlainTextEdit" name="editNotes"/>
|
||||
</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">
|
||||
<widget class="QDateTimeEdit" name="expireDatePicker">
|
||||
<property name="enabled">
|
||||
@ -46,13 +53,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</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">
|
||||
<widget class="QLabel" name="searchLabel">
|
||||
<property name="text">
|
||||
@ -73,6 +73,47 @@
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="autotypeComboBox"/>
|
||||
</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>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -81,34 +81,62 @@ QByteArray CompositeKey::rawKey() const
|
||||
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(rounds > 0);
|
||||
|
||||
bool okLeft;
|
||||
QString errorStringLeft;
|
||||
bool okRight;
|
||||
QString errorStringRight;
|
||||
|
||||
QByteArray key = rawKey();
|
||||
|
||||
QFuture<QByteArray> future = QtConcurrent::run(transformKeyRaw, key.left(16), seed, rounds);
|
||||
QByteArray result2 = transformKeyRaw(key.right(16), seed, rounds);
|
||||
QFuture<QByteArray> future = QtConcurrent::run(transformKeyRaw, key.left(16), seed, rounds, &okLeft, &errorStringLeft);
|
||||
QByteArray result2 = transformKeyRaw(key.right(16), seed, rounds, &okRight, &errorStringRight);
|
||||
|
||||
QByteArray transformed;
|
||||
transformed.append(future.result());
|
||||
transformed.append(result2);
|
||||
|
||||
*ok = (okLeft && okRight);
|
||||
|
||||
if (!okLeft) {
|
||||
*errorString = errorStringLeft;
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
if (!okRight) {
|
||||
*errorString = errorStringRight;
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
return CryptoHash::hash(transformed, CryptoHash::Sha256);
|
||||
}
|
||||
|
||||
QByteArray CompositeKey::transformKeyRaw(const QByteArray& key, const QByteArray& seed,
|
||||
quint64 rounds)
|
||||
quint64 rounds, bool* ok, QString* errorString)
|
||||
{
|
||||
QByteArray iv(16, 0);
|
||||
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;
|
||||
|
||||
cipher.processInPlace(result, rounds);
|
||||
if (!cipher.processInPlace(result, rounds)) {
|
||||
*ok = false;
|
||||
*errorString = cipher.errorString();
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
*ok = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -151,13 +179,17 @@ void TransformKeyBenchmarkThread::run()
|
||||
QByteArray iv(16, 0);
|
||||
|
||||
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb,
|
||||
SymmetricCipher::Encrypt, seed, iv);
|
||||
SymmetricCipher::Encrypt);
|
||||
cipher.init(seed, iv);
|
||||
|
||||
QTime t;
|
||||
t.start();
|
||||
|
||||
do {
|
||||
cipher.processInPlace(key, 100);
|
||||
if (!cipher.processInPlace(key, 100)) {
|
||||
m_rounds = -1;
|
||||
return;
|
||||
}
|
||||
m_rounds += 100;
|
||||
} while (t.elapsed() < m_msec);
|
||||
}
|
||||
|
@ -34,14 +34,15 @@ public:
|
||||
CompositeKey& operator=(const CompositeKey& key);
|
||||
|
||||
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);
|
||||
|
||||
static int transformKeyBenchmark(int msec);
|
||||
|
||||
private:
|
||||
static QByteArray transformKeyRaw(const QByteArray& key, const QByteArray& seed,
|
||||
quint64 rounds);
|
||||
quint64 rounds, bool* ok, QString* errorString);
|
||||
|
||||
QList<Key*> m_keys;
|
||||
};
|
||||
|
@ -150,6 +150,7 @@ bool HashedBlockStream::readHashedBlock()
|
||||
if (m_blockSize == 0) {
|
||||
if (hash.count('\0') != 32) {
|
||||
m_error = true;
|
||||
setErrorString("Invalid hash of final block.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -166,6 +167,7 @@ bool HashedBlockStream::readHashedBlock()
|
||||
|
||||
if (hash != CryptoHash::hash(m_buffer, CryptoHash::Sha256)) {
|
||||
m_error = true;
|
||||
setErrorString("Mismatch between hash and data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -213,6 +215,7 @@ bool HashedBlockStream::writeHashedBlock()
|
||||
{
|
||||
if (!Endian::writeInt32(m_blockIndex, m_baseDevice, ByteOrder)) {
|
||||
m_error = true;
|
||||
setErrorString(m_baseDevice->errorString());
|
||||
return false;
|
||||
}
|
||||
m_blockIndex++;
|
||||
@ -227,17 +230,20 @@ bool HashedBlockStream::writeHashedBlock()
|
||||
|
||||
if (m_baseDevice->write(hash) != hash.size()) {
|
||||
m_error = true;
|
||||
setErrorString(m_baseDevice->errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Endian::writeInt32(m_buffer.size(), m_baseDevice, ByteOrder)) {
|
||||
m_error = true;
|
||||
setErrorString(m_baseDevice->errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_buffer.isEmpty()) {
|
||||
if (m_baseDevice->write(m_buffer) != m_buffer.size()) {
|
||||
m_error = true;
|
||||
setErrorString(m_baseDevice->errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -34,11 +34,6 @@ bool LayeredStream::isSequential() const
|
||||
return true;
|
||||
}
|
||||
|
||||
QString LayeredStream::errorString() const
|
||||
{
|
||||
return m_baseDevice->errorString();
|
||||
}
|
||||
|
||||
bool LayeredStream::open(QIODevice::OpenMode mode)
|
||||
{
|
||||
if (isOpen()) {
|
||||
|
@ -31,7 +31,6 @@ public:
|
||||
virtual ~LayeredStream();
|
||||
|
||||
bool isSequential() const Q_DECL_OVERRIDE;
|
||||
virtual QString errorString() const;
|
||||
bool open(QIODevice::OpenMode mode) Q_DECL_OVERRIDE;
|
||||
|
||||
protected:
|
||||
|
@ -41,6 +41,10 @@ QByteArray StoreDataStream::storedData() const
|
||||
qint64 StoreDataStream::readData(char* data, qint64 maxSize)
|
||||
{
|
||||
qint64 bytesRead = LayeredStream::readData(data, maxSize);
|
||||
if (bytesRead == -1) {
|
||||
setErrorString(m_baseDevice->errorString());
|
||||
return -1;
|
||||
}
|
||||
|
||||
m_storedData.append(data, bytesRead);
|
||||
|
||||
|
@ -18,13 +18,13 @@
|
||||
#include "SymmetricCipherStream.h"
|
||||
|
||||
SymmetricCipherStream::SymmetricCipherStream(QIODevice* baseDevice, SymmetricCipher::Algorithm algo,
|
||||
SymmetricCipher::Mode mode, SymmetricCipher::Direction direction,
|
||||
const QByteArray& key, const QByteArray& iv)
|
||||
SymmetricCipher::Mode mode, SymmetricCipher::Direction direction)
|
||||
: LayeredStream(baseDevice)
|
||||
, m_cipher(new SymmetricCipher(algo, mode, direction, key, iv))
|
||||
, m_cipher(new SymmetricCipher(algo, mode, direction))
|
||||
, m_bufferPos(0)
|
||||
, m_bufferFilling(false)
|
||||
, m_error(false)
|
||||
, m_isInitalized(false)
|
||||
{
|
||||
}
|
||||
|
||||
@ -33,6 +33,25 @@ SymmetricCipherStream::~SymmetricCipherStream()
|
||||
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()
|
||||
{
|
||||
if (isWritable()) {
|
||||
@ -108,7 +127,11 @@ bool SymmetricCipherStream::readBlock()
|
||||
return false;
|
||||
}
|
||||
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_bufferFilling = false;
|
||||
|
||||
@ -125,6 +148,7 @@ bool SymmetricCipherStream::readBlock()
|
||||
else if (padLength > m_cipher->blockSize()) {
|
||||
// invalid padding
|
||||
m_error = true;
|
||||
setErrorString("Invalid padding.");
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
@ -187,11 +211,15 @@ bool SymmetricCipherStream::writeBlock(bool lastBlock)
|
||||
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()) {
|
||||
m_error = true;
|
||||
// TODO: copy error string
|
||||
setErrorString(m_cipher->errorString());
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
|
@ -29,11 +29,13 @@ class SymmetricCipherStream : public LayeredStream
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SymmetricCipherStream(QIODevice* baseDevice, SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction, const QByteArray& key, const QByteArray& iv);
|
||||
SymmetricCipherStream(QIODevice* baseDevice, SymmetricCipher::Algorithm algo,
|
||||
SymmetricCipher::Mode mode, SymmetricCipher::Direction direction);
|
||||
~SymmetricCipherStream();
|
||||
bool reset();
|
||||
void close();
|
||||
bool init(const QByteArray& key, const QByteArray& iv);
|
||||
bool open(QIODevice::OpenMode mode) Q_DECL_OVERRIDE;
|
||||
bool reset() Q_DECL_OVERRIDE;
|
||||
void close() Q_DECL_OVERRIDE;
|
||||
|
||||
protected:
|
||||
qint64 readData(char* data, qint64 maxSize) Q_DECL_OVERRIDE;
|
||||
@ -48,6 +50,7 @@ private:
|
||||
int m_bufferPos;
|
||||
bool m_bufferFilling;
|
||||
bool m_error;
|
||||
bool m_isInitalized;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_SYMMETRICCIPHERSTREAM_H
|
||||
|
@ -36,15 +36,15 @@ void TestHashedBlockStream::testWriteRead()
|
||||
QByteArray data = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
|
||||
|
||||
QBuffer buffer;
|
||||
buffer.open(QIODevice::ReadWrite);
|
||||
QVERIFY(buffer.open(QIODevice::ReadWrite));
|
||||
|
||||
HashedBlockStream writer(&buffer, 16);
|
||||
writer.open(QIODevice::WriteOnly);
|
||||
QVERIFY(writer.open(QIODevice::WriteOnly));
|
||||
|
||||
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());
|
||||
buffer.reset();
|
||||
QCOMPARE(reader.read(17), data.left(16));
|
||||
@ -52,7 +52,7 @@ void TestHashedBlockStream::testWriteRead()
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
|
||||
writer.write(data.left(10));
|
||||
QCOMPARE(writer.write(data.left(10)), qint64(10));
|
||||
QVERIFY(writer.reset());
|
||||
buffer.reset();
|
||||
QCOMPARE(reader.read(5), data.left(5));
|
||||
@ -62,7 +62,7 @@ void TestHashedBlockStream::testWriteRead()
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
|
||||
writer.write(data.left(20));
|
||||
QCOMPARE(writer.write(data.left(20)), qint64(20));
|
||||
QVERIFY(writer.reset());
|
||||
buffer.reset();
|
||||
QCOMPARE(reader.read(20), data.left(20));
|
||||
|
@ -39,8 +39,8 @@ void TestKeePass2RandomStream::test()
|
||||
const int Size = 128;
|
||||
|
||||
|
||||
SymmetricCipher cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt,
|
||||
CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV);
|
||||
SymmetricCipher cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt);
|
||||
QVERIFY(cipher.init(CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV));
|
||||
|
||||
const QByteArray data(QByteArray::fromHex("601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c5"
|
||||
"2b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6"
|
||||
@ -49,7 +49,7 @@ void TestKeePass2RandomStream::test()
|
||||
|
||||
QByteArray cipherPad;
|
||||
cipherPad.fill('\0', Size);
|
||||
cipher.processInPlace(cipherPad);
|
||||
QVERIFY(cipher.processInPlace(cipherPad));
|
||||
|
||||
QByteArray cipherData;
|
||||
cipherData.resize(Size);
|
||||
@ -59,20 +59,27 @@ void TestKeePass2RandomStream::test()
|
||||
}
|
||||
|
||||
|
||||
KeePass2RandomStream randomStream(key);
|
||||
KeePass2RandomStream randomStream;
|
||||
bool ok;
|
||||
QVERIFY(randomStream.init(key));
|
||||
QByteArray randomStreamData;
|
||||
randomStreamData.append(randomStream.process(data.mid(0, 7)));
|
||||
randomStreamData.append(randomStream.process(data.mid(7, 1)));
|
||||
randomStreamData.append(randomStream.process(data.mid(0, 7), &ok));
|
||||
QVERIFY(ok);
|
||||
randomStreamData.append(randomStream.process(data.mid(7, 1), &ok));
|
||||
QVERIFY(ok);
|
||||
QByteArray tmpData = data.mid(8, 12);
|
||||
randomStream.processInPlace(tmpData);
|
||||
QVERIFY(randomStream.processInPlace(tmpData));
|
||||
randomStreamData.append(tmpData);
|
||||
randomStreamData.append(randomStream.process(data.mid(20, 44)));
|
||||
randomStreamData.append(randomStream.process(data.mid(64, 64)));
|
||||
randomStreamData.append(randomStream.process(data.mid(20, 44), &ok));
|
||||
QVERIFY(ok);
|
||||
randomStreamData.append(randomStream.process(data.mid(64, 64), &ok));
|
||||
QVERIFY(ok);
|
||||
|
||||
|
||||
SymmetricCipher cipherEncrypt(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt,
|
||||
CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV);
|
||||
QByteArray cipherDataEncrypt = cipherEncrypt.process(data);
|
||||
SymmetricCipher cipherEncrypt(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt);
|
||||
QVERIFY(cipherEncrypt.init(CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV));
|
||||
QByteArray cipherDataEncrypt = cipherEncrypt.process(data, &ok);
|
||||
QVERIFY(ok);
|
||||
|
||||
|
||||
QCOMPARE(randomStreamData.size(), Size);
|
||||
|
@ -43,6 +43,8 @@ void TestKeys::testComposite()
|
||||
CompositeKey* compositeKey1 = new CompositeKey();
|
||||
PasswordKey* passwordKey1 = new PasswordKey();
|
||||
PasswordKey* passwordKey2 = new PasswordKey("test");
|
||||
bool ok;
|
||||
QString errorString;
|
||||
|
||||
// make sure that addKey() creates a copy of the keys
|
||||
compositeKey1->addKey(*passwordKey1);
|
||||
@ -50,13 +52,15 @@ void TestKeys::testComposite()
|
||||
delete passwordKey1;
|
||||
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);
|
||||
|
||||
// make sure the subkeys are copied
|
||||
CompositeKey* compositeKey2 = compositeKey1->clone();
|
||||
delete compositeKey1;
|
||||
QCOMPARE(compositeKey2->transform(QByteArray(32, '\0'), 1), transformed);
|
||||
QCOMPARE(compositeKey2->transform(QByteArray(32, '\0'), 1, &ok, &errorString), transformed);
|
||||
QVERIFY(ok);
|
||||
delete compositeKey2;
|
||||
|
||||
CompositeKey* compositeKey3 = new CompositeKey();
|
||||
@ -130,7 +134,7 @@ void TestKeys::testCreateFileKey()
|
||||
compositeKey.addKey(fileKey);
|
||||
|
||||
Database* dbOrg = new Database();
|
||||
dbOrg->setKey(compositeKey);
|
||||
QVERIFY(dbOrg->setKey(compositeKey));
|
||||
dbOrg->metadata()->setName(dbName);
|
||||
|
||||
QBuffer dbBuffer;
|
||||
@ -182,7 +186,10 @@ void TestKeys::benchmarkTransformKey()
|
||||
|
||||
QByteArray seed(32, '\x4B');
|
||||
|
||||
bool ok;
|
||||
QString errorString;
|
||||
|
||||
QBENCHMARK {
|
||||
compositeKey.transform(seed, 1e6);
|
||||
compositeKey.transform(seed, 1e6, &ok, &errorString);
|
||||
}
|
||||
}
|
||||
|
@ -42,24 +42,27 @@ void TestSymmetricCipher::testAes256CbcEncryption()
|
||||
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
||||
QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6");
|
||||
cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d"));
|
||||
bool ok;
|
||||
|
||||
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt,
|
||||
key, iv);
|
||||
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
|
||||
QVERIFY(cipher.init(key, iv));
|
||||
QCOMPARE(cipher.blockSize(), 16);
|
||||
|
||||
QCOMPARE(cipher.process(plainText),
|
||||
QCOMPARE(cipher.process(plainText, &ok),
|
||||
cipherText);
|
||||
QVERIFY(ok);
|
||||
|
||||
QBuffer buffer;
|
||||
SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
||||
SymmetricCipher::Encrypt, key, iv);
|
||||
SymmetricCipher::Encrypt);
|
||||
QVERIFY(stream.init(key, iv));
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
stream.open(QIODevice::WriteOnly);
|
||||
QVERIFY(stream.open(QIODevice::WriteOnly));
|
||||
QVERIFY(stream.reset());
|
||||
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
stream.write(plainText.left(16));
|
||||
QCOMPARE(stream.write(plainText.left(16)), qint64(16));
|
||||
QCOMPARE(buffer.data(), cipherText.left(16));
|
||||
QVERIFY(stream.reset());
|
||||
// make sure padding is written
|
||||
@ -67,13 +70,13 @@ void TestSymmetricCipher::testAes256CbcEncryption()
|
||||
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
stream.write(plainText.left(10));
|
||||
QCOMPARE(stream.write(plainText.left(10)), qint64(10));
|
||||
QVERIFY(buffer.data().isEmpty());
|
||||
|
||||
QVERIFY(stream.reset());
|
||||
buffer.reset();
|
||||
buffer.buffer().clear();
|
||||
stream.write(plainText.left(10));
|
||||
QCOMPARE(stream.write(plainText.left(10)), qint64(10));
|
||||
stream.close();
|
||||
QCOMPARE(buffer.data().size(), 16);
|
||||
}
|
||||
@ -86,33 +89,37 @@ void TestSymmetricCipher::testAes256CbcDecryption()
|
||||
cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d"));
|
||||
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
|
||||
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.process(cipherText),
|
||||
QCOMPARE(cipher.process(cipherText, &ok),
|
||||
plainText);
|
||||
QVERIFY(ok);
|
||||
|
||||
// padded with 16 0x16 bytes
|
||||
QByteArray cipherTextPadded = cipherText + QByteArray::fromHex("3a3aa5e0213db1a9901f9036cf5102d2");
|
||||
QBuffer buffer(&cipherTextPadded);
|
||||
SymmetricCipherStream stream(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
||||
SymmetricCipher::Decrypt, key, iv);
|
||||
SymmetricCipher::Decrypt);
|
||||
QVERIFY(stream.init(key, iv));
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
stream.open(QIODevice::ReadOnly);
|
||||
QVERIFY(stream.open(QIODevice::ReadOnly));
|
||||
|
||||
QCOMPARE(stream.read(10),
|
||||
plainText.left(10));
|
||||
buffer.reset();
|
||||
stream.reset();
|
||||
QVERIFY(stream.reset());
|
||||
QCOMPARE(stream.read(20),
|
||||
plainText.left(20));
|
||||
buffer.reset();
|
||||
stream.reset();
|
||||
QVERIFY(stream.reset());
|
||||
QCOMPARE(stream.read(16),
|
||||
plainText.left(16));
|
||||
buffer.reset();
|
||||
stream.reset();
|
||||
QVERIFY(stream.reset());
|
||||
QCOMPARE(stream.read(100),
|
||||
plainText);
|
||||
}
|
||||
@ -123,16 +130,20 @@ void TestSymmetricCipher::testSalsa20()
|
||||
|
||||
QByteArray key = QByteArray::fromHex("F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112");
|
||||
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;
|
||||
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();
|
||||
|
||||
QByteArray cipherTextB = cipher.process(QByteArray(512, '\0'));
|
||||
QByteArray cipherTextB = cipher.process(QByteArray(512, '\0'), &ok);
|
||||
QVERIFY(ok);
|
||||
cipher.reset();
|
||||
|
||||
QByteArray expectedCipherText1;
|
||||
@ -180,7 +191,8 @@ void TestSymmetricCipher::testPadding()
|
||||
buffer.open(QIODevice::ReadWrite);
|
||||
|
||||
SymmetricCipherStream streamEnc(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
||||
SymmetricCipher::Encrypt, key, iv);
|
||||
SymmetricCipher::Encrypt);
|
||||
QVERIFY(streamEnc.init(key, iv));
|
||||
streamEnc.open(QIODevice::WriteOnly);
|
||||
streamEnc.write(plainText);
|
||||
streamEnc.close();
|
||||
@ -189,7 +201,8 @@ void TestSymmetricCipher::testPadding()
|
||||
QCOMPARE(buffer.buffer().size(), 16);
|
||||
|
||||
SymmetricCipherStream streamDec(&buffer, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
||||
SymmetricCipher::Decrypt, key, iv);
|
||||
SymmetricCipher::Decrypt);
|
||||
QVERIFY(streamDec.init(key, iv));
|
||||
streamDec.open(QIODevice::ReadOnly);
|
||||
QByteArray decrypted = streamDec.readAll();
|
||||
QCOMPARE(decrypted, plainText);
|
||||
|
Loading…
x
Reference in New Issue
Block a user