This commit is contained in:
Ivan 2015-05-22 21:06:32 -07:00
commit a115bbdc6f
66 changed files with 10458 additions and 288 deletions

View File

@ -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

View File

@ -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)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -17,8 +17,8 @@
<translation>Auto-Type - KeePassX</translation>
</message>
<message>
<source>Couldn&apos;t find an entry that matches the window title.</source>
<translation>Konnte dem Fenstertitel keinen passenden Eintrag zuordnen.</translation>
<source>Couldn&apos;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&apos;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>

View File

@ -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&apos;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>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -17,8 +17,8 @@
<translation>Auto-typen - KeePassX</translation>
</message>
<message>
<source>Couldn&apos;t find an entry that matches the window title.</source>
<translation>Kon geen element vinden dat overeenkomt met de venstertitel.</translation>
<source>Couldn&apos;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>

File diff suppressed because it is too large Load Diff

View File

@ -17,8 +17,8 @@
<translation>Auto-skriv - KeePassX</translation>
</message>
<message>
<source>Couldn&apos;t find an entry that matches the window title.</source>
<translation>Kunde inte hitta en post som matchar fönstertiteln.</translation>
<source>Couldn&apos;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>

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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
)

View File

@ -89,5 +89,7 @@ void AutoTypeExecutor::execDelay(AutoTypeDelay* action)
void AutoTypeExecutor::execClearField(AutoTypeClearField* action)
{
Q_UNUSED(action);
// TODO: implement
}

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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
View 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
View 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
View 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
View 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
View 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

View File

@ -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;
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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)
};

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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();
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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"));
}
}
}

View File

@ -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)

View File

@ -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());
}
}
}

View File

@ -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;
}

View File

@ -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

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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;

View File

@ -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;

View File

@ -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();
}

View File

@ -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();

View File

@ -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());

View File

@ -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) {

View File

@ -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>

View File

@ -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);
}

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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()) {

View File

@ -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:

View File

@ -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);

View File

@ -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 {

View File

@ -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

View File

@ -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));

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);