mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Merge pull request #1230 from keepassxreboot/feature/kdbx4
New Feature: KDBX4
This commit is contained in:
commit
7a55ab64d8
@ -72,6 +72,8 @@ get_desktop
|
||||
get_icon
|
||||
cat << EOF > ./usr/bin/keepassxc_env
|
||||
#!/usr/bin/env bash
|
||||
export LD_LIBRARY_PATH="/opt/libgcrypt20-18/lib/x86_64-linux-gnu:\${LD_LIBRARY_PATH}"
|
||||
export LD_LIBRARY_PATH="/opt/gpg-error-127/lib/x86_64-linux-gnu:\${LD_LIBRARY_PATH}"
|
||||
export LD_LIBRARY_PATH="..$(dirname ${QT_PLUGIN_PATH})/lib:\${LD_LIBRARY_PATH}"
|
||||
export QT_PLUGIN_PATH="..${QT_PLUGIN_PATH}:\${KPXC_QT_PLUGIN_PATH}"
|
||||
|
||||
|
@ -261,8 +261,8 @@ endif()
|
||||
set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS_NONE QT_NO_DEBUG)
|
||||
|
||||
find_package(LibGPGError REQUIRED)
|
||||
|
||||
find_package(Gcrypt 1.6.0 REQUIRED)
|
||||
find_package(Gcrypt 1.7.0 REQUIRED)
|
||||
find_package(Argon2 REQUIRED)
|
||||
|
||||
find_package(ZLIB REQUIRED)
|
||||
|
||||
|
22
Dockerfile
22
Dockerfile
@ -16,6 +16,8 @@
|
||||
|
||||
FROM ubuntu:14.04
|
||||
|
||||
ENV REBUILD_COUNTER=2
|
||||
|
||||
ENV QT5_VERSION=59
|
||||
ENV QT5_PPA_VERSION=${QT5_VERSION}2
|
||||
|
||||
@ -25,8 +27,7 @@ RUN set -x \
|
||||
|
||||
RUN set -x \
|
||||
&& add-apt-repository ppa:beineri/opt-qt${QT5_PPA_VERSION}-trusty \
|
||||
&& add-apt-repository ppa:phoerious/keepassxc \
|
||||
&& LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php
|
||||
&& add-apt-repository ppa:phoerious/keepassxc
|
||||
|
||||
RUN set -x \
|
||||
&& apt-get update -y \
|
||||
@ -37,7 +38,9 @@ RUN set -x \
|
||||
&& apt-get install -y \
|
||||
cmake3 \
|
||||
g++ \
|
||||
libgcrypt20-dev \
|
||||
libgcrypt20-18-dev \
|
||||
libargon2-0-dev \
|
||||
libsodium-dev \
|
||||
qt${QT5_VERSION}base \
|
||||
qt${QT5_VERSION}tools \
|
||||
qt${QT5_VERSION}x11extras \
|
||||
@ -47,13 +50,16 @@ RUN set -x \
|
||||
libxtst-dev \
|
||||
mesa-common-dev \
|
||||
libyubikey-dev \
|
||||
libykpers-1-dev \
|
||||
libsodium-dev
|
||||
libykpers-1-dev
|
||||
|
||||
ENV CMAKE_PREFIX_PATH=/opt/qt${QT5_VERSION}/lib/cmake
|
||||
ENV LD_LIBRARY_PATH=/opt/qt${QT5_VERSION}/lib
|
||||
ENV CMAKE_PREFIX_PATH="/opt/qt${QT5_VERSION}/lib/cmake"
|
||||
ENV CMAKE_INCLUDE_PATH="/opt/libgcrypt20-18/include:/opt/gpg-error-127/include"
|
||||
ENV CMAKE_LIBRARY_PATH="/opt/libgcrypt20-18/lib/x86_64-linux-gnu:/opt/gpg-error-127/lib/x86_64-linux-gnu"
|
||||
ENV LD_LIBRARY_PATH="/opt/qt${QT5_VERSION}/lib:/opt/libgcrypt20-18/lib/x86_64-linux-gnu:/opt/gpg-error-127/lib/x86_64-linux-gnu"
|
||||
RUN set -x \
|
||||
&& echo /opt/qt${QT_VERSION}/lib > /etc/ld.so.conf.d/qt${QT5_VERSION}.conf
|
||||
&& echo "/opt/qt${QT_VERSION}/lib" > /etc/ld.so.conf.d/qt${QT5_VERSION}.conf \
|
||||
&& echo "/opt/libgcrypt20-18/lib/x86_64-linux-gnu" > /etc/ld.so.conf.d/libgcrypt20-18.conf \
|
||||
&& echo "/opt/gpg-error-127/lib/x86_64-linux-gnu" > /etc/ld.so.conf.d/libgpg-error-127.conf
|
||||
|
||||
# AppImage dependencies
|
||||
RUN set -x \
|
||||
|
@ -26,6 +26,7 @@ The following libraries are required:
|
||||
* libmicrohttpd
|
||||
* libxi, libxtst, qtx11extras (optional for auto-type on X11)
|
||||
* libsodium (>= 1.0.12, optional for keepassxc-browser support)
|
||||
* libargon2
|
||||
|
||||
|
||||
Prepare the Building Environment
|
||||
|
@ -18,6 +18,8 @@
|
||||
|
||||
FROM ubuntu:14.04
|
||||
|
||||
ENV REBUILD_COUNTER=2
|
||||
|
||||
ENV QT5_VERSION=53
|
||||
ENV QT5_PPA_VERSION=${QT5_VERSION}2
|
||||
|
||||
@ -27,27 +29,39 @@ RUN set -x \
|
||||
|
||||
RUN set -x \
|
||||
&& add-apt-repository ppa:beineri/opt-qt${QT5_PPA_VERSION}-trusty \
|
||||
&& LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php
|
||||
&& add-apt-repository ppa:phoerious/keepassxc
|
||||
|
||||
RUN set -x \
|
||||
&& apt-get -y update \
|
||||
&& apt-get -y --no-install-recommends install \
|
||||
git build-essential clang-3.6 libclang-common-3.6-dev clang-format-3.6 cmake3 make \
|
||||
curl ca-certificates gnupg2 \
|
||||
libgcrypt20-dev zlib1g-dev libyubikey-dev libykpers-1-dev \
|
||||
build-essential \
|
||||
clang-3.6 \
|
||||
libclang-common-3.6-dev \
|
||||
clang-format-3.6 \
|
||||
cmake3 \
|
||||
make \
|
||||
libgcrypt20-18-dev \
|
||||
libargon2-0-dev \
|
||||
libsodium-dev \
|
||||
qt${QT5_VERSION}base \
|
||||
qt${QT5_VERSION}tools \
|
||||
qt${QT5_VERSION}x11extras \
|
||||
qt${QT5_VERSION}translations \
|
||||
zlib1g-dev \
|
||||
libyubikey-dev \
|
||||
libykpers-1-dev \
|
||||
libxi-dev \
|
||||
libxtst-dev \
|
||||
xvfb \
|
||||
libsodium-dev
|
||||
xvfb
|
||||
|
||||
ENV CMAKE_PREFIX_PATH=/opt/qt${QT5_VERSION}/lib/cmake
|
||||
ENV LD_LIBRARY_PATH=/opt/qt${QT5_VERSION}/lib
|
||||
ENV CMAKE_PREFIX_PATH="/opt/qt${QT5_VERSION}/lib/cmake"
|
||||
ENV CMAKE_INCLUDE_PATH="/opt/libgcrypt20-18/include:/opt/gpg-error-127/include"
|
||||
ENV CMAKE_LIBRARY_PATH="/opt/libgcrypt20-18/lib/x86_64-linux-gnu:/opt/gpg-error-127/lib/x86_64-linux-gnu"
|
||||
ENV LD_LIBRARY_PATH="/opt/qt${QT5_VERSION}/lib:/opt/libgcrypt20-18/lib/x86_64-linux-gnu:/opt/gpg-error-127/lib/x86_64-linux-gnu"
|
||||
RUN set -x \
|
||||
&& echo /opt/qt${QT_VERSION}/lib > /etc/ld.so.conf.d/qt${QT5_VERSION}.conf
|
||||
&& echo "/opt/qt${QT_VERSION}/lib" > /etc/ld.so.conf.d/qt${QT5_VERSION}.conf \
|
||||
&& echo "/opt/libgcrypt20-18/lib/x86_64-linux-gnu" > /etc/ld.so.conf.d/libgcrypt20-18.conf \
|
||||
&& echo "/opt/gpg-error-127/lib/x86_64-linux-gnu" > /etc/ld.so.conf.d/libgpg-error-127.conf
|
||||
|
||||
RUN set -x \
|
||||
&& apt-get autoremove --purge \
|
||||
|
21
cmake/FindArgon2.cmake
Normal file
21
cmake/FindArgon2.cmake
Normal file
@ -0,0 +1,21 @@
|
||||
# Copyright (C) 2017 KeePassXC Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 or (at your option)
|
||||
# version 3 of the License.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
find_path(ARGON2_INCLUDE_DIR argon2.h)
|
||||
find_library(ARGON2_LIBRARIES argon2)
|
||||
mark_as_advanced(ARGON2_LIBRARIES ARGON2_INCLUDE_DIR)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Argon2 DEFAULT_MSG ARGON2_LIBRARIES ARGON2_INCLUDE_DIR)
|
@ -14,10 +14,10 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
find_path(GPGERROR_INCLUDE_DIR gpg-error.h)
|
||||
|
||||
find_library(GPGERROR_LIBRARIES gpg-error)
|
||||
|
||||
mark_as_advanced(GPGERROR_LIBRARIES GPGERROR_INCLUDE_DIR)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
include_directories(${GPGERROR_INCLUDE_DIR})
|
||||
find_package_handle_standard_args(LibGPGError DEFAULT_MSG GPGERROR_LIBRARIES GPGERROR_INCLUDE_DIR)
|
||||
|
BIN
share/icons/application/32x32/actions/document-encrypt.png
Normal file
BIN
share/icons/application/32x32/actions/document-encrypt.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
@ -38,11 +38,11 @@ configure_file(version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/version.h @ONLY)
|
||||
|
||||
set(keepassx_SOURCES
|
||||
core/AutoTypeAssociations.cpp
|
||||
core/AsyncTask.h
|
||||
core/Config.cpp
|
||||
core/CsvParser.cpp
|
||||
core/Database.cpp
|
||||
core/DatabaseIcons.cpp
|
||||
core/Endian.cpp
|
||||
core/Entry.cpp
|
||||
core/EntryAttachments.cpp
|
||||
core/EntryAttributes.cpp
|
||||
@ -62,7 +62,6 @@ set(keepassx_SOURCES
|
||||
core/ScreenLockListenerPrivate.cpp
|
||||
core/TimeDelta.cpp
|
||||
core/TimeInfo.cpp
|
||||
core/ToDbExporter.cpp
|
||||
core/Tools.cpp
|
||||
core/Translator.cpp
|
||||
core/Uuid.cpp
|
||||
@ -76,16 +75,26 @@ set(keepassx_SOURCES
|
||||
crypto/SymmetricCipher.cpp
|
||||
crypto/SymmetricCipherBackend.h
|
||||
crypto/SymmetricCipherGcrypt.cpp
|
||||
crypto/kdf/Kdf.cpp
|
||||
crypto/kdf/Kdf_p.h
|
||||
crypto/kdf/AesKdf.cpp
|
||||
crypto/kdf/Argon2Kdf.cpp
|
||||
format/CsvExporter.cpp
|
||||
format/KeePass1.h
|
||||
format/KeePass1Reader.cpp
|
||||
format/KeePass2.h
|
||||
format/KeePass2.cpp
|
||||
format/KeePass2RandomStream.cpp
|
||||
format/KeePass2Reader.cpp
|
||||
format/KeePass2Repair.cpp
|
||||
format/KdbxReader.cpp
|
||||
format/KdbxWriter.cpp
|
||||
format/KdbxXmlReader.cpp
|
||||
format/KeePass2Reader.cpp
|
||||
format/KeePass2Writer.cpp
|
||||
format/KeePass2XmlReader.cpp
|
||||
format/KeePass2XmlWriter.cpp
|
||||
format/Kdbx3Reader.cpp
|
||||
format/Kdbx3Writer.cpp
|
||||
format/Kdbx4Reader.cpp
|
||||
format/Kdbx4Writer.cpp
|
||||
format/KdbxXmlWriter.cpp
|
||||
gui/AboutDialog.cpp
|
||||
gui/Application.cpp
|
||||
gui/CategoryListWidget.cpp
|
||||
@ -139,13 +148,13 @@ set(keepassx_SOURCES
|
||||
gui/group/GroupModel.cpp
|
||||
gui/group/GroupView.cpp
|
||||
keys/CompositeKey.cpp
|
||||
keys/CompositeKey_p.h
|
||||
keys/drivers/YubiKey.h
|
||||
keys/FileKey.cpp
|
||||
keys/Key.h
|
||||
keys/PasswordKey.cpp
|
||||
keys/YkChallengeResponseKey.cpp
|
||||
streams/HashedBlockStream.cpp
|
||||
streams/HmacBlockStream.cpp
|
||||
streams/LayeredStream.cpp
|
||||
streams/qtiocompressor.cpp
|
||||
streams/StoreDataStream.cpp
|
||||
@ -248,6 +257,7 @@ target_link_libraries(keepassx_core
|
||||
Qt5::Network
|
||||
Qt5::Concurrent
|
||||
Qt5::Widgets
|
||||
${ARGON2_LIBRARIES}
|
||||
${GCRYPT_LIBRARIES}
|
||||
${GPGERROR_LIBRARIES}
|
||||
${ZLIB_LIBRARIES})
|
||||
|
@ -46,6 +46,7 @@ target_link_libraries(keepassxc-cli
|
||||
keepassx_core
|
||||
Qt5::Core
|
||||
${GCRYPT_LIBRARIES}
|
||||
${ARGON2_LIBRARIES}
|
||||
${GPGERROR_LIBRARIES}
|
||||
${ZLIB_LIBRARIES}
|
||||
${ZXCVBN_LIBRARIES})
|
||||
|
@ -101,7 +101,7 @@ int Extract::execute(QStringList arguments)
|
||||
Database* db = reader.readDatabase(&dbFile, compositeKey);
|
||||
delete db;
|
||||
|
||||
QByteArray xmlData = reader.xmlData();
|
||||
QByteArray xmlData = reader.reader()->xmlData();
|
||||
|
||||
if (reader.hasError()) {
|
||||
if (xmlData.isEmpty()) {
|
||||
|
63
src/core/AsyncTask.h
Normal file
63
src/core/AsyncTask.h
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSXC_ASYNCTASK_HPP
|
||||
#define KEEPASSXC_ASYNCTASK_HPP
|
||||
|
||||
#include <QFuture>
|
||||
#include <QFutureWatcher>
|
||||
#include <QtConcurrent>
|
||||
|
||||
|
||||
/**
|
||||
* Asynchronously run computations outside the GUI thread.
|
||||
*/
|
||||
namespace AsyncTask
|
||||
{
|
||||
|
||||
/**
|
||||
* Wait for the given future without blocking the event loop.
|
||||
*
|
||||
* @param future future to wait for
|
||||
* @return async task result
|
||||
*/
|
||||
template<typename FunctionObject>
|
||||
typename std::result_of<FunctionObject()>::type waitForFuture(QFuture<typename std::result_of<FunctionObject()>::type> future)
|
||||
{
|
||||
QEventLoop loop;
|
||||
QFutureWatcher<typename std::result_of<FunctionObject()>::type> watcher;
|
||||
QObject::connect(&watcher, SIGNAL(finished()), &loop, SLOT(quit()));
|
||||
watcher.setFuture(future);
|
||||
loop.exec();
|
||||
return future.result();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a given task and wait for it to finish without blocking the event loop.
|
||||
*
|
||||
* @param task std::function object to run
|
||||
* @return async task result
|
||||
*/
|
||||
template<typename FunctionObject>
|
||||
typename std::result_of<FunctionObject()>::type runAndWaitForFuture(FunctionObject task)
|
||||
{
|
||||
return waitForFuture<FunctionObject>(QtConcurrent::run(task));
|
||||
}
|
||||
|
||||
}; // namespace AsyncTask
|
||||
|
||||
#endif //KEEPASSXC_ASYNCTASK_HPP
|
@ -27,13 +27,12 @@
|
||||
#include "cli/Utils.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "crypto/Random.h"
|
||||
#include "crypto/kdf/AesKdf.h"
|
||||
#include "format/KeePass2.h"
|
||||
#include "format/KeePass2Reader.h"
|
||||
#include "format/KeePass2Writer.h"
|
||||
#include "keys/PasswordKey.h"
|
||||
#include "keys/FileKey.h"
|
||||
#include "keys/CompositeKey.h"
|
||||
|
||||
QHash<Uuid, Database*> Database::m_uuidMap;
|
||||
|
||||
@ -45,7 +44,11 @@ Database::Database()
|
||||
{
|
||||
m_data.cipher = KeePass2::CIPHER_AES;
|
||||
m_data.compressionAlgo = CompressionGZip;
|
||||
m_data.transformRounds = 100000;
|
||||
|
||||
// instantiate default AES-KDF with legacy KDBX3 flag set
|
||||
// KDBX4+ will re-initialize the KDF using parameters read from the KDBX file
|
||||
m_data.kdf = QSharedPointer<AesKdf>::create(true);
|
||||
m_data.kdf->randomizeSeed();
|
||||
m_data.hasKey = false;
|
||||
|
||||
setRootGroup(new Group());
|
||||
@ -225,16 +228,6 @@ Database::CompressionAlgorithm Database::compressionAlgo() const
|
||||
return m_data.compressionAlgo;
|
||||
}
|
||||
|
||||
QByteArray Database::transformSeed() const
|
||||
{
|
||||
return m_data.transformSeed;
|
||||
}
|
||||
|
||||
quint64 Database::transformRounds() const
|
||||
{
|
||||
return m_data.transformRounds;
|
||||
}
|
||||
|
||||
QByteArray Database::transformedMasterKey() const
|
||||
{
|
||||
return m_data.transformedMasterKey;
|
||||
@ -265,75 +258,46 @@ void Database::setCompressionAlgo(Database::CompressionAlgorithm algo)
|
||||
m_data.compressionAlgo = algo;
|
||||
}
|
||||
|
||||
bool Database::setTransformRounds(quint64 rounds)
|
||||
/**
|
||||
* Set and transform a new encryption key.
|
||||
*
|
||||
* @param key key to set and transform
|
||||
* @param updateChangedTime true to update database change time
|
||||
* @param updateTransformSalt true to update the transform salt
|
||||
* @return true on success
|
||||
*/
|
||||
bool Database::setKey(const CompositeKey& key, bool updateChangedTime, bool updateTransformSalt)
|
||||
{
|
||||
if (m_data.transformRounds != rounds) {
|
||||
quint64 oldRounds = m_data.transformRounds;
|
||||
|
||||
m_data.transformRounds = rounds;
|
||||
|
||||
if (m_data.hasKey) {
|
||||
if (!setKey(m_data.key)) {
|
||||
m_data.transformRounds = oldRounds;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (updateTransformSalt) {
|
||||
m_data.kdf->randomizeSeed();
|
||||
Q_ASSERT(!m_data.kdf->seed().isEmpty());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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) {
|
||||
QByteArray oldTransformedMasterKey = m_data.transformedMasterKey;
|
||||
QByteArray transformedMasterKey;
|
||||
if (!key.transform(*m_data.kdf, transformedMasterKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_data.key = key;
|
||||
m_data.transformSeed = transformSeed;
|
||||
m_data.transformedMasterKey = transformedMasterKey;
|
||||
m_data.hasKey = true;
|
||||
if (updateChangedTime) {
|
||||
m_metadata->setMasterKeyChanged(QDateTime::currentDateTimeUtc());
|
||||
}
|
||||
emit modifiedImmediate();
|
||||
|
||||
if (oldTransformedMasterKey != m_data.transformedMasterKey) {
|
||||
emit modifiedImmediate();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Database::setKey(const CompositeKey& key)
|
||||
{
|
||||
return setKey(key, randomGen()->randomArray(32));
|
||||
}
|
||||
|
||||
bool Database::hasKey() const
|
||||
{
|
||||
return m_data.hasKey;
|
||||
}
|
||||
|
||||
bool Database::transformKeyWithSeed(const QByteArray& transformSeed)
|
||||
{
|
||||
Q_ASSERT(hasKey());
|
||||
|
||||
bool ok;
|
||||
QString errorString;
|
||||
|
||||
QByteArray transformedMasterKey =
|
||||
m_data.key.transform(transformSeed, transformRounds(), &ok, &errorString);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_data.transformSeed = transformSeed;
|
||||
m_data.transformedMasterKey = transformedMasterKey;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Database::verifyKey(const CompositeKey& key) const
|
||||
{
|
||||
Q_ASSERT(hasKey());
|
||||
@ -426,11 +390,6 @@ void Database::setEmitModified(bool value)
|
||||
m_emitModified = value;
|
||||
}
|
||||
|
||||
void Database::copyAttributesFrom(const Database* other)
|
||||
{
|
||||
m_data = other->m_data;
|
||||
m_metadata->copyAttributesFrom(other->m_metadata);
|
||||
}
|
||||
|
||||
Uuid Database::uuid()
|
||||
{
|
||||
@ -518,7 +477,9 @@ QString Database::saveToFile(QString filePath)
|
||||
if (saveFile.open(QIODevice::WriteOnly)) {
|
||||
|
||||
// write the database to the file
|
||||
setEmitModified(false);
|
||||
writer.writeDatabase(&saveFile, this);
|
||||
setEmitModified(true);
|
||||
|
||||
if (writer.hasError()) {
|
||||
return writer.errorString();
|
||||
@ -534,3 +495,36 @@ QString Database::saveToFile(QString filePath)
|
||||
return saveFile.errorString();
|
||||
}
|
||||
}
|
||||
|
||||
QSharedPointer<Kdf> Database::kdf() const
|
||||
{
|
||||
return m_data.kdf;
|
||||
}
|
||||
|
||||
void Database::setKdf(QSharedPointer<Kdf> kdf)
|
||||
{
|
||||
m_data.kdf = std::move(kdf);
|
||||
}
|
||||
|
||||
void Database::setPublicCustomData(QByteArray data) {
|
||||
m_data.publicCustomData = data;
|
||||
}
|
||||
|
||||
QByteArray Database::publicCustomData() const {
|
||||
return m_data.publicCustomData;
|
||||
}
|
||||
|
||||
bool Database::changeKdf(QSharedPointer<Kdf> kdf)
|
||||
{
|
||||
kdf->randomizeSeed();
|
||||
QByteArray transformedMasterKey;
|
||||
if (!m_data.key.transform(*kdf, transformedMasterKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
setKdf(kdf);
|
||||
m_data.transformedMasterKey = transformedMasterKey;
|
||||
emit modifiedImmediate();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
|
||||
#include "crypto/kdf/Kdf.h"
|
||||
#include "core/Uuid.h"
|
||||
#include "keys/CompositeKey.h"
|
||||
|
||||
@ -56,9 +57,9 @@ public:
|
||||
{
|
||||
Uuid cipher;
|
||||
CompressionAlgorithm compressionAlgo;
|
||||
QByteArray transformSeed;
|
||||
quint64 transformRounds;
|
||||
QByteArray transformedMasterKey;
|
||||
QByteArray publicCustomData;
|
||||
QSharedPointer<Kdf> kdf;
|
||||
CompositeKey key;
|
||||
bool hasKey;
|
||||
QByteArray masterSeed;
|
||||
@ -66,7 +67,7 @@ public:
|
||||
};
|
||||
|
||||
Database();
|
||||
~Database();
|
||||
~Database() override;
|
||||
Group* rootGroup();
|
||||
const Group* rootGroup() const;
|
||||
|
||||
@ -90,8 +91,8 @@ public:
|
||||
|
||||
Uuid cipher() const;
|
||||
Database::CompressionAlgorithm compressionAlgo() const;
|
||||
QByteArray transformSeed() const;
|
||||
quint64 transformRounds() const;
|
||||
QSharedPointer<Kdf> kdf() const;
|
||||
QByteArray publicCustomData() const;
|
||||
QByteArray transformedMasterKey() const;
|
||||
const CompositeKey& key() const;
|
||||
QByteArray challengeResponseKey() const;
|
||||
@ -99,22 +100,16 @@ public:
|
||||
|
||||
void setCipher(const Uuid& cipher);
|
||||
void setCompressionAlgo(Database::CompressionAlgorithm algo);
|
||||
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.
|
||||
*/
|
||||
bool setKey(const CompositeKey& key);
|
||||
void setKdf(QSharedPointer<Kdf> kdf);
|
||||
void setPublicCustomData(QByteArray data);
|
||||
bool setKey(const CompositeKey& key, bool updateChangedTime = true,
|
||||
bool updateTransformSalt = false);
|
||||
bool hasKey() const;
|
||||
bool transformKeyWithSeed(const QByteArray& transformSeed);
|
||||
bool verifyKey(const CompositeKey& key) const;
|
||||
void recycleEntry(Entry* entry);
|
||||
void recycleGroup(Group* group);
|
||||
void emptyRecycleBin();
|
||||
void setEmitModified(bool value);
|
||||
void copyAttributesFrom(const Database* other);
|
||||
void merge(const Database* other);
|
||||
QString saveToFile(QString filePath);
|
||||
|
||||
@ -122,6 +117,7 @@ public:
|
||||
* Returns a unique id that is only valid as long as the Database exists.
|
||||
*/
|
||||
Uuid uuid();
|
||||
bool changeKdf(QSharedPointer<Kdf> kdf);
|
||||
|
||||
static Database* databaseByUuid(const Uuid& uuid);
|
||||
static Database* openDatabaseFile(QString fileName, CompositeKey key);
|
||||
|
@ -1,199 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Endian.h"
|
||||
|
||||
#include <QtEndian>
|
||||
#include <QIODevice>
|
||||
|
||||
namespace Endian {
|
||||
|
||||
qint16 bytesToInt16(const QByteArray& ba, QSysInfo::Endian byteOrder)
|
||||
{
|
||||
Q_ASSERT(ba.size() == 2);
|
||||
|
||||
if (byteOrder == QSysInfo::LittleEndian) {
|
||||
return qFromLittleEndian<qint16>(reinterpret_cast<const uchar*>(ba.constData()));
|
||||
}
|
||||
else {
|
||||
return qFromBigEndian<qint16>(reinterpret_cast<const uchar*>(ba.constData()));
|
||||
}
|
||||
}
|
||||
|
||||
qint32 bytesToInt32(const QByteArray& ba, QSysInfo::Endian byteOrder)
|
||||
{
|
||||
Q_ASSERT(ba.size() == 4);
|
||||
|
||||
if (byteOrder == QSysInfo::LittleEndian) {
|
||||
return qFromLittleEndian<qint32>(reinterpret_cast<const uchar*>(ba.constData()));
|
||||
}
|
||||
else {
|
||||
return qFromBigEndian<qint32>(reinterpret_cast<const uchar*>(ba.constData()));
|
||||
}
|
||||
}
|
||||
|
||||
qint64 bytesToInt64(const QByteArray& ba, QSysInfo::Endian byteOrder)
|
||||
{
|
||||
Q_ASSERT(ba.size() == 8);
|
||||
|
||||
if (byteOrder == QSysInfo::LittleEndian) {
|
||||
return qFromLittleEndian<qint64>(reinterpret_cast<const uchar*>(ba.constData()));
|
||||
}
|
||||
else {
|
||||
return qFromBigEndian<qint64>(reinterpret_cast<const uchar*>(ba.constData()));
|
||||
}
|
||||
}
|
||||
|
||||
quint16 bytesToUInt16(const QByteArray& ba, QSysInfo::Endian byteOrder)
|
||||
{
|
||||
return static_cast<quint16>(bytesToInt16(ba, byteOrder));
|
||||
}
|
||||
|
||||
quint32 bytesToUInt32(const QByteArray& ba, QSysInfo::Endian byteOrder)
|
||||
{
|
||||
return static_cast<quint32>(bytesToInt32(ba, byteOrder));
|
||||
}
|
||||
|
||||
quint64 bytesToUInt64(const QByteArray& ba, QSysInfo::Endian byteOrder)
|
||||
{
|
||||
return static_cast<quint64>(bytesToInt64(ba, byteOrder));
|
||||
}
|
||||
|
||||
qint16 readInt16(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok)
|
||||
{
|
||||
QByteArray ba = device->read(2);
|
||||
|
||||
if (ba.size() != 2) {
|
||||
*ok = false;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
*ok = true;
|
||||
return bytesToInt16(ba, byteOrder);
|
||||
}
|
||||
}
|
||||
|
||||
qint32 readInt32(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok)
|
||||
{
|
||||
QByteArray ba = device->read(4);
|
||||
|
||||
if (ba.size() != 4) {
|
||||
*ok = false;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
*ok = true;
|
||||
return bytesToInt32(ba, byteOrder);
|
||||
}
|
||||
}
|
||||
|
||||
qint64 readInt64(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok)
|
||||
{
|
||||
QByteArray ba = device->read(8);
|
||||
|
||||
if (ba.size() != 8) {
|
||||
*ok = false;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
*ok = true;
|
||||
return bytesToInt64(ba, byteOrder);
|
||||
}
|
||||
}
|
||||
|
||||
quint16 readUInt16(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok)
|
||||
{
|
||||
return static_cast<quint16>(readInt16(device, byteOrder, ok));
|
||||
}
|
||||
|
||||
quint32 readUInt32(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok)
|
||||
{
|
||||
return static_cast<quint32>(readInt32(device, byteOrder, ok));
|
||||
}
|
||||
|
||||
quint64 readUInt64(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok)
|
||||
{
|
||||
return static_cast<quint64>(readInt64(device, byteOrder, ok));
|
||||
}
|
||||
|
||||
QByteArray int16ToBytes(qint16 num, QSysInfo::Endian byteOrder)
|
||||
{
|
||||
QByteArray ba;
|
||||
ba.resize(2);
|
||||
|
||||
if (byteOrder == QSysInfo::LittleEndian) {
|
||||
qToLittleEndian<qint16>(num, reinterpret_cast<uchar*>(ba.data()));
|
||||
}
|
||||
else {
|
||||
qToBigEndian<qint64>(num, reinterpret_cast<uchar*>(ba.data()));
|
||||
}
|
||||
|
||||
return ba;
|
||||
}
|
||||
|
||||
QByteArray int32ToBytes(qint32 num, QSysInfo::Endian byteOrder)
|
||||
{
|
||||
QByteArray ba;
|
||||
ba.resize(4);
|
||||
|
||||
if (byteOrder == QSysInfo::LittleEndian) {
|
||||
qToLittleEndian<qint32>(num, reinterpret_cast<uchar*>(ba.data()));
|
||||
}
|
||||
else {
|
||||
qToBigEndian<qint32>(num, reinterpret_cast<uchar*>(ba.data()));
|
||||
}
|
||||
|
||||
return ba;
|
||||
}
|
||||
|
||||
QByteArray int64ToBytes(qint64 num, QSysInfo::Endian byteOrder)
|
||||
{
|
||||
QByteArray ba;
|
||||
ba.resize(8);
|
||||
|
||||
if (byteOrder == QSysInfo::LittleEndian) {
|
||||
qToLittleEndian<qint64>(num, reinterpret_cast<uchar*>(ba.data()));
|
||||
}
|
||||
else {
|
||||
qToBigEndian<qint64>(num, reinterpret_cast<uchar*>(ba.data()));
|
||||
}
|
||||
|
||||
return ba;
|
||||
}
|
||||
|
||||
bool writeInt16(qint16 num, QIODevice* device, QSysInfo::Endian byteOrder)
|
||||
{
|
||||
QByteArray ba = int16ToBytes(num, byteOrder);
|
||||
int bytesWritten = device->write(ba);
|
||||
return (bytesWritten == ba.size());
|
||||
}
|
||||
|
||||
bool writeInt32(qint32 num, QIODevice* device, QSysInfo::Endian byteOrder)
|
||||
{
|
||||
QByteArray ba = int32ToBytes(num, byteOrder);
|
||||
int bytesWritten = device->write(ba);
|
||||
return (bytesWritten == ba.size());
|
||||
}
|
||||
|
||||
bool writeInt64(qint64 num, QIODevice* device, QSysInfo::Endian byteOrder)
|
||||
{
|
||||
QByteArray ba = int64ToBytes(num, byteOrder);
|
||||
int bytesWritten = device->write(ba);
|
||||
return (bytesWritten == ba.size());
|
||||
}
|
||||
|
||||
} // namespace Endian
|
@ -1,4 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -20,32 +21,58 @@
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QSysInfo>
|
||||
#include <QtEndian>
|
||||
#include <QIODevice>
|
||||
|
||||
class QIODevice;
|
||||
namespace Endian
|
||||
{
|
||||
|
||||
namespace Endian {
|
||||
template<typename SizedQInt>
|
||||
SizedQInt bytesToSizedInt(const QByteArray& ba, QSysInfo::Endian byteOrder)
|
||||
{
|
||||
Q_ASSERT(ba.size() == sizeof(SizedQInt));
|
||||
|
||||
qint16 bytesToInt16(const QByteArray& ba, QSysInfo::Endian byteOrder);
|
||||
quint16 bytesToUInt16(const QByteArray& ba, QSysInfo::Endian byteOrder);
|
||||
qint32 bytesToInt32(const QByteArray& ba, QSysInfo::Endian byteOrder);
|
||||
quint32 bytesToUInt32(const QByteArray& ba, QSysInfo::Endian byteOrder);
|
||||
qint64 bytesToInt64(const QByteArray& ba, QSysInfo::Endian byteOrder);
|
||||
quint64 bytesToUInt64(const QByteArray& ba, QSysInfo::Endian byteOrder);
|
||||
if (byteOrder == QSysInfo::LittleEndian) {
|
||||
return qFromLittleEndian<SizedQInt>(reinterpret_cast<const uchar*>(ba.constData()));
|
||||
}
|
||||
return qFromBigEndian<SizedQInt>(reinterpret_cast<const uchar*>(ba.constData()));
|
||||
}
|
||||
|
||||
qint16 readInt16(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok);
|
||||
quint16 readUInt16(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok);
|
||||
qint32 readInt32(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok);
|
||||
quint32 readUInt32(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok);
|
||||
qint64 readInt64(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok);
|
||||
quint64 readUInt64(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok);
|
||||
template<typename SizedQInt>
|
||||
SizedQInt readSizedInt(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok)
|
||||
{
|
||||
QByteArray ba = device->read(sizeof(SizedQInt));
|
||||
|
||||
QByteArray int16ToBytes(qint16 num, QSysInfo::Endian byteOrder);
|
||||
QByteArray int32ToBytes(qint32 num, QSysInfo::Endian byteOrder);
|
||||
QByteArray int64ToBytes(qint64 num, QSysInfo::Endian byteOrder);
|
||||
if (ba.size() != sizeof(SizedQInt)) {
|
||||
*ok = false;
|
||||
return 0;
|
||||
}
|
||||
*ok = true;
|
||||
return bytesToSizedInt<SizedQInt>(ba, byteOrder);
|
||||
}
|
||||
|
||||
bool writeInt16(qint16 num, QIODevice* device, QSysInfo::Endian byteOrder);
|
||||
bool writeInt32(qint32 num, QIODevice* device, QSysInfo::Endian byteOrder);
|
||||
bool writeInt64(qint64 num, QIODevice* device, QSysInfo::Endian byteOrder);
|
||||
template<typename SizedQInt>
|
||||
QByteArray sizedIntToBytes(SizedQInt num, QSysInfo::Endian byteOrder)
|
||||
{
|
||||
QByteArray ba;
|
||||
ba.resize(sizeof(SizedQInt));
|
||||
|
||||
if (byteOrder == QSysInfo::LittleEndian) {
|
||||
qToLittleEndian<SizedQInt>(num, reinterpret_cast<uchar*>(ba.data()));
|
||||
} else {
|
||||
qToBigEndian<SizedQInt>(num, reinterpret_cast<uchar*>(ba.data()));
|
||||
}
|
||||
|
||||
return ba;
|
||||
}
|
||||
|
||||
template<typename SizedQInt>
|
||||
bool writeSizedInt(SizedQInt num, QIODevice* device, QSysInfo::Endian byteOrder)
|
||||
{
|
||||
QByteArray ba = sizedIntToBytes<SizedQInt>(num, byteOrder);
|
||||
qint64 bytesWritten = device->write(ba);
|
||||
return (bytesWritten == ba.size());
|
||||
}
|
||||
|
||||
} // namespace Endian
|
||||
|
||||
|
@ -49,6 +49,7 @@ Metadata::Metadata(QObject* parent)
|
||||
m_recycleBinChanged = now;
|
||||
m_entryTemplatesGroupChanged = now;
|
||||
m_masterKeyChanged = now;
|
||||
m_settingsChanged = now;
|
||||
}
|
||||
|
||||
template <class P, class V> bool Metadata::set(P& property, const V& value)
|
||||
@ -525,3 +526,12 @@ void Metadata::removeCustomField(const QString& key)
|
||||
m_customFields.remove(key);
|
||||
emit modified();
|
||||
}
|
||||
|
||||
QDateTime Metadata::settingsChanged() const {
|
||||
return m_settingsChanged;
|
||||
}
|
||||
|
||||
void Metadata::setSettingsChanged(const QDateTime& value) {
|
||||
Q_ASSERT(value.timeSpec() == Qt::UTC);
|
||||
m_settingsChanged = value;
|
||||
}
|
||||
|
@ -69,6 +69,7 @@ public:
|
||||
QDateTime descriptionChanged() const;
|
||||
QString defaultUserName() const;
|
||||
QDateTime defaultUserNameChanged() const;
|
||||
QDateTime settingsChanged() const;
|
||||
int maintenanceHistoryDays() const;
|
||||
QColor color() const;
|
||||
bool protectTitle() const;
|
||||
@ -108,6 +109,7 @@ public:
|
||||
void setDescriptionChanged(const QDateTime& value);
|
||||
void setDefaultUserName(const QString& value);
|
||||
void setDefaultUserNameChanged(const QDateTime& value);
|
||||
void setSettingsChanged(const QDateTime& value);
|
||||
void setMaintenanceHistoryDays(int value);
|
||||
void setColor(const QColor& value);
|
||||
void setProtectTitle(bool value);
|
||||
@ -141,6 +143,7 @@ public:
|
||||
* - Master key changed date
|
||||
* - Custom icons
|
||||
* - Custom fields
|
||||
* - Settings changed date
|
||||
*/
|
||||
void copyAttributesFrom(const Metadata* other);
|
||||
|
||||
@ -170,6 +173,7 @@ private:
|
||||
QPointer<Group> m_lastTopVisibleGroup;
|
||||
|
||||
QDateTime m_masterKeyChanged;
|
||||
QDateTime m_settingsChanged;
|
||||
|
||||
QHash<QString, QString> m_customFields;
|
||||
|
||||
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Felix Geyer <debfx@fobos.de>
|
||||
* Copyright (C) 2014 Florian Geyer <blueice@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ToDbExporter.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Metadata.h"
|
||||
|
||||
Database* ToDbExporter::exportGroup(Group* group)
|
||||
{
|
||||
Database* oldDb = group->database();
|
||||
Q_ASSERT(oldDb);
|
||||
|
||||
Database* db = new Database();
|
||||
Group* clonedGroup = group->clone(Entry::CloneNewUuid | Entry::CloneIncludeHistory);
|
||||
clonedGroup->setParent(db->rootGroup());
|
||||
|
||||
QSet<Uuid> customIcons = group->customIconsRecursive();
|
||||
db->metadata()->copyCustomIcons(customIcons, oldDb->metadata());
|
||||
|
||||
db->copyAttributesFrom(oldDb);
|
||||
|
||||
return db;
|
||||
}
|
@ -95,18 +95,28 @@ bool Crypto::checkAlgorithms()
|
||||
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
|
||||
return false;
|
||||
}
|
||||
if (gcry_cipher_algo_info(GCRY_CIPHER_CHACHA20, GCRYCTL_TEST_ALGO, nullptr, nullptr) != 0) {
|
||||
m_errorStr = "GCRY_CIPHER_CHACHA20 not found.";
|
||||
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
|
||||
return false;
|
||||
}
|
||||
if (gcry_md_test_algo(GCRY_MD_SHA256) != 0) {
|
||||
m_errorStr = "GCRY_MD_SHA256 not found.";
|
||||
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
|
||||
return false;
|
||||
}
|
||||
if (gcry_md_test_algo(GCRY_MD_SHA512) != 0) {
|
||||
m_errorStr = "GCRY_MD_SHA512 not found.";
|
||||
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Crypto::selfTest()
|
||||
{
|
||||
return testSha256() && testAes256Cbc() && testAes256Ecb() && testTwofish() && testSalsa20();
|
||||
return testSha256() && testSha512() && testAes256Cbc() && testAes256Ecb() && testTwofish() && testSalsa20() && testChaCha20();
|
||||
}
|
||||
|
||||
void Crypto::raiseError(const QString& str)
|
||||
@ -128,6 +138,19 @@ bool Crypto::testSha256()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Crypto::testSha512()
|
||||
{
|
||||
QByteArray sha512Test = CryptoHash::hash("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
|
||||
CryptoHash::Sha512);
|
||||
|
||||
if (sha512Test != QByteArray::fromHex("204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445")) {
|
||||
raiseError("SHA-512 mismatch.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Crypto::testAes256Cbc()
|
||||
{
|
||||
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
|
||||
@ -285,3 +308,30 @@ bool Crypto::testSalsa20()
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Crypto::testChaCha20() {
|
||||
QByteArray chacha20Key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000");
|
||||
QByteArray chacha20iv = QByteArray::fromHex("0000000000000000");
|
||||
QByteArray chacha20Plain = QByteArray::fromHex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
|
||||
QByteArray chacha20Cipher = QByteArray::fromHex("76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586");
|
||||
bool ok;
|
||||
|
||||
SymmetricCipher chacha20Stream(SymmetricCipher::ChaCha20, SymmetricCipher::Stream,
|
||||
SymmetricCipher::Encrypt);
|
||||
if (!chacha20Stream.init(chacha20Key, chacha20iv)) {
|
||||
raiseError(chacha20Stream.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray chacha20Processed = chacha20Stream.process(chacha20Plain, &ok);
|
||||
if (!ok) {
|
||||
raiseError(chacha20Stream.errorString());
|
||||
return false;
|
||||
}
|
||||
if (chacha20Processed != chacha20Cipher) {
|
||||
raiseError("ChaCha20 stream cipher mismatch.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
@ -35,10 +35,12 @@ private:
|
||||
static bool selfTest();
|
||||
static void raiseError(const QString& str);
|
||||
static bool testSha256();
|
||||
static bool testSha512();
|
||||
static bool testAes256Cbc();
|
||||
static bool testAes256Ecb();
|
||||
static bool testTwofish();
|
||||
static bool testSalsa20();
|
||||
static bool testChaCha20();
|
||||
|
||||
static bool m_initalized;
|
||||
static QString m_errorStr;
|
||||
|
@ -28,28 +28,40 @@ public:
|
||||
int hashLen;
|
||||
};
|
||||
|
||||
CryptoHash::CryptoHash(CryptoHash::Algorithm algo)
|
||||
CryptoHash::CryptoHash(Algorithm algo, bool hmac)
|
||||
: d_ptr(new CryptoHashPrivate())
|
||||
{
|
||||
Q_D(CryptoHash);
|
||||
|
||||
Q_ASSERT(Crypto::initalized());
|
||||
|
||||
int algoGcrypt;
|
||||
int algoGcrypt = -1;
|
||||
unsigned int flagsGcrypt = GCRY_MD_FLAG_SECURE;
|
||||
|
||||
switch (algo) {
|
||||
case CryptoHash::Sha256:
|
||||
algoGcrypt = GCRY_MD_SHA256;
|
||||
break;
|
||||
|
||||
case CryptoHash::Sha512:
|
||||
algoGcrypt = GCRY_MD_SHA512;
|
||||
break;
|
||||
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
|
||||
gcry_error_t error = gcry_md_open(&d->ctx, algoGcrypt, 0);
|
||||
if (hmac) {
|
||||
flagsGcrypt |= GCRY_MD_FLAG_HMAC;
|
||||
}
|
||||
|
||||
gcry_error_t error = gcry_md_open(&d->ctx, algoGcrypt, flagsGcrypt);
|
||||
if (error != GPG_ERR_NO_ERROR) {
|
||||
qWarning("Gcrypt error (ctor): %s", gcry_strerror(error));
|
||||
qWarning("Gcrypt error (ctor): %s", gcry_strsource(error));
|
||||
}
|
||||
Q_ASSERT(error == 0); // TODO: error handling
|
||||
Q_UNUSED(error);
|
||||
|
||||
d->hashLen = gcry_md_get_algo_dlen(algoGcrypt);
|
||||
}
|
||||
@ -71,7 +83,19 @@ void CryptoHash::addData(const QByteArray& data)
|
||||
return;
|
||||
}
|
||||
|
||||
gcry_md_write(d->ctx, data.constData(), data.size());
|
||||
gcry_md_write(d->ctx, data.constData(), static_cast<size_t>(data.size()));
|
||||
}
|
||||
|
||||
void CryptoHash::setKey(const QByteArray& data)
|
||||
{
|
||||
Q_D(CryptoHash);
|
||||
|
||||
gcry_error_t error = gcry_md_setkey(d->ctx, data.constData(), static_cast<size_t>(data.size()));
|
||||
if (error) {
|
||||
qWarning("Gcrypt error (setKey): %s", gcry_strerror(error));
|
||||
qWarning("Gcrypt error (setKey): %s", gcry_strsource(error));
|
||||
}
|
||||
Q_ASSERT(error == 0);
|
||||
}
|
||||
|
||||
void CryptoHash::reset()
|
||||
@ -85,14 +109,23 @@ QByteArray CryptoHash::result() const
|
||||
{
|
||||
Q_D(const CryptoHash);
|
||||
|
||||
const char* result = reinterpret_cast<const char*>(gcry_md_read(d->ctx, 0));
|
||||
const auto result = reinterpret_cast<const char*>(gcry_md_read(d->ctx, 0));
|
||||
return QByteArray(result, d->hashLen);
|
||||
}
|
||||
|
||||
QByteArray CryptoHash::hash(const QByteArray& data, CryptoHash::Algorithm algo)
|
||||
QByteArray CryptoHash::hash(const QByteArray& data, Algorithm algo)
|
||||
{
|
||||
// replace with gcry_md_hash_buffer()?
|
||||
CryptoHash cryptoHash(algo);
|
||||
cryptoHash.addData(data);
|
||||
return cryptoHash.result();
|
||||
}
|
||||
|
||||
QByteArray CryptoHash::hmac(const QByteArray& data, const QByteArray& key, Algorithm algo)
|
||||
{
|
||||
// replace with gcry_md_hash_buffer()?
|
||||
CryptoHash cryptoHash(algo, true);
|
||||
cryptoHash.setKey(key);
|
||||
cryptoHash.addData(data);
|
||||
return cryptoHash.result();
|
||||
}
|
||||
|
@ -27,16 +27,19 @@ class CryptoHash
|
||||
public:
|
||||
enum Algorithm
|
||||
{
|
||||
Sha256
|
||||
Sha256,
|
||||
Sha512
|
||||
};
|
||||
|
||||
explicit CryptoHash(CryptoHash::Algorithm algo);
|
||||
explicit CryptoHash(Algorithm algo, bool hmac = false);
|
||||
~CryptoHash();
|
||||
void addData(const QByteArray& data);
|
||||
void reset();
|
||||
QByteArray result() const;
|
||||
void setKey(const QByteArray& data);
|
||||
|
||||
static QByteArray hash(const QByteArray& data, CryptoHash::Algorithm algo);
|
||||
static QByteArray hash(const QByteArray& data, Algorithm algo);
|
||||
static QByteArray hmac(const QByteArray& data, const QByteArray& key, Algorithm algo);
|
||||
|
||||
private:
|
||||
CryptoHashPrivate* const d_ptr;
|
||||
|
@ -20,10 +20,10 @@
|
||||
#include "config-keepassx.h"
|
||||
#include "crypto/SymmetricCipherGcrypt.h"
|
||||
|
||||
SymmetricCipher::SymmetricCipher(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction)
|
||||
SymmetricCipher::SymmetricCipher(Algorithm algo, Mode mode, Direction direction)
|
||||
: m_backend(createBackend(algo, mode, direction))
|
||||
, m_initialized(false)
|
||||
, m_algo(algo)
|
||||
{
|
||||
}
|
||||
|
||||
@ -54,13 +54,13 @@ bool SymmetricCipher::isInitalized() const
|
||||
return m_initialized;
|
||||
}
|
||||
|
||||
SymmetricCipherBackend* SymmetricCipher::createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction)
|
||||
SymmetricCipherBackend* SymmetricCipher::createBackend(Algorithm algo, Mode mode, Direction direction)
|
||||
{
|
||||
switch (algo) {
|
||||
case SymmetricCipher::Aes256:
|
||||
case SymmetricCipher::Twofish:
|
||||
case SymmetricCipher::Salsa20:
|
||||
case Aes256:
|
||||
case Twofish:
|
||||
case Salsa20:
|
||||
case ChaCha20:
|
||||
return new SymmetricCipherGcrypt(algo, mode, direction);
|
||||
|
||||
default:
|
||||
@ -92,19 +92,62 @@ QString SymmetricCipher::errorString() const
|
||||
SymmetricCipher::Algorithm SymmetricCipher::cipherToAlgorithm(Uuid cipher)
|
||||
{
|
||||
if (cipher == KeePass2::CIPHER_AES) {
|
||||
return SymmetricCipher::Aes256;
|
||||
return Aes256;
|
||||
} else if (cipher == KeePass2::CIPHER_CHACHA20) {
|
||||
return ChaCha20;
|
||||
} else if (cipher == KeePass2::CIPHER_TWOFISH) {
|
||||
return Twofish;
|
||||
}
|
||||
else {
|
||||
return SymmetricCipher::Twofish;
|
||||
|
||||
qWarning("SymmetricCipher::cipherToAlgorithm: invalid Uuid %s", cipher.toByteArray().toHex().data());
|
||||
return InvalidAlgorithm;
|
||||
}
|
||||
|
||||
Uuid SymmetricCipher::algorithmToCipher(Algorithm algo)
|
||||
{
|
||||
switch (algo) {
|
||||
case Aes256:
|
||||
return KeePass2::CIPHER_AES;
|
||||
case ChaCha20:
|
||||
return KeePass2::CIPHER_CHACHA20;
|
||||
case Twofish:
|
||||
return KeePass2::CIPHER_TWOFISH;
|
||||
default:
|
||||
qWarning("SymmetricCipher::algorithmToCipher: invalid algorithm %d", algo);
|
||||
return Uuid();
|
||||
}
|
||||
}
|
||||
|
||||
Uuid SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm algo)
|
||||
int SymmetricCipher::algorithmIvSize(Algorithm algo)
|
||||
{
|
||||
switch (algo) {
|
||||
case SymmetricCipher::Aes256:
|
||||
return KeePass2::CIPHER_AES;
|
||||
case ChaCha20:
|
||||
return 12;
|
||||
case Aes256:
|
||||
return 16;
|
||||
case Twofish:
|
||||
return 16;
|
||||
default:
|
||||
return KeePass2::CIPHER_TWOFISH;
|
||||
qWarning("SymmetricCipher::algorithmIvSize: invalid algorithm %d", algo);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
SymmetricCipher::Mode SymmetricCipher::algorithmMode(Algorithm algo)
|
||||
{
|
||||
switch (algo) {
|
||||
case ChaCha20:
|
||||
return Stream;
|
||||
case Aes256:
|
||||
case Twofish:
|
||||
return Cbc;
|
||||
default:
|
||||
qWarning("SymmetricCipher::algorithmMode: invalid algorithm %d", algo);
|
||||
return InvalidMode;
|
||||
}
|
||||
}
|
||||
|
||||
SymmetricCipher::Algorithm SymmetricCipher::algorithm() const
|
||||
{
|
||||
return m_algo;
|
||||
}
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
#include "crypto/SymmetricCipherBackend.h"
|
||||
#include "format/KeePass2.h"
|
||||
#include "core/Uuid.h"
|
||||
|
||||
class SymmetricCipher
|
||||
{
|
||||
@ -32,7 +33,9 @@ public:
|
||||
{
|
||||
Aes256,
|
||||
Twofish,
|
||||
Salsa20
|
||||
Salsa20,
|
||||
ChaCha20,
|
||||
InvalidAlgorithm = -1
|
||||
};
|
||||
|
||||
enum Mode
|
||||
@ -40,7 +43,8 @@ public:
|
||||
Cbc,
|
||||
Ctr,
|
||||
Ecb,
|
||||
Stream
|
||||
Stream,
|
||||
InvalidMode = -1
|
||||
};
|
||||
|
||||
enum Direction
|
||||
@ -49,22 +53,25 @@ public:
|
||||
Encrypt
|
||||
};
|
||||
|
||||
SymmetricCipher(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction);
|
||||
SymmetricCipher(Algorithm algo, Mode mode, Direction direction);
|
||||
~SymmetricCipher();
|
||||
Q_DISABLE_COPY(SymmetricCipher)
|
||||
|
||||
bool init(const QByteArray& key, const QByteArray& iv);
|
||||
bool isInitalized() const;
|
||||
|
||||
inline QByteArray process(const QByteArray& data, bool* ok) {
|
||||
inline QByteArray process(const QByteArray& data, bool* ok)
|
||||
{
|
||||
return m_backend->process(data, ok);
|
||||
}
|
||||
|
||||
Q_REQUIRED_RESULT inline bool processInPlace(QByteArray& data) {
|
||||
Q_REQUIRED_RESULT inline bool processInPlace(QByteArray& data)
|
||||
{
|
||||
return m_backend->processInPlace(data);
|
||||
}
|
||||
|
||||
Q_REQUIRED_RESULT inline bool processInPlace(QByteArray& data, quint64 rounds) {
|
||||
Q_REQUIRED_RESULT inline bool processInPlace(QByteArray& data, quint64 rounds)
|
||||
{
|
||||
Q_ASSERT(rounds > 0);
|
||||
return m_backend->processInPlace(data, rounds);
|
||||
}
|
||||
@ -73,18 +80,19 @@ public:
|
||||
int keySize() const;
|
||||
int blockSize() const;
|
||||
QString errorString() const;
|
||||
Algorithm algorithm() const;
|
||||
|
||||
static SymmetricCipher::Algorithm cipherToAlgorithm(Uuid cipher);
|
||||
static Uuid algorithmToCipher(SymmetricCipher::Algorithm algo);
|
||||
static Algorithm cipherToAlgorithm(Uuid cipher);
|
||||
static Uuid algorithmToCipher(Algorithm algo);
|
||||
static int algorithmIvSize(Algorithm algo);
|
||||
static Mode algorithmMode(Algorithm algo);
|
||||
|
||||
private:
|
||||
static SymmetricCipherBackend* createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction);
|
||||
static SymmetricCipherBackend* createBackend(Algorithm algo, Mode mode, Direction direction);
|
||||
|
||||
const QScopedPointer<SymmetricCipherBackend> m_backend;
|
||||
bool m_initialized;
|
||||
|
||||
Q_DISABLE_COPY(SymmetricCipher)
|
||||
Algorithm m_algo;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_SYMMETRICCIPHER_H
|
||||
|
@ -46,6 +46,9 @@ int SymmetricCipherGcrypt::gcryptAlgo(SymmetricCipher::Algorithm algo)
|
||||
case SymmetricCipher::Salsa20:
|
||||
return GCRY_CIPHER_SALSA20;
|
||||
|
||||
case SymmetricCipher::ChaCha20:
|
||||
return GCRY_CIPHER_CHACHA20;
|
||||
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
return -1;
|
||||
@ -142,8 +145,7 @@ QByteArray SymmetricCipherGcrypt::process(const QByteArray& data, bool* ok)
|
||||
|
||||
if (m_direction == SymmetricCipher::Decrypt) {
|
||||
error = gcry_cipher_decrypt(m_ctx, result.data(), data.size(), data.constData(), data.size());
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error = gcry_cipher_encrypt(m_ctx, result.data(), data.size(), data.constData(), data.size());
|
||||
}
|
||||
|
||||
@ -151,7 +153,7 @@ QByteArray SymmetricCipherGcrypt::process(const QByteArray& data, bool* ok)
|
||||
setErrorString(error);
|
||||
*ok = false;
|
||||
} else {
|
||||
*ok = true;
|
||||
*ok = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -165,8 +167,7 @@ bool SymmetricCipherGcrypt::processInPlace(QByteArray& data)
|
||||
|
||||
if (m_direction == SymmetricCipher::Decrypt) {
|
||||
error = gcry_cipher_decrypt(m_ctx, data.data(), data.size(), nullptr, 0);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error = gcry_cipher_encrypt(m_ctx, data.data(), data.size(), nullptr, 0);
|
||||
}
|
||||
|
||||
@ -196,8 +197,7 @@ bool SymmetricCipherGcrypt::processInPlace(QByteArray& data, quint64 rounds)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
for (quint64 i = 0; i != rounds; ++i) {
|
||||
error = gcry_cipher_encrypt(m_ctx, rawData, size, nullptr, 0);
|
||||
|
||||
|
@ -23,7 +23,7 @@
|
||||
#include "crypto/SymmetricCipher.h"
|
||||
#include "crypto/SymmetricCipherBackend.h"
|
||||
|
||||
class SymmetricCipherGcrypt : public SymmetricCipherBackend
|
||||
class SymmetricCipherGcrypt: public SymmetricCipherBackend
|
||||
{
|
||||
public:
|
||||
SymmetricCipherGcrypt(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
|
@ -1,112 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "SymmetricCipherSalsa20.h"
|
||||
|
||||
SymmetricCipherSalsa20::SymmetricCipherSalsa20(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction)
|
||||
{
|
||||
Q_ASSERT(algo == SymmetricCipher::Salsa20);
|
||||
Q_UNUSED(algo);
|
||||
|
||||
Q_ASSERT(mode == SymmetricCipher::Stream);
|
||||
Q_UNUSED(mode);
|
||||
|
||||
Q_UNUSED(direction);
|
||||
}
|
||||
|
||||
SymmetricCipherSalsa20::~SymmetricCipherSalsa20()
|
||||
{
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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, bool* ok)
|
||||
{
|
||||
Q_ASSERT((data.size() < blockSize()) || ((data.size() % blockSize()) == 0));
|
||||
|
||||
QByteArray result;
|
||||
result.resize(data.size());
|
||||
|
||||
ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast<const u8*>(data.constData()),
|
||||
reinterpret_cast<u8*>(result.data()), data.size());
|
||||
|
||||
*ok = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool SymmetricCipherSalsa20::processInPlace(QByteArray& data, quint64 rounds)
|
||||
{
|
||||
Q_ASSERT((data.size() < blockSize()) || ((data.size() % blockSize()) == 0));
|
||||
|
||||
for (quint64 i = 0; i != rounds; ++i) {
|
||||
ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast<const u8*>(data.constData()),
|
||||
reinterpret_cast<u8*>(data.data()), data.size());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_SYMMETRICCIPHERSALSA20_H
|
||||
#define KEEPASSX_SYMMETRICCIPHERSALSA20_H
|
||||
|
||||
#include "crypto/SymmetricCipher.h"
|
||||
#include "crypto/SymmetricCipherBackend.h"
|
||||
#include "crypto/salsa20/ecrypt-sync.h"
|
||||
|
||||
class SymmetricCipherSalsa20 : public SymmetricCipherBackend
|
||||
{
|
||||
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);
|
||||
bool setKey(const QByteArray& key);
|
||||
bool setIv(const QByteArray& iv);
|
||||
|
||||
QByteArray process(const QByteArray& data, bool* ok);
|
||||
bool processInPlace(QByteArray& data);
|
||||
bool processInPlace(QByteArray& data, quint64 rounds);
|
||||
|
||||
bool reset();
|
||||
int blockSize() const;
|
||||
|
||||
QString errorString() const;
|
||||
|
||||
private:
|
||||
ECRYPT_ctx m_ctx;
|
||||
QByteArray m_key;
|
||||
QByteArray m_iv;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_SYMMETRICCIPHERSALSA20_H
|
128
src/crypto/kdf/AesKdf.cpp
Normal file
128
src/crypto/kdf/AesKdf.cpp
Normal file
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "AesKdf.h"
|
||||
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include "format/KeePass2.h"
|
||||
#include "crypto/CryptoHash.h"
|
||||
|
||||
AesKdf::AesKdf()
|
||||
: Kdf::Kdf(KeePass2::KDF_AES_KDBX4)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param legacyKdbx3 initialize as legacy KDBX3 KDF
|
||||
*/
|
||||
AesKdf::AesKdf(bool legacyKdbx3)
|
||||
: Kdf::Kdf(legacyKdbx3 ? KeePass2::KDF_AES_KDBX3 : KeePass2::KDF_AES_KDBX4)
|
||||
{
|
||||
}
|
||||
|
||||
bool AesKdf::processParameters(const QVariantMap &p)
|
||||
{
|
||||
bool ok;
|
||||
int rounds = p.value(KeePass2::KDFPARAM_AES_ROUNDS).toInt(&ok);
|
||||
if (!ok || !setRounds(rounds)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray seed = p.value(KeePass2::KDFPARAM_AES_SEED).toByteArray();
|
||||
return setSeed(seed);
|
||||
}
|
||||
|
||||
QVariantMap AesKdf::writeParameters()
|
||||
{
|
||||
QVariantMap p;
|
||||
|
||||
// always write old KDBX3 AES-KDF UUID for compatibility with other applications
|
||||
p.insert(KeePass2::KDFPARAM_UUID, KeePass2::KDF_AES_KDBX3.toByteArray());
|
||||
|
||||
p.insert(KeePass2::KDFPARAM_AES_ROUNDS, static_cast<quint64>(rounds()));
|
||||
p.insert(KeePass2::KDFPARAM_AES_SEED, seed());
|
||||
return p;
|
||||
}
|
||||
|
||||
bool AesKdf::transform(const QByteArray& raw, QByteArray& result) const
|
||||
{
|
||||
QByteArray resultLeft;
|
||||
QByteArray resultRight;
|
||||
|
||||
QFuture<bool> future = QtConcurrent::run(transformKeyRaw, raw.left(16), m_seed, m_rounds, &resultLeft);
|
||||
|
||||
bool rightResult = transformKeyRaw(raw.right(16), m_seed, m_rounds, &resultRight);
|
||||
bool leftResult = future.result();
|
||||
|
||||
if (!rightResult || !leftResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray transformed;
|
||||
transformed.append(resultLeft);
|
||||
transformed.append(resultRight);
|
||||
|
||||
result = CryptoHash::hash(transformed, CryptoHash::Sha256);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AesKdf::transformKeyRaw(const QByteArray& key, const QByteArray& seed, int rounds, QByteArray* result)
|
||||
{
|
||||
QByteArray iv(16, 0);
|
||||
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb,
|
||||
SymmetricCipher::Encrypt);
|
||||
if (!cipher.init(seed, iv)) {
|
||||
qWarning("AesKdf::transformKeyRaw: error in SymmetricCipher::init: %s", cipher.errorString().toUtf8().data());
|
||||
return false;
|
||||
}
|
||||
|
||||
*result = key;
|
||||
|
||||
if (!cipher.processInPlace(*result, rounds)) {
|
||||
qWarning("AesKdf::transformKeyRaw: error in SymmetricCipher::processInPlace: %s",
|
||||
cipher.errorString().toUtf8().data());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QSharedPointer<Kdf> AesKdf::clone() const
|
||||
{
|
||||
return QSharedPointer<AesKdf>::create(*this);
|
||||
}
|
||||
|
||||
int AesKdf::benchmarkImpl(int msec) const
|
||||
{
|
||||
QByteArray key = QByteArray(16, '\x7E');
|
||||
QByteArray seed = QByteArray(32, '\x4B');
|
||||
QByteArray iv(16, 0);
|
||||
|
||||
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb, SymmetricCipher::Encrypt);
|
||||
cipher.init(seed, iv);
|
||||
|
||||
quint64 rounds = 1000000;
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
if (!cipher.processInPlace(key, rounds)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return static_cast<int>(rounds * (static_cast<float>(msec) / timer.elapsed()));
|
||||
}
|
44
src/crypto/kdf/AesKdf.h
Normal file
44
src/crypto/kdf/AesKdf.h
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_AESKDF_H
|
||||
#define KEEPASSX_AESKDF_H
|
||||
|
||||
#include "Kdf.h"
|
||||
|
||||
class AesKdf: public Kdf
|
||||
{
|
||||
public:
|
||||
AesKdf();
|
||||
explicit AesKdf(bool legacyKdbx3);
|
||||
|
||||
bool processParameters(const QVariantMap& p) override;
|
||||
QVariantMap writeParameters() override;
|
||||
bool transform(const QByteArray& raw, QByteArray& result) const override;
|
||||
QSharedPointer<Kdf> clone() const override;
|
||||
|
||||
protected:
|
||||
int benchmarkImpl(int msec) const override;
|
||||
|
||||
private:
|
||||
static bool transformKeyRaw(const QByteArray& key,
|
||||
const QByteArray& seed,
|
||||
int rounds,
|
||||
QByteArray* result) Q_REQUIRED_RESULT;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_AESKDF_H
|
198
src/crypto/kdf/Argon2Kdf.cpp
Normal file
198
src/crypto/kdf/Argon2Kdf.cpp
Normal file
@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Argon2Kdf.h"
|
||||
|
||||
#include <QtConcurrent>
|
||||
#include <argon2.h>
|
||||
|
||||
#include "format/KeePass2.h"
|
||||
|
||||
/**
|
||||
* KeePass' Argon2 implementation supports all parameters that are defined in the official specification,
|
||||
* but only the number of iterations, the memory size and the degree of parallelism can be configured by
|
||||
* the user in the database settings dialog. For the other parameters, KeePass chooses reasonable defaults:
|
||||
* a 256-bit salt is generated each time the database is saved, the tag length is 256 bits, no secret key
|
||||
* or associated data. KeePass uses the latest version of Argon2, v1.3.
|
||||
*/
|
||||
Argon2Kdf::Argon2Kdf()
|
||||
: Kdf::Kdf(KeePass2::KDF_ARGON2)
|
||||
, m_version(0x13)
|
||||
, m_memory(1 << 16)
|
||||
, m_parallelism(static_cast<quint32>(QThread::idealThreadCount()))
|
||||
{
|
||||
m_rounds = 1;
|
||||
}
|
||||
|
||||
quint32 Argon2Kdf::version() const
|
||||
{
|
||||
return m_version;
|
||||
}
|
||||
|
||||
bool Argon2Kdf::setVersion(quint32 version)
|
||||
{
|
||||
// MIN=0x10; MAX=0x13)
|
||||
if (version >= 0x10 && version <= 0x13) {
|
||||
m_version = version;
|
||||
return true;
|
||||
}
|
||||
m_version = 0x13;
|
||||
return false;
|
||||
}
|
||||
|
||||
quint64 Argon2Kdf::memory() const
|
||||
{
|
||||
return m_memory;
|
||||
}
|
||||
|
||||
bool Argon2Kdf::setMemory(quint64 kibibytes)
|
||||
{
|
||||
// MIN=8KB; MAX=2,147,483,648KB
|
||||
if (kibibytes >= 8 && kibibytes < (1ULL << 32)) {
|
||||
m_memory = kibibytes;
|
||||
return true;
|
||||
}
|
||||
m_memory = 16;
|
||||
return false;
|
||||
}
|
||||
|
||||
quint32 Argon2Kdf::parallelism() const
|
||||
{
|
||||
return m_parallelism;
|
||||
}
|
||||
|
||||
bool Argon2Kdf::setParallelism(quint32 threads)
|
||||
{
|
||||
// MIN=1; MAX=16,777,215
|
||||
if (threads >= 1 && threads < (1 << 24)) {
|
||||
m_parallelism = threads;
|
||||
return true;
|
||||
}
|
||||
m_parallelism = 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Argon2Kdf::processParameters(const QVariantMap &p)
|
||||
{
|
||||
QByteArray salt = p.value(KeePass2::KDFPARAM_ARGON2_SALT).toByteArray();
|
||||
if (!setSeed(salt)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok;
|
||||
quint32 version = p.value(KeePass2::KDFPARAM_ARGON2_VERSION).toUInt(&ok);
|
||||
if (!ok || !setVersion(version)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
quint32 lanes = p.value(KeePass2::KDFPARAM_ARGON2_PARALLELISM).toUInt(&ok);
|
||||
if (!ok || !setParallelism(lanes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
quint64 memory = p.value(KeePass2::KDFPARAM_ARGON2_MEMORY).toULongLong(&ok) / 1024ULL;
|
||||
if (!ok || !setMemory(memory)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
quint64 iterations = p.value(KeePass2::KDFPARAM_ARGON2_ITERATIONS).toULongLong(&ok);
|
||||
if (!ok || !setRounds(iterations)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* KeePass2 does not currently implement these parameters
|
||||
*
|
||||
QByteArray secret = p.value(KeePass2::KDFPARAM_ARGON2_SECRET).toByteArray();
|
||||
if (!argon2Kdf->setSecret(secret)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QByteArray ad = p.value(KeePass2::KDFPARAM_ARGON2_ASSOCDATA).toByteArray();
|
||||
if (!argon2Kdf->setAssocData(ad)) {
|
||||
return nullptr;
|
||||
}
|
||||
*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariantMap Argon2Kdf::writeParameters()
|
||||
{
|
||||
QVariantMap p;
|
||||
p.insert(KeePass2::KDFPARAM_UUID, KeePass2::KDF_ARGON2.toByteArray());
|
||||
p.insert(KeePass2::KDFPARAM_ARGON2_VERSION, version());
|
||||
p.insert(KeePass2::KDFPARAM_ARGON2_PARALLELISM, parallelism());
|
||||
p.insert(KeePass2::KDFPARAM_ARGON2_MEMORY, memory() * 1024);
|
||||
p.insert(KeePass2::KDFPARAM_ARGON2_ITERATIONS, static_cast<quint64>(rounds()));
|
||||
p.insert(KeePass2::KDFPARAM_ARGON2_SALT, seed());
|
||||
|
||||
/* KeePass2 does not currently implement these
|
||||
*
|
||||
if (!assocData().isEmpty()) {
|
||||
p.insert(KeePass2::KDFPARAM_ARGON2_ASSOCDATA, argon2Kdf.assocData());
|
||||
}
|
||||
|
||||
if (!secret().isEmpty()) {
|
||||
p.insert(KeePass2::KDFPARAM_ARGON2_SECRET, argon2Kdf.secret());
|
||||
}
|
||||
*/
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
bool Argon2Kdf::transform(const QByteArray& raw, QByteArray& result) const
|
||||
{
|
||||
result.clear();
|
||||
result.resize(32);
|
||||
return transformKeyRaw(raw, seed(), version(), rounds(), memory(), parallelism(), result);
|
||||
}
|
||||
|
||||
bool Argon2Kdf::transformKeyRaw(const QByteArray& key, const QByteArray& seed, quint32 version,
|
||||
quint32 rounds, quint64 memory, quint32 parallelism, QByteArray& result)
|
||||
{
|
||||
// Time Cost, Mem Cost, Threads/Lanes, Password, length, Salt, length, out, length
|
||||
int rc = argon2_hash(rounds, memory, parallelism, key.data(), key.size(),
|
||||
seed.data(), seed.size(), result.data(), result.size(),
|
||||
nullptr, 0, Argon2_d, version);
|
||||
if (rc != ARGON2_OK) {
|
||||
qWarning("Argon2 error: %s", argon2_error_message(rc));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QSharedPointer<Kdf> Argon2Kdf::clone() const
|
||||
{
|
||||
return QSharedPointer<Argon2Kdf>::create(*this);
|
||||
}
|
||||
|
||||
int Argon2Kdf::benchmarkImpl(int msec) const
|
||||
{
|
||||
QByteArray key = QByteArray(16, '\x7E');
|
||||
QByteArray seed = QByteArray(32, '\x4B');
|
||||
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
int rounds = 4;
|
||||
if (transformKeyRaw(key, seed, version(), rounds, memory(), parallelism(), key)) {
|
||||
return static_cast<int>(rounds * (static_cast<float>(msec) / timer.elapsed()));
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
56
src/crypto/kdf/Argon2Kdf.h
Normal file
56
src/crypto/kdf/Argon2Kdf.h
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_ARGON2KDF_H
|
||||
#define KEEPASSX_ARGON2KDF_H
|
||||
|
||||
#include "Kdf.h"
|
||||
|
||||
class Argon2Kdf : public Kdf {
|
||||
public:
|
||||
Argon2Kdf();
|
||||
|
||||
bool processParameters(const QVariantMap& p) override;
|
||||
QVariantMap writeParameters() override;
|
||||
bool transform(const QByteArray& raw, QByteArray& result) const override;
|
||||
QSharedPointer<Kdf> clone() const override;
|
||||
|
||||
quint32 version() const;
|
||||
bool setVersion(quint32 version);
|
||||
quint64 memory() const;
|
||||
bool setMemory(quint64 kibibytes);
|
||||
quint32 parallelism() const;
|
||||
bool setParallelism(quint32 threads);
|
||||
|
||||
protected:
|
||||
int benchmarkImpl(int msec) const override;
|
||||
|
||||
quint32 m_version;
|
||||
quint64 m_memory;
|
||||
quint32 m_parallelism;
|
||||
|
||||
private:
|
||||
static bool transformKeyRaw(const QByteArray& key,
|
||||
const QByteArray& seed,
|
||||
quint32 version,
|
||||
quint32 rounds,
|
||||
quint64 memory,
|
||||
quint32 parallelism,
|
||||
QByteArray& result) Q_REQUIRED_RESULT;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_ARGON2KDF_H
|
100
src/crypto/kdf/Kdf.cpp
Normal file
100
src/crypto/kdf/Kdf.cpp
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Kdf.h"
|
||||
#include "Kdf_p.h"
|
||||
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include "crypto/Random.h"
|
||||
|
||||
Kdf::Kdf(Uuid uuid)
|
||||
: m_rounds(KDF_DEFAULT_ROUNDS)
|
||||
, m_seed(QByteArray(KDF_DEFAULT_SEED_SIZE, 0))
|
||||
, m_uuid(uuid)
|
||||
{
|
||||
}
|
||||
|
||||
Uuid Kdf::uuid() const
|
||||
{
|
||||
return m_uuid;
|
||||
}
|
||||
|
||||
int Kdf::rounds() const
|
||||
{
|
||||
return m_rounds;
|
||||
}
|
||||
|
||||
QByteArray Kdf::seed() const
|
||||
{
|
||||
return m_seed;
|
||||
}
|
||||
|
||||
bool Kdf::setRounds(int rounds)
|
||||
{
|
||||
if (rounds >= 1 && rounds < INT_MAX) {
|
||||
m_rounds = rounds;
|
||||
return true;
|
||||
}
|
||||
m_rounds = 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Kdf::setSeed(const QByteArray& seed)
|
||||
{
|
||||
if (seed.size() != m_seed.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_seed = seed;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Kdf::randomizeSeed()
|
||||
{
|
||||
setSeed(randomGen()->randomArray(m_seed.size()));
|
||||
}
|
||||
|
||||
int Kdf::benchmark(int msec) const
|
||||
{
|
||||
BenchmarkThread thread1(msec, this);
|
||||
BenchmarkThread thread2(msec, this);
|
||||
|
||||
thread1.start();
|
||||
thread2.start();
|
||||
|
||||
thread1.wait();
|
||||
thread2.wait();
|
||||
|
||||
return qMax(1, qMin(thread1.rounds(), thread2.rounds()));
|
||||
}
|
||||
|
||||
Kdf::BenchmarkThread::BenchmarkThread(int msec, const Kdf* kdf)
|
||||
: m_msec(msec)
|
||||
, m_kdf(kdf)
|
||||
{
|
||||
}
|
||||
|
||||
int Kdf::BenchmarkThread::rounds()
|
||||
{
|
||||
return m_rounds;
|
||||
}
|
||||
|
||||
void Kdf::BenchmarkThread::run()
|
||||
{
|
||||
m_rounds = m_kdf->benchmarkImpl(m_msec);
|
||||
}
|
61
src/crypto/kdf/Kdf.h
Normal file
61
src/crypto/kdf/Kdf.h
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_KDF_H
|
||||
#define KEEPASSX_KDF_H
|
||||
|
||||
#include <QVariant>
|
||||
|
||||
#include "core/Uuid.h"
|
||||
|
||||
#define KDF_DEFAULT_SEED_SIZE 32
|
||||
#define KDF_DEFAULT_ROUNDS 1000000ull
|
||||
|
||||
class Kdf
|
||||
{
|
||||
public:
|
||||
explicit Kdf(Uuid uuid);
|
||||
virtual ~Kdf() = default;
|
||||
|
||||
Uuid uuid() const;
|
||||
|
||||
int rounds() const;
|
||||
virtual bool setRounds(int rounds);
|
||||
QByteArray seed() const;
|
||||
virtual bool setSeed(const QByteArray& seed);
|
||||
virtual void randomizeSeed();
|
||||
|
||||
virtual bool processParameters(const QVariantMap& p) = 0;
|
||||
virtual QVariantMap writeParameters() = 0;
|
||||
virtual bool transform(const QByteArray& raw, QByteArray& result) const = 0;
|
||||
virtual QSharedPointer<Kdf> clone() const = 0;
|
||||
|
||||
int benchmark(int msec) const;
|
||||
|
||||
protected:
|
||||
virtual int benchmarkImpl(int msec) const = 0;
|
||||
|
||||
int m_rounds;
|
||||
QByteArray m_seed;
|
||||
|
||||
private:
|
||||
class BenchmarkThread;
|
||||
const Uuid m_uuid;
|
||||
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_KDF_H
|
43
src/crypto/kdf/Kdf_p.h
Normal file
43
src/crypto/kdf/Kdf_p.h
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Kdf.h"
|
||||
|
||||
#include <QThread>
|
||||
|
||||
#ifndef KEEPASSXC_KDF_P_H
|
||||
#define KEEPASSXC_KDF_P_H
|
||||
|
||||
class Kdf::BenchmarkThread: public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BenchmarkThread(int msec, const Kdf* kdf);
|
||||
|
||||
int rounds();
|
||||
|
||||
protected:
|
||||
void run();
|
||||
|
||||
private:
|
||||
int m_rounds;
|
||||
int m_msec;
|
||||
const Kdf* m_kdf;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_KDF_P_H
|
218
src/format/Kdbx3Reader.cpp
Normal file
218
src/format/Kdbx3Reader.cpp
Normal file
@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Kdbx3Reader.h"
|
||||
|
||||
#include "core/Group.h"
|
||||
#include "core/Endian.h"
|
||||
#include "crypto/CryptoHash.h"
|
||||
#include "format/KeePass2RandomStream.h"
|
||||
#include "format/KdbxXmlReader.h"
|
||||
#include "streams/HashedBlockStream.h"
|
||||
#include "streams/QtIOCompressor"
|
||||
#include "streams/SymmetricCipherStream.h"
|
||||
|
||||
#include <QBuffer>
|
||||
|
||||
Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device, const QByteArray& headerData,
|
||||
const CompositeKey& key, bool keepDatabase)
|
||||
{
|
||||
Q_ASSERT(m_kdbxVersion <= KeePass2::FILE_VERSION_3);
|
||||
|
||||
if (hasError()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// check if all required headers were present
|
||||
if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty()
|
||||
|| m_streamStartBytes.isEmpty() || m_protectedStreamKey.isEmpty()
|
||||
|| m_db->cipher().isNull()) {
|
||||
raiseError("missing database headers");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!m_db->setKey(key, false)) {
|
||||
raiseError(tr("Unable to calculate master key"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!m_db->challengeMasterSeed(m_masterSeed)) {
|
||||
raiseError(tr("Unable to issue challenge-response."));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CryptoHash hash(CryptoHash::Sha256);
|
||||
hash.addData(m_masterSeed);
|
||||
hash.addData(m_db->challengeResponseKey());
|
||||
hash.addData(m_db->transformedMasterKey());
|
||||
QByteArray finalKey = hash.result();
|
||||
|
||||
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(m_db->cipher());
|
||||
SymmetricCipherStream cipherStream(device, cipher,
|
||||
SymmetricCipher::algorithmMode(cipher), SymmetricCipher::Decrypt);
|
||||
if (!cipherStream.init(finalKey, m_encryptionIV)) {
|
||||
raiseError(cipherStream.errorString());
|
||||
return nullptr;
|
||||
}
|
||||
if (!cipherStream.open(QIODevice::ReadOnly)) {
|
||||
raiseError(cipherStream.errorString());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QByteArray realStart = cipherStream.read(32);
|
||||
|
||||
if (realStart != m_streamStartBytes) {
|
||||
raiseError(tr("Wrong key or database file is corrupt."));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
HashedBlockStream hashedStream(&cipherStream);
|
||||
if (!hashedStream.open(QIODevice::ReadOnly)) {
|
||||
raiseError(hashedStream.errorString());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QIODevice* xmlDevice = nullptr;
|
||||
QScopedPointer<QtIOCompressor> ioCompressor;
|
||||
|
||||
if (m_db->compressionAlgo() == Database::CompressionNone) {
|
||||
xmlDevice = &hashedStream;
|
||||
} else {
|
||||
ioCompressor.reset(new QtIOCompressor(&hashedStream));
|
||||
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
|
||||
if (!ioCompressor->open(QIODevice::ReadOnly)) {
|
||||
raiseError(ioCompressor->errorString());
|
||||
return nullptr;
|
||||
}
|
||||
xmlDevice = ioCompressor.data();
|
||||
}
|
||||
|
||||
KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::Salsa20);
|
||||
if (!randomStream.init(m_protectedStreamKey)) {
|
||||
raiseError(randomStream.errorString());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QBuffer buffer;
|
||||
if (saveXml()) {
|
||||
m_xmlData = xmlDevice->readAll();
|
||||
buffer.setBuffer(&m_xmlData);
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
xmlDevice = &buffer;
|
||||
}
|
||||
|
||||
Q_ASSERT(xmlDevice);
|
||||
|
||||
KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_3);
|
||||
xmlReader.readDatabase(xmlDevice, m_db.data(), &randomStream);
|
||||
|
||||
if (xmlReader.hasError()) {
|
||||
raiseError(xmlReader.errorString());
|
||||
if (keepDatabase) {
|
||||
return m_db.take();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Q_ASSERT(!xmlReader.headerHash().isEmpty() || m_kdbxVersion < KeePass2::FILE_VERSION_3);
|
||||
|
||||
if (!xmlReader.headerHash().isEmpty()) {
|
||||
QByteArray headerHash = CryptoHash::hash(headerData, CryptoHash::Sha256);
|
||||
if (headerHash != xmlReader.headerHash()) {
|
||||
raiseError("Header doesn't match hash");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return m_db.take();
|
||||
}
|
||||
|
||||
bool Kdbx3Reader::readHeaderField(StoreDataStream& headerStream)
|
||||
{
|
||||
QByteArray fieldIDArray = headerStream.read(1);
|
||||
if (fieldIDArray.size() != 1) {
|
||||
raiseError("Invalid header id size");
|
||||
return false;
|
||||
}
|
||||
char fieldID = fieldIDArray.at(0);
|
||||
|
||||
bool ok;
|
||||
auto fieldLen = Endian::readSizedInt<quint16>(&headerStream, KeePass2::BYTEORDER, &ok);
|
||||
if (!ok) {
|
||||
raiseError("Invalid header field length");
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray fieldData;
|
||||
if (fieldLen != 0) {
|
||||
fieldData = headerStream.read(fieldLen);
|
||||
if (fieldData.size() != fieldLen) {
|
||||
raiseError("Invalid header data length");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool headerEnd = false;
|
||||
switch (static_cast<KeePass2::HeaderFieldID>(fieldID)) {
|
||||
case KeePass2::HeaderFieldID::EndOfHeader:
|
||||
headerEnd = true;
|
||||
break;
|
||||
|
||||
case KeePass2::HeaderFieldID::CipherID:
|
||||
setCipher(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::HeaderFieldID::CompressionFlags:
|
||||
setCompressionFlags(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::HeaderFieldID::MasterSeed:
|
||||
setMasterSeed(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::HeaderFieldID::TransformSeed:
|
||||
setTransformSeed(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::HeaderFieldID::TransformRounds:
|
||||
setTransformRounds(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::HeaderFieldID::EncryptionIV:
|
||||
setEncryptionIV(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::HeaderFieldID::ProtectedStreamKey:
|
||||
setProtectedStreamKey(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::HeaderFieldID::StreamStartBytes:
|
||||
setStreamStartBytes(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::HeaderFieldID::InnerRandomStreamID:
|
||||
setInnerRandomStreamID(fieldData);
|
||||
break;
|
||||
|
||||
default:
|
||||
qWarning("Unknown header field read: id=%d", fieldID);
|
||||
break;
|
||||
}
|
||||
|
||||
return !headerEnd;
|
||||
}
|
37
src/format/Kdbx3Reader.h
Normal file
37
src/format/Kdbx3Reader.h
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_KDBX3READER_H
|
||||
#define KEEPASSX_KDBX3READER_H
|
||||
|
||||
#include "format/KdbxReader.h"
|
||||
|
||||
/**
|
||||
* KDBX 2/3 reader implementation.
|
||||
*/
|
||||
class Kdbx3Reader: public KdbxReader
|
||||
{
|
||||
public:
|
||||
Database* readDatabaseImpl(QIODevice* device, const QByteArray& headerData,
|
||||
const CompositeKey& key, bool keepDatabase) override;
|
||||
|
||||
protected:
|
||||
bool readHeaderField(StoreDataStream& headerStream) override;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_KDBX3READER_H
|
157
src/format/Kdbx3Writer.cpp
Normal file
157
src/format/Kdbx3Writer.cpp
Normal file
@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Kdbx3Writer.h"
|
||||
|
||||
#include <QBuffer>
|
||||
|
||||
#include "core/Database.h"
|
||||
#include "crypto/CryptoHash.h"
|
||||
#include "crypto/Random.h"
|
||||
#include "format/KeePass2.h"
|
||||
#include "format/KeePass2RandomStream.h"
|
||||
#include "format/KdbxXmlWriter.h"
|
||||
#include "streams/HashedBlockStream.h"
|
||||
#include "streams/QtIOCompressor"
|
||||
#include "streams/SymmetricCipherStream.h"
|
||||
|
||||
bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
|
||||
{
|
||||
m_error = false;
|
||||
m_errorStr.clear();
|
||||
|
||||
QByteArray masterSeed = randomGen()->randomArray(32);
|
||||
QByteArray encryptionIV = randomGen()->randomArray(16);
|
||||
QByteArray protectedStreamKey = randomGen()->randomArray(32);
|
||||
QByteArray startBytes = randomGen()->randomArray(32);
|
||||
QByteArray endOfHeader = "\r\n\r\n";
|
||||
|
||||
if (!db->challengeMasterSeed(masterSeed)) {
|
||||
raiseError(tr("Unable to issue challenge-response."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!db->setKey(db->key(), false, true)) {
|
||||
raiseError(tr("Unable to calculate master key"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// generate transformed master key
|
||||
CryptoHash hash(CryptoHash::Sha256);
|
||||
hash.addData(masterSeed);
|
||||
hash.addData(db->challengeResponseKey());
|
||||
Q_ASSERT(!db->transformedMasterKey().isEmpty());
|
||||
hash.addData(db->transformedMasterKey());
|
||||
QByteArray finalKey = hash.result();
|
||||
|
||||
// write header
|
||||
QBuffer header;
|
||||
header.open(QIODevice::WriteOnly);
|
||||
|
||||
writeMagicNumbers(&header, KeePass2::SIGNATURE_1, KeePass2::SIGNATURE_2, KeePass2::FILE_VERSION_3);
|
||||
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toByteArray()));
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::CompressionFlags,
|
||||
Endian::sizedIntToBytes<qint32>(db->compressionAlgo(),
|
||||
KeePass2::BYTEORDER)));
|
||||
auto kdf = db->kdf();
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::MasterSeed, masterSeed));
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::TransformSeed, kdf->seed()));
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::TransformRounds,
|
||||
Endian::sizedIntToBytes<qint64>(kdf->rounds(),
|
||||
KeePass2::BYTEORDER)));
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::EncryptionIV, encryptionIV));
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::ProtectedStreamKey, protectedStreamKey));
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::StreamStartBytes, startBytes));
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::InnerRandomStreamID,
|
||||
Endian::sizedIntToBytes<qint32>(static_cast<qint32>(
|
||||
KeePass2::ProtectedStreamAlgo::Salsa20),
|
||||
KeePass2::BYTEORDER)));
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::EndOfHeader, endOfHeader));
|
||||
header.close();
|
||||
|
||||
// write header data
|
||||
CHECK_RETURN_FALSE(writeData(device, header.data()));
|
||||
|
||||
// hash header
|
||||
const QByteArray headerHash = CryptoHash::hash(header.data(), CryptoHash::Sha256);
|
||||
|
||||
// write cipher stream
|
||||
SymmetricCipher::Algorithm algo = SymmetricCipher::cipherToAlgorithm(db->cipher());
|
||||
SymmetricCipherStream cipherStream(device, algo,
|
||||
SymmetricCipher::algorithmMode(algo), SymmetricCipher::Encrypt);
|
||||
cipherStream.init(finalKey, encryptionIV);
|
||||
if (!cipherStream.open(QIODevice::WriteOnly)) {
|
||||
raiseError(cipherStream.errorString());
|
||||
return false;
|
||||
}
|
||||
CHECK_RETURN_FALSE(writeData(&cipherStream, startBytes));
|
||||
|
||||
HashedBlockStream hashedStream(&cipherStream);
|
||||
if (!hashedStream.open(QIODevice::WriteOnly)) {
|
||||
raiseError(hashedStream.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
QIODevice* outputDevice = nullptr;
|
||||
QScopedPointer<QtIOCompressor> ioCompressor;
|
||||
|
||||
if (db->compressionAlgo() == Database::CompressionNone) {
|
||||
outputDevice = &hashedStream;
|
||||
} else {
|
||||
ioCompressor.reset(new QtIOCompressor(&hashedStream));
|
||||
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
|
||||
if (!ioCompressor->open(QIODevice::WriteOnly)) {
|
||||
raiseError(ioCompressor->errorString());
|
||||
return false;
|
||||
}
|
||||
outputDevice = ioCompressor.data();
|
||||
}
|
||||
|
||||
Q_ASSERT(outputDevice);
|
||||
|
||||
KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::Salsa20);
|
||||
if (!randomStream.init(protectedStreamKey)) {
|
||||
raiseError(randomStream.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
KdbxXmlWriter xmlWriter(KeePass2::FILE_VERSION_3);
|
||||
xmlWriter.writeDatabase(outputDevice, db, &randomStream, headerHash);
|
||||
|
||||
// Explicitly close/reset streams so they are flushed and we can detect
|
||||
// errors. QIODevice::close() resets errorString() etc.
|
||||
if (ioCompressor) {
|
||||
ioCompressor->close();
|
||||
}
|
||||
if (!hashedStream.reset()) {
|
||||
raiseError(hashedStream.errorString());
|
||||
return false;
|
||||
}
|
||||
if (!cipherStream.reset()) {
|
||||
raiseError(cipherStream.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (xmlWriter.hasError()) {
|
||||
raiseError(xmlWriter.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Felix Geyer <debfx@fobos.de>
|
||||
* Copyright (C) 2014 Florian Geyer <blueice@fobos.de>
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -16,18 +15,18 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_TODBEXPORTER_H
|
||||
#define KEEPASSX_TODBEXPORTER_H
|
||||
#ifndef KEEPASSX_KDBX3WRITER_H
|
||||
#define KEEPASSX_KDBX3WRITER_H
|
||||
|
||||
#include "core/Exporter.h"
|
||||
#include "KdbxWriter.h"
|
||||
|
||||
class Database;
|
||||
class Group;
|
||||
|
||||
class ToDbExporter : Exporter
|
||||
/**
|
||||
* KDBX2/3 writer implementation.
|
||||
*/
|
||||
class Kdbx3Writer: public KdbxWriter
|
||||
{
|
||||
public:
|
||||
Database* exportGroup(Group* group);
|
||||
bool writeDatabase(QIODevice* device, Database* db) override;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_TODBEXPORTER_H
|
||||
#endif // KEEPASSX_KDBX3WRITER_H
|
@ -1,4 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -15,8 +16,8 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_KEEPASS2XMLWRITER_H
|
||||
#define KEEPASSX_KEEPASS2XMLWRITER_H
|
||||
#ifndef KEEPASSX_KDBX3XMLWRITER_H
|
||||
#define KEEPASSX_KDBX3XMLWRITER_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QDateTime>
|
||||
@ -32,10 +33,10 @@
|
||||
class KeePass2RandomStream;
|
||||
class Metadata;
|
||||
|
||||
class KeePass2XmlWriter
|
||||
class Kdbx3XmlWriter
|
||||
{
|
||||
public:
|
||||
KeePass2XmlWriter();
|
||||
Kdbx3XmlWriter();
|
||||
void writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr,
|
||||
const QByteArray& headerHash = QByteArray());
|
||||
void writeDatabase(const QString& filename, Database* db);
|
||||
@ -87,4 +88,4 @@ private:
|
||||
QString m_errorStr;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_KEEPASS2XMLWRITER_H
|
||||
#endif // KEEPASSX_KDBX3XMLWRITER_H
|
411
src/format/Kdbx4Reader.cpp
Normal file
411
src/format/Kdbx4Reader.cpp
Normal file
@ -0,0 +1,411 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Kdbx4Reader.h"
|
||||
|
||||
#include <QBuffer>
|
||||
|
||||
#include "core/Group.h"
|
||||
#include "core/Endian.h"
|
||||
#include "crypto/CryptoHash.h"
|
||||
#include "crypto/kdf/AesKdf.h"
|
||||
#include "format/KeePass2RandomStream.h"
|
||||
#include "format/KdbxXmlReader.h"
|
||||
#include "streams/HmacBlockStream.h"
|
||||
#include "streams/QtIOCompressor"
|
||||
#include "streams/SymmetricCipherStream.h"
|
||||
|
||||
Database* Kdbx4Reader::readDatabaseImpl(QIODevice* device, const QByteArray& headerData,
|
||||
const CompositeKey& key, bool keepDatabase)
|
||||
{
|
||||
Q_ASSERT(m_kdbxVersion == KeePass2::FILE_VERSION_4);
|
||||
|
||||
m_binaryPool.clear();
|
||||
|
||||
if (hasError()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// check if all required headers were present
|
||||
if (m_masterSeed.isEmpty()
|
||||
|| m_encryptionIV.isEmpty()
|
||||
|| m_db->cipher().isNull()) {
|
||||
raiseError(tr("missing database headers"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!m_db->setKey(key, false, false)) {
|
||||
raiseError(tr("Unable to calculate master key"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CryptoHash hash(CryptoHash::Sha256);
|
||||
hash.addData(m_masterSeed);
|
||||
hash.addData(m_db->transformedMasterKey());
|
||||
QByteArray finalKey = hash.result();
|
||||
|
||||
QByteArray headerSha256 = device->read(32);
|
||||
QByteArray headerHmac = device->read(32);
|
||||
if (headerSha256.size() != 32 || headerHmac.size() != 32) {
|
||||
raiseError(tr("Invalid header checksum size"));
|
||||
return nullptr;
|
||||
}
|
||||
if (headerSha256 != CryptoHash::hash(headerData, CryptoHash::Sha256)) {
|
||||
raiseError(tr("Header SHA256 mismatch"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QByteArray hmacKey = KeePass2::hmacKey(m_masterSeed, m_db->transformedMasterKey());
|
||||
if (headerHmac != CryptoHash::hmac(headerData,
|
||||
HmacBlockStream::getHmacKey(UINT64_MAX, hmacKey), CryptoHash::Sha256)) {
|
||||
raiseError(tr("Wrong key or database file is corrupt. (HMAC mismatch)"));
|
||||
return nullptr;
|
||||
}
|
||||
HmacBlockStream hmacStream(device, hmacKey);
|
||||
if (!hmacStream.open(QIODevice::ReadOnly)) {
|
||||
raiseError(hmacStream.errorString());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(m_db->cipher());
|
||||
if (cipher == SymmetricCipher::InvalidAlgorithm) {
|
||||
raiseError(tr("Unknown cipher"));
|
||||
return nullptr;
|
||||
}
|
||||
SymmetricCipherStream cipherStream(&hmacStream, cipher,
|
||||
SymmetricCipher::algorithmMode(cipher), SymmetricCipher::Decrypt);
|
||||
if (!cipherStream.init(finalKey, m_encryptionIV)) {
|
||||
raiseError(cipherStream.errorString());
|
||||
return nullptr;
|
||||
}
|
||||
if (!cipherStream.open(QIODevice::ReadOnly)) {
|
||||
raiseError(cipherStream.errorString());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QIODevice* xmlDevice = nullptr;
|
||||
QScopedPointer<QtIOCompressor> ioCompressor;
|
||||
|
||||
if (m_db->compressionAlgo() == Database::CompressionNone) {
|
||||
xmlDevice = &cipherStream;
|
||||
} else {
|
||||
ioCompressor.reset(new QtIOCompressor(&cipherStream));
|
||||
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
|
||||
if (!ioCompressor->open(QIODevice::ReadOnly)) {
|
||||
raiseError(ioCompressor->errorString());
|
||||
return nullptr;
|
||||
}
|
||||
xmlDevice = ioCompressor.data();
|
||||
}
|
||||
|
||||
while (readInnerHeaderField(xmlDevice) && !hasError()) {
|
||||
}
|
||||
|
||||
if (hasError()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
KeePass2RandomStream randomStream(m_irsAlgo);
|
||||
if (!randomStream.init(m_protectedStreamKey)) {
|
||||
raiseError(randomStream.errorString());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QBuffer buffer;
|
||||
if (saveXml()) {
|
||||
m_xmlData = xmlDevice->readAll();
|
||||
buffer.setBuffer(&m_xmlData);
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
xmlDevice = &buffer;
|
||||
}
|
||||
|
||||
Q_ASSERT(xmlDevice);
|
||||
|
||||
KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_4, m_binaryPool);
|
||||
xmlReader.readDatabase(xmlDevice, m_db.data(), &randomStream);
|
||||
|
||||
if (xmlReader.hasError()) {
|
||||
raiseError(xmlReader.errorString());
|
||||
if (keepDatabase) {
|
||||
return m_db.take();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return m_db.take();
|
||||
}
|
||||
|
||||
bool Kdbx4Reader::readHeaderField(StoreDataStream& device)
|
||||
{
|
||||
QByteArray fieldIDArray = device.read(1);
|
||||
if (fieldIDArray.size() != 1) {
|
||||
raiseError(tr("Invalid header id size"));
|
||||
return false;
|
||||
}
|
||||
char fieldID = fieldIDArray.at(0);
|
||||
|
||||
bool ok;
|
||||
auto fieldLen = Endian::readSizedInt<quint32>(&device, KeePass2::BYTEORDER, &ok);
|
||||
if (!ok) {
|
||||
raiseError(tr("Invalid header field length"));
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray fieldData;
|
||||
if (fieldLen != 0) {
|
||||
fieldData = device.read(fieldLen);
|
||||
if (static_cast<quint32>(fieldData.size()) != fieldLen) {
|
||||
raiseError(tr("Invalid header data length"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
switch (static_cast<KeePass2::HeaderFieldID>(fieldID)) {
|
||||
case KeePass2::HeaderFieldID::EndOfHeader:
|
||||
return false;
|
||||
|
||||
case KeePass2::HeaderFieldID::CipherID:
|
||||
setCipher(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::HeaderFieldID::CompressionFlags:
|
||||
setCompressionFlags(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::HeaderFieldID::MasterSeed:
|
||||
setMasterSeed(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::HeaderFieldID::EncryptionIV:
|
||||
setEncryptionIV(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::HeaderFieldID::KdfParameters: {
|
||||
QBuffer bufIoDevice(&fieldData);
|
||||
if (!bufIoDevice.open(QIODevice::ReadOnly)) {
|
||||
raiseError(tr("Failed to open buffer for KDF parameters in header"));
|
||||
return false;
|
||||
}
|
||||
QVariantMap kdfParams = readVariantMap(&bufIoDevice);
|
||||
QSharedPointer<Kdf> kdf = KeePass2::kdfFromParameters(kdfParams);
|
||||
if (!kdf) {
|
||||
raiseError(tr("Unsupported key derivation function (KDF) or invalid parameters"));
|
||||
return false;
|
||||
}
|
||||
m_db->setKdf(kdf);
|
||||
break;
|
||||
}
|
||||
|
||||
case KeePass2::HeaderFieldID::PublicCustomData:
|
||||
m_db->setPublicCustomData(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::HeaderFieldID::ProtectedStreamKey:
|
||||
case KeePass2::HeaderFieldID::TransformRounds:
|
||||
case KeePass2::HeaderFieldID::TransformSeed:
|
||||
case KeePass2::HeaderFieldID::StreamStartBytes:
|
||||
case KeePass2::HeaderFieldID::InnerRandomStreamID:
|
||||
raiseError(tr("Legacy header fields found in KDBX4 file."));
|
||||
return false;
|
||||
|
||||
default:
|
||||
qWarning("Unknown header field read: id=%d", fieldID);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for reading KDBX4 inner header fields.
|
||||
*
|
||||
* @param device input device
|
||||
* @return true if there are more inner header fields
|
||||
*/
|
||||
bool Kdbx4Reader::readInnerHeaderField(QIODevice* device)
|
||||
{
|
||||
QByteArray fieldIDArray = device->read(1);
|
||||
if (fieldIDArray.size() != 1) {
|
||||
raiseError(tr("Invalid inner header id size"));
|
||||
return false;
|
||||
}
|
||||
auto fieldID = static_cast<KeePass2::InnerHeaderFieldID>(fieldIDArray.at(0));
|
||||
|
||||
bool ok;
|
||||
auto fieldLen = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
|
||||
if (!ok) {
|
||||
raiseError(tr("Invalid inner header field length"));
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray fieldData;
|
||||
if (fieldLen != 0) {
|
||||
fieldData = device->read(fieldLen);
|
||||
if (static_cast<quint32>(fieldData.size()) != fieldLen) {
|
||||
raiseError(tr("Invalid header data length"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
switch (fieldID) {
|
||||
case KeePass2::InnerHeaderFieldID::End:
|
||||
return false;
|
||||
|
||||
case KeePass2::InnerHeaderFieldID::InnerRandomStreamID:
|
||||
setInnerRandomStreamID(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::InnerHeaderFieldID::InnerRandomStreamKey:
|
||||
setProtectedStreamKey(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::InnerHeaderFieldID::Binary:
|
||||
if (fieldLen < 1) {
|
||||
raiseError(tr("Invalid inner header binary size"));
|
||||
return false;
|
||||
}
|
||||
m_binaryPool.insert(QString::number(m_binaryPool.size()), fieldData.mid(1));
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for reading KDF parameters into variant map.
|
||||
*
|
||||
* @param device input device
|
||||
* @return filled variant map
|
||||
*/
|
||||
QVariantMap Kdbx4Reader::readVariantMap(QIODevice* device)
|
||||
{
|
||||
bool ok;
|
||||
quint16 version = Endian::readSizedInt<quint16>(device, KeePass2::BYTEORDER, &ok)
|
||||
& KeePass2::VARIANTMAP_CRITICAL_MASK;
|
||||
quint16 maxVersion = KeePass2::VARIANTMAP_VERSION & KeePass2::VARIANTMAP_CRITICAL_MASK;
|
||||
if (!ok || (version > maxVersion)) {
|
||||
raiseError(tr("Unsupported KeePass variant map version."));
|
||||
return {};
|
||||
}
|
||||
|
||||
QVariantMap vm;
|
||||
QByteArray fieldTypeArray;
|
||||
KeePass2::VariantMapFieldType fieldType = KeePass2::VariantMapFieldType::End;
|
||||
while (((fieldTypeArray = device->read(1)).size() == 1)
|
||||
&& ((fieldType = static_cast<KeePass2::VariantMapFieldType>(fieldTypeArray.at(0)))
|
||||
!= KeePass2::VariantMapFieldType::End)) {
|
||||
auto nameLen = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
|
||||
if (!ok) {
|
||||
raiseError(tr("Invalid variant map entry name length"));
|
||||
return {};
|
||||
}
|
||||
QByteArray nameBytes;
|
||||
if (nameLen != 0) {
|
||||
nameBytes = device->read(nameLen);
|
||||
if (static_cast<quint32>(nameBytes.size()) != nameLen) {
|
||||
raiseError(tr("Invalid variant map entry name data"));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
QString name = QString::fromUtf8(nameBytes);
|
||||
|
||||
auto valueLen = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
|
||||
if (!ok) {
|
||||
raiseError(tr("Invalid variant map entry value length"));
|
||||
return {};
|
||||
}
|
||||
QByteArray valueBytes;
|
||||
if (valueLen != 0) {
|
||||
valueBytes = device->read(valueLen);
|
||||
if (static_cast<quint32>(valueBytes.size()) != valueLen) {
|
||||
raiseError(tr("Invalid variant map entry value data"));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
switch (fieldType) {
|
||||
case KeePass2::VariantMapFieldType::Bool:
|
||||
if (valueLen == 1) {
|
||||
vm.insert(name, QVariant(valueBytes.at(0) != 0));
|
||||
} else {
|
||||
raiseError(tr("Invalid variant map Bool entry value length"));
|
||||
return {};
|
||||
}
|
||||
break;
|
||||
|
||||
case KeePass2::VariantMapFieldType::Int32:
|
||||
if (valueLen == 4) {
|
||||
vm.insert(name, QVariant(Endian::bytesToSizedInt<qint32>(valueBytes, KeePass2::BYTEORDER)));
|
||||
} else {
|
||||
raiseError(tr("Invalid variant map Int32 entry value length"));
|
||||
return {};
|
||||
}
|
||||
break;
|
||||
|
||||
case KeePass2::VariantMapFieldType::UInt32:
|
||||
if (valueLen == 4) {
|
||||
vm.insert(name, QVariant(Endian::bytesToSizedInt<quint32>(valueBytes, KeePass2::BYTEORDER)));
|
||||
} else {
|
||||
raiseError(tr("Invalid variant map UInt32 entry value length"));
|
||||
return {};
|
||||
}
|
||||
break;
|
||||
|
||||
case KeePass2::VariantMapFieldType::Int64:
|
||||
if (valueLen == 8) {
|
||||
vm.insert(name, QVariant(Endian::bytesToSizedInt<qint64>(valueBytes, KeePass2::BYTEORDER)));
|
||||
} else {
|
||||
raiseError(tr("Invalid variant map Int64 entry value length"));
|
||||
return {};
|
||||
}
|
||||
break;
|
||||
|
||||
case KeePass2::VariantMapFieldType::UInt64:
|
||||
if (valueLen == 8) {
|
||||
vm.insert(name, QVariant(Endian::bytesToSizedInt<quint64>(valueBytes, KeePass2::BYTEORDER)));
|
||||
} else {
|
||||
raiseError(tr("Invalid variant map UInt64 entry value length"));
|
||||
return {};
|
||||
}
|
||||
break;
|
||||
|
||||
case KeePass2::VariantMapFieldType::String:
|
||||
vm.insert(name, QVariant(QString::fromUtf8(valueBytes)));
|
||||
break;
|
||||
|
||||
case KeePass2::VariantMapFieldType::ByteArray:
|
||||
vm.insert(name, QVariant(valueBytes));
|
||||
break;
|
||||
|
||||
default:
|
||||
raiseError(tr("Invalid variant map entry type"));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldTypeArray.size() != 1) {
|
||||
raiseError(tr("Invalid variant map field type size"));
|
||||
return {};
|
||||
}
|
||||
|
||||
return vm;
|
||||
}
|
||||
|
||||
QHash<QString, QByteArray> Kdbx4Reader::binaryPool() const
|
||||
{
|
||||
return m_binaryPool;
|
||||
}
|
45
src/format/Kdbx4Reader.h
Normal file
45
src/format/Kdbx4Reader.h
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_KDBX4READER_H
|
||||
#define KEEPASSX_KDBX4READER_H
|
||||
|
||||
#include "format/KdbxReader.h"
|
||||
|
||||
#include <QVariantMap>
|
||||
|
||||
/**
|
||||
* KDBX4 reader implementation.
|
||||
*/
|
||||
class Kdbx4Reader : public KdbxReader
|
||||
{
|
||||
public:
|
||||
Database* readDatabaseImpl(QIODevice* device, const QByteArray& headerData,
|
||||
const CompositeKey& key, bool keepDatabase) override;
|
||||
QHash<QString, QByteArray> binaryPool() const;
|
||||
|
||||
protected:
|
||||
bool readHeaderField(StoreDataStream& headerStream) override;
|
||||
|
||||
private:
|
||||
bool readInnerHeaderField(QIODevice* device);
|
||||
QVariantMap readVariantMap(QIODevice* device);
|
||||
|
||||
QHash<QString, QByteArray> m_binaryPool;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_KDBX4READER_H
|
300
src/format/Kdbx4Writer.cpp
Normal file
300
src/format/Kdbx4Writer.cpp
Normal file
@ -0,0 +1,300 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Kdbx4Writer.h"
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QFile>
|
||||
|
||||
#include "streams/HmacBlockStream.h"
|
||||
#include "core/Database.h"
|
||||
#include "crypto/CryptoHash.h"
|
||||
#include "crypto/Random.h"
|
||||
#include "format/KeePass2RandomStream.h"
|
||||
#include "format/KdbxXmlWriter.h"
|
||||
#include "streams/QtIOCompressor"
|
||||
#include "streams/SymmetricCipherStream.h"
|
||||
|
||||
bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
|
||||
{
|
||||
m_error = false;
|
||||
m_errorStr.clear();
|
||||
|
||||
SymmetricCipher::Algorithm algo = SymmetricCipher::cipherToAlgorithm(db->cipher());
|
||||
if (algo == SymmetricCipher::InvalidAlgorithm) {
|
||||
raiseError(tr("Invalid symmetric cipher algorithm."));
|
||||
return false;
|
||||
}
|
||||
int ivSize = SymmetricCipher::algorithmIvSize(algo);
|
||||
if (ivSize < 0) {
|
||||
raiseError(tr("Invalid symmetric cipher IV size."));
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray masterSeed = randomGen()->randomArray(32);
|
||||
QByteArray encryptionIV = randomGen()->randomArray(ivSize);
|
||||
QByteArray protectedStreamKey = randomGen()->randomArray(64);
|
||||
QByteArray startBytes;
|
||||
QByteArray endOfHeader = "\r\n\r\n";
|
||||
|
||||
if (!db->setKey(db->key(), false, true)) {
|
||||
raiseError(tr("Unable to calculate master key"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// generate transformed master key
|
||||
CryptoHash hash(CryptoHash::Sha256);
|
||||
hash.addData(masterSeed);
|
||||
Q_ASSERT(!db->transformedMasterKey().isEmpty());
|
||||
hash.addData(db->transformedMasterKey());
|
||||
QByteArray finalKey = hash.result();
|
||||
|
||||
// write header
|
||||
QByteArray headerData;
|
||||
{
|
||||
QBuffer header;
|
||||
header.open(QIODevice::WriteOnly);
|
||||
|
||||
writeMagicNumbers(&header, KeePass2::SIGNATURE_1, KeePass2::SIGNATURE_2, KeePass2::FILE_VERSION_4);
|
||||
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toByteArray()));
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::CompressionFlags,
|
||||
Endian::sizedIntToBytes(static_cast<int>(db->compressionAlgo()),
|
||||
KeePass2::BYTEORDER)));
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::MasterSeed, masterSeed));
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::EncryptionIV, encryptionIV));
|
||||
|
||||
// convert current Kdf to basic parameters
|
||||
QVariantMap kdfParams = KeePass2::kdfToParameters(db->kdf());
|
||||
QByteArray kdfParamBytes;
|
||||
if (!serializeVariantMap(kdfParams, kdfParamBytes)) {
|
||||
raiseError(tr("Failed to serialize KDF parameters variant map"));
|
||||
return false;
|
||||
}
|
||||
QByteArray publicCustomData = db->publicCustomData();
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::KdfParameters, kdfParamBytes));
|
||||
if (!publicCustomData.isEmpty()) {
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::PublicCustomData, publicCustomData));
|
||||
}
|
||||
|
||||
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::EndOfHeader, endOfHeader));
|
||||
header.close();
|
||||
headerData = header.data();
|
||||
}
|
||||
CHECK_RETURN_FALSE(writeData(device, headerData));
|
||||
|
||||
// hash header
|
||||
QByteArray headerHash = CryptoHash::hash(headerData, CryptoHash::Sha256);
|
||||
|
||||
// write HMAC-authenticated cipher stream
|
||||
QByteArray hmacKey = KeePass2::hmacKey(masterSeed, db->transformedMasterKey());
|
||||
QByteArray headerHmac = CryptoHash::hmac(headerData, HmacBlockStream::getHmacKey(UINT64_MAX, hmacKey),
|
||||
CryptoHash::Sha256);
|
||||
CHECK_RETURN_FALSE(writeData(device, headerHash));
|
||||
CHECK_RETURN_FALSE(writeData(device, headerHmac));
|
||||
|
||||
QScopedPointer<HmacBlockStream> hmacBlockStream;
|
||||
QScopedPointer<SymmetricCipherStream> cipherStream;
|
||||
|
||||
hmacBlockStream.reset(new HmacBlockStream(device, hmacKey));
|
||||
if (!hmacBlockStream->open(QIODevice::WriteOnly)) {
|
||||
raiseError(hmacBlockStream->errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
cipherStream.reset(new SymmetricCipherStream(hmacBlockStream.data(), algo,
|
||||
SymmetricCipher::algorithmMode(algo),
|
||||
SymmetricCipher::Encrypt));
|
||||
|
||||
if (!cipherStream->init(finalKey, encryptionIV)) {
|
||||
raiseError(cipherStream->errorString());
|
||||
return false;
|
||||
}
|
||||
if (!cipherStream->open(QIODevice::WriteOnly)) {
|
||||
raiseError(cipherStream->errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
QIODevice* outputDevice = nullptr;
|
||||
QScopedPointer<QtIOCompressor> ioCompressor;
|
||||
|
||||
if (db->compressionAlgo() == Database::CompressionNone) {
|
||||
outputDevice = cipherStream.data();
|
||||
} else {
|
||||
ioCompressor.reset(new QtIOCompressor(cipherStream.data()));
|
||||
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
|
||||
if (!ioCompressor->open(QIODevice::WriteOnly)) {
|
||||
raiseError(ioCompressor->errorString());
|
||||
return false;
|
||||
}
|
||||
outputDevice = ioCompressor.data();
|
||||
}
|
||||
|
||||
Q_ASSERT(outputDevice);
|
||||
|
||||
CHECK_RETURN_FALSE(writeInnerHeaderField(outputDevice, KeePass2::InnerHeaderFieldID::InnerRandomStreamID,
|
||||
Endian::sizedIntToBytes(static_cast<int>(KeePass2::ProtectedStreamAlgo::ChaCha20),
|
||||
KeePass2::BYTEORDER)));
|
||||
CHECK_RETURN_FALSE(writeInnerHeaderField(outputDevice, KeePass2::InnerHeaderFieldID::InnerRandomStreamKey,
|
||||
protectedStreamKey));
|
||||
|
||||
CHECK_RETURN_FALSE(writeInnerHeaderField(outputDevice, KeePass2::InnerHeaderFieldID::End, QByteArray()));
|
||||
|
||||
KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::ChaCha20);
|
||||
if (!randomStream.init(protectedStreamKey)) {
|
||||
raiseError(randomStream.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
KdbxXmlWriter xmlWriter(KeePass2::FILE_VERSION_4);
|
||||
xmlWriter.writeDatabase(outputDevice, db, &randomStream, headerHash);
|
||||
|
||||
// Explicitly close/reset streams so they are flushed and we can detect
|
||||
// errors. QIODevice::close() resets errorString() etc.
|
||||
if (ioCompressor) {
|
||||
ioCompressor->close();
|
||||
}
|
||||
if (!cipherStream->reset()) {
|
||||
raiseError(cipherStream->errorString());
|
||||
return false;
|
||||
}
|
||||
if (!hmacBlockStream->reset()) {
|
||||
raiseError(hmacBlockStream->errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (xmlWriter.hasError()) {
|
||||
raiseError(xmlWriter.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write KDBX4 inner header field.
|
||||
*
|
||||
* @param device output device
|
||||
* @param fieldId field identifier
|
||||
* @param data header payload
|
||||
* @return true on success
|
||||
*/
|
||||
bool Kdbx4Writer::writeInnerHeaderField(QIODevice* device, KeePass2::InnerHeaderFieldID fieldId, const QByteArray& data)
|
||||
{
|
||||
QByteArray fieldIdArr;
|
||||
fieldIdArr[0] = static_cast<char>(fieldId);
|
||||
CHECK_RETURN_FALSE(writeData(device, fieldIdArr));
|
||||
CHECK_RETURN_FALSE(writeData(device, Endian::sizedIntToBytes(static_cast<quint32>(data.size()), KeePass2::BYTEORDER)));
|
||||
CHECK_RETURN_FALSE(writeData(device, data));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write binary header field..
|
||||
*
|
||||
* @param device output device
|
||||
* @param fieldId field identifier
|
||||
* @param data header payload
|
||||
* @return true on success
|
||||
*/
|
||||
bool Kdbx4Writer::writeBinary(QIODevice* device, const QByteArray& data)
|
||||
{
|
||||
QByteArray fieldIdArr;
|
||||
fieldIdArr[0] = static_cast<char>(KeePass2::InnerHeaderFieldID::Binary);
|
||||
CHECK_RETURN_FALSE(writeData(device, fieldIdArr));
|
||||
CHECK_RETURN_FALSE(writeData(device, Endian::sizedIntToBytes(static_cast<quint32>(data.size() + 1), KeePass2::BYTEORDER)));
|
||||
CHECK_RETURN_FALSE(writeData(device, QByteArray(1, '\1')));
|
||||
CHECK_RETURN_FALSE(writeData(device, data));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize KDF parameter variant map to byte array.
|
||||
*
|
||||
* @param map input variant map
|
||||
* @param outputBytes output byte array
|
||||
* @return true on success
|
||||
*/
|
||||
bool Kdbx4Writer::serializeVariantMap(const QVariantMap& map, QByteArray& outputBytes)
|
||||
{
|
||||
QBuffer buf(&outputBytes);
|
||||
buf.open(QIODevice::WriteOnly);
|
||||
CHECK_RETURN_FALSE(buf.write(Endian::sizedIntToBytes(KeePass2::VARIANTMAP_VERSION, KeePass2::BYTEORDER)) == 2);
|
||||
|
||||
bool ok;
|
||||
QList<QString> keys = map.keys();
|
||||
for (const auto& k : keys) {
|
||||
KeePass2::VariantMapFieldType fieldType;
|
||||
QByteArray data;
|
||||
QVariant v = map.value(k);
|
||||
switch (static_cast<QMetaType::Type>(v.type())) {
|
||||
case QMetaType::Type::Int:
|
||||
fieldType = KeePass2::VariantMapFieldType::Int32;
|
||||
data = Endian::sizedIntToBytes(v.toInt(&ok), KeePass2::BYTEORDER);
|
||||
CHECK_RETURN_FALSE(ok);
|
||||
break;
|
||||
case QMetaType::Type::UInt:
|
||||
fieldType = KeePass2::VariantMapFieldType::UInt32;
|
||||
data = Endian::sizedIntToBytes(v.toUInt(&ok), KeePass2::BYTEORDER);
|
||||
CHECK_RETURN_FALSE(ok);
|
||||
break;
|
||||
case QMetaType::Type::LongLong:
|
||||
fieldType = KeePass2::VariantMapFieldType::Int64;
|
||||
data = Endian::sizedIntToBytes(v.toLongLong(&ok), KeePass2::BYTEORDER);
|
||||
CHECK_RETURN_FALSE(ok);
|
||||
break;
|
||||
case QMetaType::Type::ULongLong:
|
||||
fieldType = KeePass2::VariantMapFieldType::UInt64;
|
||||
data = Endian::sizedIntToBytes(v.toULongLong(&ok), KeePass2::BYTEORDER);
|
||||
CHECK_RETURN_FALSE(ok);
|
||||
break;
|
||||
case QMetaType::Type::QString:
|
||||
fieldType = KeePass2::VariantMapFieldType::String;
|
||||
data = v.toString().toUtf8();
|
||||
break;
|
||||
case QMetaType::Type::Bool:
|
||||
fieldType = KeePass2::VariantMapFieldType::Bool;
|
||||
data = QByteArray(1, static_cast<char>(v.toBool() ? '\1' : '\0'));
|
||||
break;
|
||||
case QMetaType::Type::QByteArray:
|
||||
fieldType = KeePass2::VariantMapFieldType::ByteArray;
|
||||
data = v.toByteArray();
|
||||
break;
|
||||
default:
|
||||
qWarning("Unknown object type %d in QVariantMap", v.type());
|
||||
return false;
|
||||
}
|
||||
QByteArray typeBytes;
|
||||
typeBytes[0] = static_cast<char>(fieldType);
|
||||
QByteArray nameBytes = k.toUtf8();
|
||||
QByteArray nameLenBytes = Endian::sizedIntToBytes(nameBytes.size(), KeePass2::BYTEORDER);
|
||||
QByteArray dataLenBytes = Endian::sizedIntToBytes(data.size(), KeePass2::BYTEORDER);
|
||||
|
||||
CHECK_RETURN_FALSE(buf.write(typeBytes) == 1);
|
||||
CHECK_RETURN_FALSE(buf.write(nameLenBytes) == 4);
|
||||
CHECK_RETURN_FALSE(buf.write(nameBytes) == nameBytes.size());
|
||||
CHECK_RETURN_FALSE(buf.write(dataLenBytes) == 4);
|
||||
CHECK_RETURN_FALSE(buf.write(data) == data.size());
|
||||
}
|
||||
|
||||
QByteArray endBytes;
|
||||
endBytes[0] = static_cast<char>(KeePass2::VariantMapFieldType::End);
|
||||
CHECK_RETURN_FALSE(buf.write(endBytes) == 1);
|
||||
return true;
|
||||
}
|
37
src/format/Kdbx4Writer.h
Normal file
37
src/format/Kdbx4Writer.h
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_KDBX4WRITER_H
|
||||
#define KEEPASSX_KDBX4WRITER_H
|
||||
|
||||
#include "KdbxWriter.h"
|
||||
|
||||
/**
|
||||
* KDBX4 writer implementation.
|
||||
*/
|
||||
class Kdbx4Writer : public KdbxWriter
|
||||
{
|
||||
public:
|
||||
bool writeDatabase(QIODevice* device, Database* db) override;
|
||||
|
||||
private:
|
||||
bool writeInnerHeaderField(QIODevice* device, KeePass2::InnerHeaderFieldID fieldId, const QByteArray& data);
|
||||
bool writeBinary(QIODevice* device, const QByteArray& data);
|
||||
static bool serializeVariantMap(const QVariantMap& map, QByteArray& outputBytes);
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_KDBX4WRITER_H
|
267
src/format/KdbxReader.cpp
Normal file
267
src/format/KdbxReader.cpp
Normal file
@ -0,0 +1,267 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "KdbxReader.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/Endian.h"
|
||||
|
||||
/**
|
||||
* Read KDBX magic header numbers from a device.
|
||||
*
|
||||
* @param device input device
|
||||
* @param sig1 KDBX signature 1
|
||||
* @param sig2 KDBX signature 2
|
||||
* @param version KDBX version
|
||||
* @return true if magic numbers were read successfully
|
||||
*/
|
||||
bool KdbxReader::readMagicNumbers(QIODevice* device, quint32& sig1, quint32& sig2, quint32& version)
|
||||
{
|
||||
bool ok;
|
||||
sig1 = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sig2 = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
version = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read KDBX stream from device.
|
||||
* The device will automatically be reset to 0 before reading.
|
||||
*
|
||||
* @param device input device
|
||||
* @param key database encryption composite key
|
||||
* @param keepDatabase keep database in case of read failure
|
||||
* @return pointer to the read database, nullptr on failure
|
||||
*/
|
||||
Database* KdbxReader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase)
|
||||
{
|
||||
device->seek(0);
|
||||
|
||||
m_db.reset(new Database());
|
||||
m_xmlData.clear();
|
||||
m_masterSeed.clear();
|
||||
m_encryptionIV.clear();
|
||||
m_streamStartBytes.clear();
|
||||
m_protectedStreamKey.clear();
|
||||
|
||||
StoreDataStream headerStream(device);
|
||||
headerStream.open(QIODevice::ReadOnly);
|
||||
|
||||
// read KDBX magic numbers
|
||||
quint32 sig1, sig2;
|
||||
readMagicNumbers(&headerStream, sig1, sig2, m_kdbxVersion);
|
||||
m_kdbxSignature = qMakePair(sig1, sig2);
|
||||
|
||||
// mask out minor version
|
||||
m_kdbxVersion &= KeePass2::FILE_VERSION_CRITICAL_MASK;
|
||||
|
||||
// read header fields
|
||||
while (readHeaderField(headerStream) && !hasError()) {
|
||||
}
|
||||
|
||||
headerStream.close();
|
||||
|
||||
if (hasError()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// read payload
|
||||
return readDatabaseImpl(device, headerStream.storedData(), key, keepDatabase);
|
||||
}
|
||||
|
||||
bool KdbxReader::hasError() const
|
||||
{
|
||||
return m_error;
|
||||
}
|
||||
|
||||
QString KdbxReader::errorString() const
|
||||
{
|
||||
return m_errorStr;
|
||||
}
|
||||
|
||||
bool KdbxReader::saveXml() const
|
||||
{
|
||||
return m_saveXml;
|
||||
}
|
||||
|
||||
void KdbxReader::setSaveXml(bool save)
|
||||
{
|
||||
m_saveXml = save;
|
||||
}
|
||||
|
||||
QByteArray KdbxReader::xmlData() const
|
||||
{
|
||||
return m_xmlData;
|
||||
}
|
||||
|
||||
QByteArray KdbxReader::streamKey() const
|
||||
{
|
||||
return m_protectedStreamKey;
|
||||
}
|
||||
|
||||
KeePass2::ProtectedStreamAlgo KdbxReader::protectedStreamAlgo() const
|
||||
{
|
||||
return m_irsAlgo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param data stream cipher UUID as bytes
|
||||
*/
|
||||
void KdbxReader::setCipher(const QByteArray& data)
|
||||
{
|
||||
if (data.size() != Uuid::Length) {
|
||||
raiseError(tr("Invalid cipher uuid length"));
|
||||
return;
|
||||
}
|
||||
|
||||
Uuid uuid(data);
|
||||
|
||||
if (SymmetricCipher::cipherToAlgorithm(uuid) == SymmetricCipher::InvalidAlgorithm) {
|
||||
raiseError(tr("Unsupported cipher"));
|
||||
return;
|
||||
}
|
||||
m_db->setCipher(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param data compression flags as bytes
|
||||
*/
|
||||
void KdbxReader::setCompressionFlags(const QByteArray& data)
|
||||
{
|
||||
if (data.size() != 4) {
|
||||
raiseError(tr("Invalid compression flags length"));
|
||||
return;
|
||||
}
|
||||
auto id = Endian::bytesToSizedInt<quint32>(data, KeePass2::BYTEORDER);
|
||||
|
||||
if (id > Database::CompressionAlgorithmMax) {
|
||||
raiseError(tr("Unsupported compression algorithm"));
|
||||
return;
|
||||
}
|
||||
m_db->setCompressionAlgo(static_cast<Database::CompressionAlgorithm>(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param data master seed as bytes
|
||||
*/
|
||||
void KdbxReader::setMasterSeed(const QByteArray& data)
|
||||
{
|
||||
if (data.size() != 32) {
|
||||
raiseError(tr("Invalid master seed size"));
|
||||
return;
|
||||
}
|
||||
m_masterSeed = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param data KDF seed as bytes
|
||||
*/
|
||||
void KdbxReader::setTransformSeed(const QByteArray& data)
|
||||
{
|
||||
if (data.size() != 32) {
|
||||
raiseError(tr("Invalid transform seed size"));
|
||||
return;
|
||||
}
|
||||
|
||||
auto kdf = m_db->kdf();
|
||||
if (!kdf.isNull()) {
|
||||
kdf->setSeed(data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param data KDF transform rounds as bytes
|
||||
*/
|
||||
void KdbxReader::setTransformRounds(const QByteArray& data)
|
||||
{
|
||||
if (data.size() != 8) {
|
||||
raiseError(tr("Invalid transform rounds size"));
|
||||
return;
|
||||
}
|
||||
|
||||
auto rounds = Endian::bytesToSizedInt<quint64>(data, KeePass2::BYTEORDER);
|
||||
auto kdf = m_db->kdf();
|
||||
if (!kdf.isNull()) {
|
||||
kdf->setRounds(static_cast<int>(rounds));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param data cipher stream IV as bytes
|
||||
*/
|
||||
void KdbxReader::setEncryptionIV(const QByteArray& data)
|
||||
{
|
||||
m_encryptionIV = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param data key for random (inner) stream as bytes
|
||||
*/
|
||||
void KdbxReader::setProtectedStreamKey(const QByteArray& data)
|
||||
{
|
||||
m_protectedStreamKey = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param data start bytes for cipher stream
|
||||
*/
|
||||
void KdbxReader::setStreamStartBytes(const QByteArray& data)
|
||||
{
|
||||
if (data.size() != 32) {
|
||||
raiseError(tr("Invalid start bytes size"));
|
||||
return;
|
||||
}
|
||||
m_streamStartBytes = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param data id of inner cipher stream algorithm
|
||||
*/
|
||||
void KdbxReader::setInnerRandomStreamID(const QByteArray& data)
|
||||
{
|
||||
if (data.size() != 4) {
|
||||
raiseError(tr("Invalid random stream id size"));
|
||||
return;
|
||||
}
|
||||
auto id = Endian::bytesToSizedInt<quint32>(data, KeePass2::BYTEORDER);
|
||||
KeePass2::ProtectedStreamAlgo irsAlgo = KeePass2::idToProtectedStreamAlgo(id);
|
||||
if (irsAlgo == KeePass2::ProtectedStreamAlgo::InvalidProtectedStreamAlgo ||
|
||||
irsAlgo == KeePass2::ProtectedStreamAlgo::ArcFourVariant) {
|
||||
raiseError(tr("Invalid inner random stream cipher"));
|
||||
return;
|
||||
}
|
||||
m_irsAlgo = irsAlgo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Raise an error. Use in case of an unexpected read error.
|
||||
*
|
||||
* @param errorMessage error message
|
||||
*/
|
||||
void KdbxReader::raiseError(const QString& errorMessage)
|
||||
{
|
||||
m_error = true;
|
||||
m_errorStr = errorMessage;
|
||||
}
|
107
src/format/KdbxReader.h
Normal file
107
src/format/KdbxReader.h
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSXC_KDBXREADER_H
|
||||
#define KEEPASSXC_KDBXREADER_H
|
||||
|
||||
#include "KeePass2.h"
|
||||
#include "keys/CompositeKey.h"
|
||||
#include "streams/StoreDataStream.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QPointer>
|
||||
|
||||
class Database;
|
||||
class QIODevice;
|
||||
|
||||
/**
|
||||
* Abstract KDBX reader base class.
|
||||
*/
|
||||
class KdbxReader
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(KdbxReader)
|
||||
|
||||
public:
|
||||
KdbxReader() = default;
|
||||
virtual ~KdbxReader() = default;
|
||||
|
||||
static bool readMagicNumbers(QIODevice* device, quint32& sig1, quint32& sig2, quint32& version);
|
||||
Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false);
|
||||
|
||||
bool hasError() const;
|
||||
QString errorString() const;
|
||||
|
||||
bool saveXml() const;
|
||||
void setSaveXml(bool save);
|
||||
QByteArray xmlData() const;
|
||||
QByteArray streamKey() const;
|
||||
KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Concrete reader implementation for reading database from device.
|
||||
*
|
||||
* @param device input device at the payload starting position
|
||||
* @param KDBX header data as bytes
|
||||
* @param key database encryption composite key
|
||||
* @param keepDatabase keep database in case of read failure
|
||||
* @return pointer to the read database, nullptr on failure
|
||||
*/
|
||||
virtual Database* readDatabaseImpl(QIODevice* device, const QByteArray& headerData,
|
||||
const CompositeKey& key, bool keepDatabase) = 0;
|
||||
|
||||
/**
|
||||
* Read next header field from stream.
|
||||
*
|
||||
* @param headerStream input header stream
|
||||
* @return true if there are more header fields
|
||||
*/
|
||||
virtual bool readHeaderField(StoreDataStream& headerStream) = 0;
|
||||
|
||||
virtual void setCipher(const QByteArray& data);
|
||||
virtual void setCompressionFlags(const QByteArray& data);
|
||||
virtual void setMasterSeed(const QByteArray& data);
|
||||
virtual void setTransformSeed(const QByteArray& data);
|
||||
virtual void setTransformRounds(const QByteArray& data);
|
||||
virtual void setEncryptionIV(const QByteArray& data);
|
||||
virtual void setProtectedStreamKey(const QByteArray& data);
|
||||
virtual void setStreamStartBytes(const QByteArray& data);
|
||||
virtual void setInnerRandomStreamID(const QByteArray& data);
|
||||
|
||||
void raiseError(const QString& errorMessage);
|
||||
|
||||
QScopedPointer<Database> m_db;
|
||||
|
||||
QPair<quint32, quint32> m_kdbxSignature;
|
||||
quint32 m_kdbxVersion = 0;
|
||||
|
||||
QByteArray m_masterSeed;
|
||||
QByteArray m_encryptionIV;
|
||||
QByteArray m_streamStartBytes;
|
||||
QByteArray m_protectedStreamKey;
|
||||
KeePass2::ProtectedStreamAlgo m_irsAlgo = KeePass2::ProtectedStreamAlgo::InvalidProtectedStreamAlgo;
|
||||
|
||||
QByteArray m_xmlData;
|
||||
|
||||
private:
|
||||
bool m_saveXml = false;
|
||||
bool m_error = false;
|
||||
QString m_errorStr = "";
|
||||
};
|
||||
|
||||
|
||||
#endif //KEEPASSXC_KDBXREADER_H
|
74
src/format/KdbxWriter.cpp
Normal file
74
src/format/KdbxWriter.cpp
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "KdbxWriter.h"
|
||||
|
||||
bool KdbxWriter::hasError() const
|
||||
{
|
||||
return m_error;
|
||||
}
|
||||
|
||||
QString KdbxWriter::errorString() const
|
||||
{
|
||||
return m_errorStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write KDBX magic header numbers to a device.
|
||||
*
|
||||
* @param device output device
|
||||
* @param sig1 KDBX signature 1
|
||||
* @param sig2 KDBX signature 2
|
||||
* @param version KDBX version
|
||||
* @return true if magic numbers were written successfully
|
||||
*/
|
||||
bool KdbxWriter::writeMagicNumbers(QIODevice* device, quint32 sig1, quint32 sig2, quint32 version)
|
||||
{
|
||||
CHECK_RETURN_FALSE(writeData(device, Endian::sizedIntToBytes<qint32>(sig1, KeePass2::BYTEORDER)));
|
||||
CHECK_RETURN_FALSE(writeData(device, Endian::sizedIntToBytes<qint32>(sig2, KeePass2::BYTEORDER)));
|
||||
CHECK_RETURN_FALSE(writeData(device, Endian::sizedIntToBytes<qint32>(version, KeePass2::BYTEORDER)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for writing bytes to the device and raising an error
|
||||
* in case of write failure.
|
||||
*
|
||||
* @param device output device
|
||||
* @param data byte contents
|
||||
* @return true on success
|
||||
*/
|
||||
bool KdbxWriter::writeData(QIODevice* device, const QByteArray& data)
|
||||
{
|
||||
if (device->write(data) != data.size()) {
|
||||
raiseError(device->errorString());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Raise an error. Use in case of an unexpected write error.
|
||||
*
|
||||
* @param errorMessage error message
|
||||
*/
|
||||
void KdbxWriter::raiseError(const QString& errorMessage)
|
||||
{
|
||||
m_error = true;
|
||||
m_errorStr = errorMessage;
|
||||
}
|
90
src/format/KdbxWriter.h
Normal file
90
src/format/KdbxWriter.h
Normal file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSXC_KDBXWRITER_H
|
||||
#define KEEPASSXC_KDBXWRITER_H
|
||||
|
||||
#include "KeePass2.h"
|
||||
#include "core/Endian.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
#define CHECK_RETURN_FALSE(x) if (!(x)) return false;
|
||||
|
||||
class QIODevice;
|
||||
class Database;
|
||||
|
||||
/**
|
||||
* Abstract KDBX writer base class.
|
||||
*/
|
||||
class KdbxWriter
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(KdbxWriter)
|
||||
|
||||
public:
|
||||
KdbxWriter() = default;
|
||||
virtual ~KdbxWriter() = default;
|
||||
|
||||
bool writeMagicNumbers(QIODevice* device, quint32 sig1, quint32 sig2, quint32 version);
|
||||
|
||||
/**
|
||||
* Write a database to a device in KDBX format.
|
||||
*
|
||||
* @param device output device
|
||||
* @param db source database
|
||||
* @return true on success
|
||||
*/
|
||||
virtual bool writeDatabase(QIODevice* device, Database* db) = 0;
|
||||
|
||||
bool hasError() const;
|
||||
QString errorString() const;
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
* Helper method for writing a KDBX header field to a device.
|
||||
*
|
||||
* @tparam SizedQInt field width
|
||||
* @param device output device
|
||||
* @param fieldId field identifier
|
||||
* @param data field contents
|
||||
* @return true on success
|
||||
*/
|
||||
template <typename SizedQInt>
|
||||
bool writeHeaderField(QIODevice* device, KeePass2::HeaderFieldID fieldId, const QByteArray& data)
|
||||
{
|
||||
Q_ASSERT(static_cast<unsigned long>(data.size()) < (1ull << (sizeof(SizedQInt) * 8)));
|
||||
|
||||
QByteArray fieldIdArr;
|
||||
fieldIdArr[0] = static_cast<char>(fieldId);
|
||||
CHECK_RETURN_FALSE(writeData(device, fieldIdArr));
|
||||
CHECK_RETURN_FALSE(writeData(device, Endian::sizedIntToBytes<SizedQInt>(static_cast<SizedQInt>(data.size()),
|
||||
KeePass2::BYTEORDER)));
|
||||
CHECK_RETURN_FALSE(writeData(device, data));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool writeData(QIODevice* device, const QByteArray& data);
|
||||
void raiseError(const QString& errorMessage);
|
||||
|
||||
bool m_error = false;
|
||||
QString m_errorStr = "";
|
||||
};
|
||||
|
||||
|
||||
#endif //KEEPASSXC_KDBXWRITER_H
|
File diff suppressed because it is too large
Load Diff
120
src/format/KdbxXmlReader.h
Normal file
120
src/format/KdbxXmlReader.h
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSXC_KDBXXMLREADER_H
|
||||
#define KEEPASSXC_KDBXXMLREADER_H
|
||||
|
||||
#include "core/Metadata.h"
|
||||
#include "core/TimeInfo.h"
|
||||
#include "core/Uuid.h"
|
||||
#include "core/Database.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QString>
|
||||
#include <QPair>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
class QIODevice;
|
||||
class Group;
|
||||
class Entry;
|
||||
class KeePass2RandomStream;
|
||||
|
||||
/**
|
||||
* KDBX XML payload reader.
|
||||
*/
|
||||
class KdbxXmlReader
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(KdbxXmlReader)
|
||||
|
||||
public:
|
||||
explicit KdbxXmlReader(quint32 version);
|
||||
explicit KdbxXmlReader(quint32 version, QHash<QString, QByteArray>& binaryPool);
|
||||
virtual ~KdbxXmlReader() = default;
|
||||
|
||||
virtual Database* readDatabase(const QString& filename);
|
||||
virtual Database* readDatabase(QIODevice* device);
|
||||
virtual void readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr);
|
||||
|
||||
bool hasError() const;
|
||||
QString errorString() const;
|
||||
|
||||
QByteArray headerHash() const;
|
||||
|
||||
bool strictMode() const;
|
||||
void setStrictMode(bool strictMode);
|
||||
|
||||
protected:
|
||||
typedef QPair<QString, QString> StringPair;
|
||||
|
||||
virtual bool parseKeePassFile();
|
||||
virtual void parseMeta();
|
||||
virtual void parseMemoryProtection();
|
||||
virtual void parseCustomIcons();
|
||||
virtual void parseIcon();
|
||||
virtual void parseBinaries();
|
||||
virtual void parseCustomData();
|
||||
virtual void parseCustomDataItem();
|
||||
virtual bool parseRoot();
|
||||
virtual Group* parseGroup();
|
||||
virtual void parseDeletedObjects();
|
||||
virtual void parseDeletedObject();
|
||||
virtual Entry* parseEntry(bool history);
|
||||
virtual void parseEntryString(Entry* entry);
|
||||
virtual QPair<QString, QString> parseEntryBinary(Entry* entry);
|
||||
virtual void parseAutoType(Entry* entry);
|
||||
virtual void parseAutoTypeAssoc(Entry* entry);
|
||||
virtual QList<Entry*> parseEntryHistory();
|
||||
virtual TimeInfo parseTimes();
|
||||
|
||||
virtual QString readString();
|
||||
virtual bool readBool();
|
||||
virtual QDateTime readDateTime();
|
||||
virtual QColor readColor();
|
||||
virtual int readNumber();
|
||||
virtual Uuid readUuid();
|
||||
virtual QByteArray readBinary();
|
||||
virtual QByteArray readCompressedBinary();
|
||||
|
||||
virtual void skipCurrentElement();
|
||||
|
||||
virtual Group* getGroup(const Uuid& uuid);
|
||||
virtual Entry* getEntry(const Uuid& uuid);
|
||||
|
||||
virtual void raiseError(const QString& errorMessage);
|
||||
|
||||
const quint32 m_kdbxVersion;
|
||||
|
||||
bool m_strictMode = false;
|
||||
|
||||
QPointer<Database> m_db;
|
||||
QPointer<Metadata> m_meta;
|
||||
KeePass2RandomStream* m_randomStream = nullptr;
|
||||
QXmlStreamReader m_xml;
|
||||
|
||||
QScopedPointer<Group> m_tmpParent;
|
||||
QHash<Uuid, Group*> m_groups;
|
||||
QHash<Uuid, Entry*> m_entries;
|
||||
|
||||
QHash<QString, QByteArray> m_binaryPool;
|
||||
QHash<QString, QPair<Entry*, QString> > m_binaryMap;
|
||||
QByteArray m_headerHash;
|
||||
|
||||
bool m_error = false;
|
||||
QString m_errorStr = "";
|
||||
};
|
||||
|
||||
#endif //KEEPASSXC_KDBXXMLREADER_H
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -15,47 +15,45 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "KeePass2XmlWriter.h"
|
||||
#include "KdbxXmlWriter.h"
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QFile>
|
||||
|
||||
#include "core/Endian.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "format/KeePass2RandomStream.h"
|
||||
#include "streams/QtIOCompressor"
|
||||
|
||||
KeePass2XmlWriter::KeePass2XmlWriter()
|
||||
: m_db(nullptr)
|
||||
, m_meta(nullptr)
|
||||
, m_randomStream(nullptr)
|
||||
, m_error(false)
|
||||
/**
|
||||
* @param version KDBX version
|
||||
*/
|
||||
KdbxXmlWriter::KdbxXmlWriter(quint32 version)
|
||||
: m_kdbxVersion(version)
|
||||
{
|
||||
m_xml.setAutoFormatting(true);
|
||||
m_xml.setAutoFormattingIndent(-1); // 1 tab
|
||||
m_xml.setCodec("UTF-8");
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream,
|
||||
const QByteArray& headerHash)
|
||||
void KdbxXmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream, const QByteArray& headerHash)
|
||||
{
|
||||
m_db = db;
|
||||
m_meta = db->metadata();
|
||||
m_randomStream = randomStream;
|
||||
m_headerHash = headerHash;
|
||||
|
||||
m_xml.setAutoFormatting(true);
|
||||
m_xml.setAutoFormattingIndent(-1); // 1 tab
|
||||
m_xml.setCodec("UTF-8");
|
||||
|
||||
generateIdMap();
|
||||
|
||||
m_xml.setDevice(device);
|
||||
|
||||
m_xml.writeStartDocument("1.0", true);
|
||||
|
||||
m_xml.writeStartElement("KeePassFile");
|
||||
|
||||
writeMetadata();
|
||||
writeRoot();
|
||||
|
||||
m_xml.writeEndElement();
|
||||
|
||||
m_xml.writeEndDocument();
|
||||
|
||||
if (m_xml.hasError()) {
|
||||
@ -63,24 +61,24 @@ void KeePass2XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2R
|
||||
}
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeDatabase(const QString& filename, Database* db)
|
||||
void KdbxXmlWriter::writeDatabase(const QString& filename, Database* db)
|
||||
{
|
||||
QFile file(filename);
|
||||
file.open(QIODevice::WriteOnly|QIODevice::Truncate);
|
||||
writeDatabase(&file, db);
|
||||
}
|
||||
|
||||
bool KeePass2XmlWriter::hasError()
|
||||
bool KdbxXmlWriter::hasError()
|
||||
{
|
||||
return m_error;
|
||||
}
|
||||
|
||||
QString KeePass2XmlWriter::errorString()
|
||||
QString KdbxXmlWriter::errorString()
|
||||
{
|
||||
return m_errorStr;
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::generateIdMap()
|
||||
void KdbxXmlWriter::generateIdMap()
|
||||
{
|
||||
const QList<Entry*> allEntries = m_db->rootGroup()->entriesRecursive(true);
|
||||
int nextId = 0;
|
||||
@ -96,12 +94,11 @@ void KeePass2XmlWriter::generateIdMap()
|
||||
}
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeMetadata()
|
||||
void KdbxXmlWriter::writeMetadata()
|
||||
{
|
||||
m_xml.writeStartElement("Meta");
|
||||
|
||||
writeString("Generator", m_meta->generator());
|
||||
if (!m_headerHash.isEmpty()) {
|
||||
if (m_kdbxVersion < KeePass2::FILE_VERSION_4 && !m_headerHash.isEmpty()) {
|
||||
writeBinary("HeaderHash", m_headerHash);
|
||||
}
|
||||
writeString("DatabaseName", m_meta->name());
|
||||
@ -126,13 +123,18 @@ void KeePass2XmlWriter::writeMetadata()
|
||||
writeUuid("LastTopVisibleGroup", m_meta->lastTopVisibleGroup());
|
||||
writeNumber("HistoryMaxItems", m_meta->historyMaxItems());
|
||||
writeNumber("HistoryMaxSize", m_meta->historyMaxSize());
|
||||
writeBinaries();
|
||||
if (m_kdbxVersion >= KeePass2::FILE_VERSION_4) {
|
||||
writeDateTime("SettingsChanged", m_meta->settingsChanged());
|
||||
}
|
||||
if (m_kdbxVersion < KeePass2::FILE_VERSION_4) {
|
||||
writeBinaries();
|
||||
}
|
||||
writeCustomData();
|
||||
|
||||
m_xml.writeEndElement();
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeMemoryProtection()
|
||||
void KdbxXmlWriter::writeMemoryProtection()
|
||||
{
|
||||
m_xml.writeStartElement("MemoryProtection");
|
||||
|
||||
@ -145,7 +147,7 @@ void KeePass2XmlWriter::writeMemoryProtection()
|
||||
m_xml.writeEndElement();
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeCustomIcons()
|
||||
void KdbxXmlWriter::writeCustomIcons()
|
||||
{
|
||||
m_xml.writeStartElement("CustomIcons");
|
||||
|
||||
@ -157,7 +159,7 @@ void KeePass2XmlWriter::writeCustomIcons()
|
||||
m_xml.writeEndElement();
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeIcon(const Uuid& uuid, const QImage& icon)
|
||||
void KdbxXmlWriter::writeIcon(const Uuid& uuid, const QImage& icon)
|
||||
{
|
||||
m_xml.writeStartElement("Icon");
|
||||
|
||||
@ -174,7 +176,7 @@ void KeePass2XmlWriter::writeIcon(const Uuid& uuid, const QImage& icon)
|
||||
m_xml.writeEndElement();
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeBinaries()
|
||||
void KdbxXmlWriter::writeBinaries()
|
||||
{
|
||||
m_xml.writeStartElement("Binaries");
|
||||
|
||||
@ -216,7 +218,7 @@ void KeePass2XmlWriter::writeBinaries()
|
||||
m_xml.writeEndElement();
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeCustomData()
|
||||
void KdbxXmlWriter::writeCustomData()
|
||||
{
|
||||
m_xml.writeStartElement("CustomData");
|
||||
|
||||
@ -229,7 +231,7 @@ void KeePass2XmlWriter::writeCustomData()
|
||||
m_xml.writeEndElement();
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeCustomDataItem(const QString& key, const QString& value)
|
||||
void KdbxXmlWriter::writeCustomDataItem(const QString& key, const QString& value)
|
||||
{
|
||||
m_xml.writeStartElement("Item");
|
||||
|
||||
@ -239,7 +241,7 @@ void KeePass2XmlWriter::writeCustomDataItem(const QString& key, const QString& v
|
||||
m_xml.writeEndElement();
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeRoot()
|
||||
void KdbxXmlWriter::writeRoot()
|
||||
{
|
||||
Q_ASSERT(m_db->rootGroup());
|
||||
|
||||
@ -251,7 +253,7 @@ void KeePass2XmlWriter::writeRoot()
|
||||
m_xml.writeEndElement();
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeGroup(const Group* group)
|
||||
void KdbxXmlWriter::writeGroup(const Group* group)
|
||||
{
|
||||
Q_ASSERT(!group->uuid().isNull());
|
||||
|
||||
@ -275,12 +277,12 @@ void KeePass2XmlWriter::writeGroup(const Group* group)
|
||||
|
||||
writeUuid("LastTopVisibleEntry", group->lastTopVisibleEntry());
|
||||
|
||||
const QList<Entry*> entryList = group->entries();
|
||||
const QList<Entry*>& entryList = group->entries();
|
||||
for (const Entry* entry : entryList) {
|
||||
writeEntry(entry);
|
||||
}
|
||||
|
||||
const QList<Group*> children = group->children();
|
||||
const QList<Group*>& children = group->children();
|
||||
for (const Group* child : children) {
|
||||
writeGroup(child);
|
||||
}
|
||||
@ -288,7 +290,7 @@ void KeePass2XmlWriter::writeGroup(const Group* group)
|
||||
m_xml.writeEndElement();
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeTimes(const TimeInfo& ti)
|
||||
void KdbxXmlWriter::writeTimes(const TimeInfo& ti)
|
||||
{
|
||||
m_xml.writeStartElement("Times");
|
||||
|
||||
@ -303,7 +305,7 @@ void KeePass2XmlWriter::writeTimes(const TimeInfo& ti)
|
||||
m_xml.writeEndElement();
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeDeletedObjects()
|
||||
void KdbxXmlWriter::writeDeletedObjects()
|
||||
{
|
||||
m_xml.writeStartElement("DeletedObjects");
|
||||
|
||||
@ -315,7 +317,7 @@ void KeePass2XmlWriter::writeDeletedObjects()
|
||||
m_xml.writeEndElement();
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeDeletedObject(const DeletedObject& delObj)
|
||||
void KdbxXmlWriter::writeDeletedObject(const DeletedObject& delObj)
|
||||
{
|
||||
m_xml.writeStartElement("DeletedObject");
|
||||
|
||||
@ -325,7 +327,7 @@ void KeePass2XmlWriter::writeDeletedObject(const DeletedObject& delObj)
|
||||
m_xml.writeEndElement();
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeEntry(const Entry* entry)
|
||||
void KdbxXmlWriter::writeEntry(const Entry* entry)
|
||||
{
|
||||
Q_ASSERT(!entry->uuid().isNull());
|
||||
|
||||
@ -407,7 +409,7 @@ void KeePass2XmlWriter::writeEntry(const Entry* entry)
|
||||
m_xml.writeEndElement();
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeAutoType(const Entry* entry)
|
||||
void KdbxXmlWriter::writeAutoType(const Entry* entry)
|
||||
{
|
||||
m_xml.writeStartElement("AutoType");
|
||||
|
||||
@ -423,7 +425,7 @@ void KeePass2XmlWriter::writeAutoType(const Entry* entry)
|
||||
m_xml.writeEndElement();
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Association& assoc)
|
||||
void KdbxXmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Association& assoc)
|
||||
{
|
||||
m_xml.writeStartElement("Association");
|
||||
|
||||
@ -433,7 +435,7 @@ void KeePass2XmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Associati
|
||||
m_xml.writeEndElement();
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeEntryHistory(const Entry* entry)
|
||||
void KdbxXmlWriter::writeEntryHistory(const Entry* entry)
|
||||
{
|
||||
m_xml.writeStartElement("History");
|
||||
|
||||
@ -445,7 +447,7 @@ void KeePass2XmlWriter::writeEntryHistory(const Entry* entry)
|
||||
m_xml.writeEndElement();
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeString(const QString& qualifiedName, const QString& string)
|
||||
void KdbxXmlWriter::writeString(const QString& qualifiedName, const QString& string)
|
||||
{
|
||||
if (string.isEmpty()) {
|
||||
m_xml.writeEmptyElement(qualifiedName);
|
||||
@ -455,12 +457,12 @@ void KeePass2XmlWriter::writeString(const QString& qualifiedName, const QString&
|
||||
}
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeNumber(const QString& qualifiedName, int number)
|
||||
void KdbxXmlWriter::writeNumber(const QString& qualifiedName, int number)
|
||||
{
|
||||
writeString(qualifiedName, QString::number(number));
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeBool(const QString& qualifiedName, bool b)
|
||||
void KdbxXmlWriter::writeBool(const QString& qualifiedName, bool b)
|
||||
{
|
||||
if (b) {
|
||||
writeString(qualifiedName, "True");
|
||||
@ -470,27 +472,33 @@ void KeePass2XmlWriter::writeBool(const QString& qualifiedName, bool b)
|
||||
}
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeDateTime(const QString& qualifiedName, const QDateTime& dateTime)
|
||||
void KdbxXmlWriter::writeDateTime(const QString& qualifiedName, const QDateTime& dateTime)
|
||||
{
|
||||
Q_ASSERT(dateTime.isValid());
|
||||
Q_ASSERT(dateTime.timeSpec() == Qt::UTC);
|
||||
|
||||
QString dateTimeStr = dateTime.toString(Qt::ISODate);
|
||||
QString dateTimeStr;
|
||||
if (m_kdbxVersion < KeePass2::FILE_VERSION_4) {
|
||||
dateTimeStr = dateTime.toString(Qt::ISODate);
|
||||
|
||||
// Qt < 4.8 doesn't append a 'Z' at the end
|
||||
if (!dateTimeStr.isEmpty() && dateTimeStr[dateTimeStr.size() - 1] != 'Z') {
|
||||
dateTimeStr.append('Z');
|
||||
// Qt < 4.8 doesn't append a 'Z' at the end
|
||||
if (!dateTimeStr.isEmpty() && dateTimeStr[dateTimeStr.size() - 1] != 'Z') {
|
||||
dateTimeStr.append('Z');
|
||||
}
|
||||
} else {
|
||||
qint64 secs = QDateTime(QDate(1, 1, 1), QTime(0, 0, 0, 0), Qt::UTC).secsTo(dateTime);
|
||||
QByteArray secsBytes = Endian::sizedIntToBytes(secs, KeePass2::BYTEORDER);
|
||||
dateTimeStr = QString::fromLatin1(secsBytes.toBase64());
|
||||
}
|
||||
|
||||
writeString(qualifiedName, dateTimeStr);
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeUuid(const QString& qualifiedName, const Uuid& uuid)
|
||||
void KdbxXmlWriter::writeUuid(const QString& qualifiedName, const Uuid& uuid)
|
||||
{
|
||||
writeString(qualifiedName, uuid.toBase64());
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeUuid(const QString& qualifiedName, const Group* group)
|
||||
void KdbxXmlWriter::writeUuid(const QString& qualifiedName, const Group* group)
|
||||
{
|
||||
if (group) {
|
||||
writeUuid(qualifiedName, group->uuid());
|
||||
@ -500,7 +508,7 @@ void KeePass2XmlWriter::writeUuid(const QString& qualifiedName, const Group* gro
|
||||
}
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeUuid(const QString& qualifiedName, const Entry* entry)
|
||||
void KdbxXmlWriter::writeUuid(const QString& qualifiedName, const Entry* entry)
|
||||
{
|
||||
if (entry) {
|
||||
writeUuid(qualifiedName, entry->uuid());
|
||||
@ -510,12 +518,12 @@ void KeePass2XmlWriter::writeUuid(const QString& qualifiedName, const Entry* ent
|
||||
}
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeBinary(const QString& qualifiedName, const QByteArray& ba)
|
||||
void KdbxXmlWriter::writeBinary(const QString& qualifiedName, const QByteArray& ba)
|
||||
{
|
||||
writeString(qualifiedName, QString::fromLatin1(ba.toBase64()));
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeColor(const QString& qualifiedName, const QColor& color)
|
||||
void KdbxXmlWriter::writeColor(const QString& qualifiedName, const QColor& color)
|
||||
{
|
||||
QString colorStr;
|
||||
|
||||
@ -528,7 +536,7 @@ void KeePass2XmlWriter::writeColor(const QString& qualifiedName, const QColor& c
|
||||
writeString(qualifiedName, colorStr);
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::writeTriState(const QString& qualifiedName, Group::TriState triState)
|
||||
void KdbxXmlWriter::writeTriState(const QString& qualifiedName, Group::TriState triState)
|
||||
{
|
||||
QString value;
|
||||
|
||||
@ -545,7 +553,7 @@ void KeePass2XmlWriter::writeTriState(const QString& qualifiedName, Group::TriSt
|
||||
writeString(qualifiedName, value);
|
||||
}
|
||||
|
||||
QString KeePass2XmlWriter::colorPartToString(int value)
|
||||
QString KdbxXmlWriter::colorPartToString(int value)
|
||||
{
|
||||
QString str = QString::number(value, 16).toUpper();
|
||||
if (str.length() == 1) {
|
||||
@ -555,7 +563,7 @@ QString KeePass2XmlWriter::colorPartToString(int value)
|
||||
return str;
|
||||
}
|
||||
|
||||
QString KeePass2XmlWriter::stripInvalidXml10Chars(QString str)
|
||||
QString KdbxXmlWriter::stripInvalidXml10Chars(QString str)
|
||||
{
|
||||
for (int i = str.size() - 1; i >= 0; i--) {
|
||||
const QChar ch = str.at(i);
|
||||
@ -580,7 +588,7 @@ QString KeePass2XmlWriter::stripInvalidXml10Chars(QString str)
|
||||
return str;
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::raiseError(const QString& errorMessage)
|
||||
void KdbxXmlWriter::raiseError(const QString& errorMessage)
|
||||
{
|
||||
m_error = true;
|
||||
m_errorStr = errorMessage;
|
95
src/format/KdbxXmlWriter.h
Normal file
95
src/format/KdbxXmlWriter.h
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_KDBXXMLWRITER_H
|
||||
#define KEEPASSX_KDBXXMLWRITER_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QDateTime>
|
||||
#include <QImage>
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
#include "core/Database.h"
|
||||
#include "core/Entry.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/TimeInfo.h"
|
||||
#include "core/Uuid.h"
|
||||
|
||||
class KeePass2RandomStream;
|
||||
class Metadata;
|
||||
|
||||
class KdbxXmlWriter
|
||||
{
|
||||
public:
|
||||
explicit KdbxXmlWriter(quint32 version);
|
||||
|
||||
void writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr,
|
||||
const QByteArray& headerHash = QByteArray());
|
||||
void writeDatabase(const QString& filename, Database* db);
|
||||
bool hasError();
|
||||
QString errorString();
|
||||
|
||||
private:
|
||||
void generateIdMap();
|
||||
|
||||
void writeMetadata();
|
||||
void writeMemoryProtection();
|
||||
void writeCustomIcons();
|
||||
void writeIcon(const Uuid& uuid, const QImage& icon);
|
||||
void writeBinaries();
|
||||
void writeCustomData();
|
||||
void writeCustomDataItem(const QString& key, const QString& value);
|
||||
void writeRoot();
|
||||
void writeGroup(const Group* group);
|
||||
void writeTimes(const TimeInfo& ti);
|
||||
void writeDeletedObjects();
|
||||
void writeDeletedObject(const DeletedObject& delObj);
|
||||
void writeEntry(const Entry* entry);
|
||||
void writeAutoType(const Entry* entry);
|
||||
void writeAutoTypeAssoc(const AutoTypeAssociations::Association& assoc);
|
||||
void writeEntryHistory(const Entry* entry);
|
||||
|
||||
void writeString(const QString& qualifiedName, const QString& string);
|
||||
void writeNumber(const QString& qualifiedName, int number);
|
||||
void writeBool(const QString& qualifiedName, bool b);
|
||||
void writeDateTime(const QString& qualifiedName, const QDateTime& dateTime);
|
||||
void writeUuid(const QString& qualifiedName, const Uuid& uuid);
|
||||
void writeUuid(const QString& qualifiedName, const Group* group);
|
||||
void writeUuid(const QString& qualifiedName, const Entry* entry);
|
||||
void writeBinary(const QString& qualifiedName, const QByteArray& ba);
|
||||
void writeColor(const QString& qualifiedName, const QColor& color);
|
||||
void writeTriState(const QString& qualifiedName, Group::TriState triState);
|
||||
QString colorPartToString(int value);
|
||||
QString stripInvalidXml10Chars(QString str);
|
||||
|
||||
void raiseError(const QString& errorMessage);
|
||||
|
||||
const quint32 m_kdbxVersion;
|
||||
|
||||
QXmlStreamWriter m_xml;
|
||||
QPointer<Database> m_db;
|
||||
QPointer<Metadata> m_meta;
|
||||
KeePass2RandomStream* m_randomStream = nullptr;
|
||||
QHash<QByteArray, int> m_idMap;
|
||||
QByteArray m_headerHash;
|
||||
|
||||
bool m_error = false;
|
||||
|
||||
QString m_errorStr = "";
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_KDBXXMLWRITER_H
|
@ -21,6 +21,7 @@
|
||||
#include <QImage>
|
||||
#include <QTextCodec>
|
||||
|
||||
#include "crypto/kdf/AesKdf.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/Endian.h"
|
||||
#include "core/Entry.h"
|
||||
@ -29,7 +30,6 @@
|
||||
#include "core/Tools.h"
|
||||
#include "crypto/CryptoHash.h"
|
||||
#include "format/KeePass1.h"
|
||||
#include "keys/CompositeKey.h"
|
||||
#include "keys/FileKey.h"
|
||||
#include "keys/PasswordKey.h"
|
||||
#include "streams/SymmetricCipherStream.h"
|
||||
@ -93,25 +93,25 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
|
||||
|
||||
bool ok;
|
||||
|
||||
quint32 signature1 = Endian::readUInt32(m_device, KeePass1::BYTEORDER, &ok);
|
||||
auto signature1 = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
|
||||
if (!ok || signature1 != KeePass1::SIGNATURE_1) {
|
||||
raiseError(tr("Not a KeePass database."));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
quint32 signature2 = Endian::readUInt32(m_device, KeePass1::BYTEORDER, &ok);
|
||||
auto signature2 = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
|
||||
if (!ok || signature2 != KeePass1::SIGNATURE_2) {
|
||||
raiseError(tr("Not a KeePass database."));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
m_encryptionFlags = Endian::readUInt32(m_device, KeePass1::BYTEORDER, &ok);
|
||||
m_encryptionFlags = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
|
||||
if (!ok || !(m_encryptionFlags & KeePass1::Rijndael || m_encryptionFlags & KeePass1::Twofish)) {
|
||||
raiseError(tr("Unsupported encryption algorithm."));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
quint32 version = Endian::readUInt32(m_device, KeePass1::BYTEORDER, &ok);
|
||||
auto version = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
|
||||
if (!ok || (version & KeePass1::FILE_VERSION_CRITICAL_MASK)
|
||||
!= (KeePass1::FILE_VERSION & KeePass1::FILE_VERSION_CRITICAL_MASK)) {
|
||||
raiseError(tr("Unsupported KeePass database version."));
|
||||
@ -130,13 +130,13 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
quint32 numGroups = Endian::readUInt32(m_device, KeePass1::BYTEORDER, &ok);
|
||||
auto numGroups = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
|
||||
if (!ok) {
|
||||
raiseError("Invalid number of groups");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
quint32 numEntries = Endian::readUInt32(m_device, KeePass1::BYTEORDER, &ok);
|
||||
auto numEntries = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
|
||||
if (!ok) {
|
||||
raiseError("Invalid number of entries");
|
||||
return nullptr;
|
||||
@ -154,15 +154,15 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
m_transformRounds = Endian::readUInt32(m_device, KeePass1::BYTEORDER, &ok);
|
||||
m_transformRounds = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
|
||||
if (!ok) {
|
||||
raiseError("Invalid number of transform rounds");
|
||||
return nullptr;
|
||||
}
|
||||
if (!m_db->setTransformRounds(m_transformRounds)) {
|
||||
raiseError(tr("Unable to calculate master key"));
|
||||
return nullptr;
|
||||
}
|
||||
auto kdf = QSharedPointer<AesKdf>::create(true);
|
||||
kdf->setRounds(m_transformRounds);
|
||||
kdf->setSeed(m_transformSeed);
|
||||
db->setKdf(kdf);
|
||||
|
||||
qint64 contentPos = m_device->pos();
|
||||
|
||||
@ -397,12 +397,11 @@ 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);
|
||||
QByteArray transformedKey;
|
||||
bool result = key.transform(*m_db->kdf(), transformedKey);
|
||||
|
||||
if (!ok) {
|
||||
raiseError(errorString);
|
||||
if (!result) {
|
||||
raiseError("Key transformation failed");
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
@ -444,13 +443,13 @@ Group* KeePass1Reader::readGroup(QIODevice* cipherStream)
|
||||
bool reachedEnd = false;
|
||||
|
||||
do {
|
||||
quint16 fieldType = Endian::readUInt16(cipherStream, KeePass1::BYTEORDER, &ok);
|
||||
quint16 fieldType = Endian::readSizedInt<quint16>(cipherStream, KeePass1::BYTEORDER, &ok);
|
||||
if (!ok) {
|
||||
raiseError("Invalid group field type number");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int fieldSize = static_cast<int>(Endian::readUInt32(cipherStream, KeePass1::BYTEORDER, &ok));
|
||||
int fieldSize = static_cast<int>(Endian::readSizedInt<quint32>(cipherStream, KeePass1::BYTEORDER, &ok));
|
||||
if (!ok) {
|
||||
raiseError("Invalid group field size");
|
||||
return nullptr;
|
||||
@ -471,7 +470,7 @@ Group* KeePass1Reader::readGroup(QIODevice* cipherStream)
|
||||
raiseError("Incorrect group id field size");
|
||||
return nullptr;
|
||||
}
|
||||
groupId = Endian::bytesToUInt32(fieldData, KeePass1::BYTEORDER);
|
||||
groupId = Endian::bytesToSizedInt<quint32>(fieldData, KeePass1::BYTEORDER);
|
||||
groupIdSet = true;
|
||||
break;
|
||||
case 0x0002:
|
||||
@ -530,7 +529,7 @@ Group* KeePass1Reader::readGroup(QIODevice* cipherStream)
|
||||
raiseError("Incorrect group icon field size");
|
||||
return nullptr;
|
||||
}
|
||||
quint32 iconNumber = Endian::bytesToUInt32(fieldData, KeePass1::BYTEORDER);
|
||||
quint32 iconNumber = Endian::bytesToSizedInt<quint32>(fieldData, KeePass1::BYTEORDER);
|
||||
group->setIcon(iconNumber);
|
||||
break;
|
||||
}
|
||||
@ -540,7 +539,7 @@ Group* KeePass1Reader::readGroup(QIODevice* cipherStream)
|
||||
raiseError("Incorrect group level field size");
|
||||
return nullptr;
|
||||
}
|
||||
groupLevel = Endian::bytesToUInt16(fieldData, KeePass1::BYTEORDER);
|
||||
groupLevel = Endian::bytesToSizedInt<quint16>(fieldData, KeePass1::BYTEORDER);
|
||||
groupLevelSet = true;
|
||||
break;
|
||||
}
|
||||
@ -582,13 +581,13 @@ Entry* KeePass1Reader::readEntry(QIODevice* cipherStream)
|
||||
bool reachedEnd = false;
|
||||
|
||||
do {
|
||||
quint16 fieldType = Endian::readUInt16(cipherStream, KeePass1::BYTEORDER, &ok);
|
||||
quint16 fieldType = Endian::readSizedInt<quint16>(cipherStream, KeePass1::BYTEORDER, &ok);
|
||||
if (!ok) {
|
||||
raiseError("Missing entry field type number");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int fieldSize = static_cast<int>(Endian::readUInt32(cipherStream, KeePass1::BYTEORDER, &ok));
|
||||
int fieldSize = static_cast<int>(Endian::readSizedInt<quint32>(cipherStream, KeePass1::BYTEORDER, &ok));
|
||||
if (!ok) {
|
||||
raiseError("Invalid entry field size");
|
||||
return nullptr;
|
||||
@ -617,7 +616,7 @@ Entry* KeePass1Reader::readEntry(QIODevice* cipherStream)
|
||||
raiseError("Invalid entry group id field size");
|
||||
return nullptr;
|
||||
}
|
||||
quint32 groupId = Endian::bytesToUInt32(fieldData, KeePass1::BYTEORDER);
|
||||
quint32 groupId = Endian::bytesToSizedInt<quint32>(fieldData, KeePass1::BYTEORDER);
|
||||
m_entryGroupIds.insert(entry.data(), groupId);
|
||||
break;
|
||||
}
|
||||
@ -627,7 +626,7 @@ Entry* KeePass1Reader::readEntry(QIODevice* cipherStream)
|
||||
raiseError("Invalid entry icon field size");
|
||||
return nullptr;
|
||||
}
|
||||
quint32 iconNumber = Endian::bytesToUInt32(fieldData, KeePass1::BYTEORDER);
|
||||
quint32 iconNumber = Endian::bytesToSizedInt<quint32>(fieldData, KeePass1::BYTEORDER);
|
||||
entry->setIcon(iconNumber);
|
||||
break;
|
||||
}
|
||||
@ -837,7 +836,7 @@ bool KeePass1Reader::parseGroupTreeState(const QByteArray& data)
|
||||
}
|
||||
|
||||
int pos = 0;
|
||||
quint32 num = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER);
|
||||
quint32 num = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
|
||||
pos += 4;
|
||||
|
||||
if (static_cast<quint32>(data.size() - 4) != (num * 5)) {
|
||||
@ -845,7 +844,7 @@ bool KeePass1Reader::parseGroupTreeState(const QByteArray& data)
|
||||
}
|
||||
|
||||
for (quint32 i = 0; i < num; i++) {
|
||||
quint32 groupId = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER);
|
||||
quint32 groupId = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
|
||||
pos += 4;
|
||||
|
||||
bool expanded = data.at(pos);
|
||||
@ -867,13 +866,13 @@ bool KeePass1Reader::parseCustomIcons4(const QByteArray& data)
|
||||
|
||||
int pos = 0;
|
||||
|
||||
quint32 numIcons = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER);
|
||||
quint32 numIcons = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
|
||||
pos += 4;
|
||||
|
||||
quint32 numEntries = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER);
|
||||
quint32 numEntries = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
|
||||
pos += 4;
|
||||
|
||||
quint32 numGroups = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER);
|
||||
quint32 numGroups = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
|
||||
pos += 4;
|
||||
|
||||
QList<Uuid> iconUuids;
|
||||
@ -882,7 +881,7 @@ bool KeePass1Reader::parseCustomIcons4(const QByteArray& data)
|
||||
if (data.size() < (pos + 4)) {
|
||||
return false;
|
||||
}
|
||||
quint32 iconSize = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER);
|
||||
quint32 iconSize = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
|
||||
pos += 4;
|
||||
|
||||
if (static_cast<quint32>(data.size()) < (pos + iconSize)) {
|
||||
@ -908,7 +907,7 @@ bool KeePass1Reader::parseCustomIcons4(const QByteArray& data)
|
||||
QByteArray entryUuid = data.mid(pos, 16);
|
||||
pos += 16;
|
||||
|
||||
quint32 iconId = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER);
|
||||
quint32 iconId = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
|
||||
pos += 4;
|
||||
|
||||
if (m_entryUuids.contains(entryUuid) && (iconId < static_cast<quint32>(iconUuids.size()))) {
|
||||
@ -921,10 +920,10 @@ bool KeePass1Reader::parseCustomIcons4(const QByteArray& data)
|
||||
}
|
||||
|
||||
for (quint32 i = 0; i < numGroups; i++) {
|
||||
quint32 groupId = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER);
|
||||
quint32 groupId = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
|
||||
pos += 4;
|
||||
|
||||
quint32 iconId = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER);
|
||||
quint32 iconId = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
|
||||
pos += 4;
|
||||
|
||||
if (m_groupIds.contains(groupId) && (iconId < static_cast<quint32>(iconUuids.size()))) {
|
||||
|
129
src/format/KeePass2.cpp
Normal file
129
src/format/KeePass2.cpp
Normal file
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "KeePass2.h"
|
||||
#include <QSharedPointer>
|
||||
#include "crypto/kdf/AesKdf.h"
|
||||
#include "crypto/kdf/Argon2Kdf.h"
|
||||
#include "crypto/CryptoHash.h"
|
||||
|
||||
const Uuid KeePass2::CIPHER_AES = Uuid(QByteArray::fromHex("31c1f2e6bf714350be5805216afc5aff"));
|
||||
const Uuid KeePass2::CIPHER_TWOFISH = Uuid(QByteArray::fromHex("ad68f29f576f4bb9a36ad47af965346c"));
|
||||
const Uuid KeePass2::CIPHER_CHACHA20 = Uuid(QByteArray::fromHex("D6038A2B8B6F4CB5A524339A31DBB59A"));
|
||||
|
||||
const Uuid KeePass2::KDF_AES_KDBX3 = Uuid(QByteArray::fromHex("C9D9F39A628A4460BF740D08C18A4FEA"));
|
||||
const Uuid KeePass2::KDF_AES_KDBX4 = Uuid(QByteArray::fromHex("7C02BB8279A74AC0927D114A00648238"));
|
||||
const Uuid KeePass2::KDF_ARGON2 = Uuid(QByteArray::fromHex("EF636DDF8C29444B91F7A9A403E30A0C"));
|
||||
|
||||
const QByteArray KeePass2::INNER_STREAM_SALSA20_IV("\xE8\x30\x09\x4B\x97\x20\x5D\x2A");
|
||||
|
||||
const QString KeePass2::KDFPARAM_UUID("$UUID");
|
||||
// AES parameters
|
||||
const QString KeePass2::KDFPARAM_AES_ROUNDS("R");
|
||||
const QString KeePass2::KDFPARAM_AES_SEED("S");
|
||||
// Argon2 parameters
|
||||
const QString KeePass2::KDFPARAM_ARGON2_SALT("S");
|
||||
const QString KeePass2::KDFPARAM_ARGON2_PARALLELISM("P");
|
||||
const QString KeePass2::KDFPARAM_ARGON2_MEMORY("M");
|
||||
const QString KeePass2::KDFPARAM_ARGON2_ITERATIONS("I");
|
||||
const QString KeePass2::KDFPARAM_ARGON2_VERSION("V");
|
||||
const QString KeePass2::KDFPARAM_ARGON2_SECRET("K");
|
||||
const QString KeePass2::KDFPARAM_ARGON2_ASSOCDATA("A");
|
||||
|
||||
const QList<QPair<Uuid, QString>> KeePass2::CIPHERS{
|
||||
qMakePair(KeePass2::CIPHER_AES, QObject::tr("AES: 256-bit")),
|
||||
qMakePair(KeePass2::CIPHER_TWOFISH, QObject::tr("Twofish: 256-bit")),
|
||||
qMakePair(KeePass2::CIPHER_CHACHA20, QObject::tr("ChaCha20: 256-bit"))
|
||||
};
|
||||
|
||||
const QList<QPair<Uuid, QString>> KeePass2::KDFS{
|
||||
qMakePair(KeePass2::KDF_ARGON2, QObject::tr("Argon2 (KDBX 4 – recommended)")),
|
||||
qMakePair(KeePass2::KDF_AES_KDBX4, QObject::tr("AES-KDF (KDBX 4)")),
|
||||
qMakePair(KeePass2::KDF_AES_KDBX3, QObject::tr("AES-KDF (KDBX 3.1)"))
|
||||
};
|
||||
|
||||
QByteArray KeePass2::hmacKey(QByteArray masterSeed, QByteArray transformedMasterKey) {
|
||||
CryptoHash hmacKeyHash(CryptoHash::Sha512);
|
||||
hmacKeyHash.addData(masterSeed);
|
||||
hmacKeyHash.addData(transformedMasterKey);
|
||||
hmacKeyHash.addData(QByteArray(1, '\x01'));
|
||||
return hmacKeyHash.result();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create KDF object from KDBX4+ KDF parameters.
|
||||
*
|
||||
* @param p variant map containing parameters
|
||||
* @return initialized KDF
|
||||
*/
|
||||
QSharedPointer<Kdf> KeePass2::kdfFromParameters(const QVariantMap& p)
|
||||
{
|
||||
QByteArray uuidBytes = p.value(KDFPARAM_UUID).toByteArray();
|
||||
if (uuidBytes.size() != Uuid::Length) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Uuid kdfUuid(uuidBytes);
|
||||
if (kdfUuid == KDF_AES_KDBX3) {
|
||||
// upgrade to non-legacy AES-KDF, since KDBX3 doesn't have any KDF parameters
|
||||
kdfUuid = KDF_AES_KDBX4;
|
||||
}
|
||||
QSharedPointer<Kdf> kdf = uuidToKdf(kdfUuid);
|
||||
if (kdf.isNull()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!kdf->processParameters(p)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return kdf;
|
||||
}
|
||||
|
||||
QVariantMap KeePass2::kdfToParameters(QSharedPointer<Kdf> kdf)
|
||||
{
|
||||
return kdf->writeParameters();
|
||||
}
|
||||
|
||||
QSharedPointer<Kdf> KeePass2::uuidToKdf(const Uuid& uuid)
|
||||
{
|
||||
if (uuid == KDF_AES_KDBX3) {
|
||||
return QSharedPointer<AesKdf>::create(true);
|
||||
}
|
||||
if (uuid == KDF_AES_KDBX4) {
|
||||
return QSharedPointer<AesKdf>::create();
|
||||
}
|
||||
if (uuid == KDF_ARGON2) {
|
||||
return QSharedPointer<Argon2Kdf>::create();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
KeePass2::ProtectedStreamAlgo KeePass2::idToProtectedStreamAlgo(quint32 id)
|
||||
{
|
||||
switch (id) {
|
||||
case static_cast<quint32>(KeePass2::ProtectedStreamAlgo::ArcFourVariant):
|
||||
return KeePass2::ProtectedStreamAlgo::ArcFourVariant;
|
||||
case static_cast<quint32>(KeePass2::ProtectedStreamAlgo::Salsa20):
|
||||
return KeePass2::ProtectedStreamAlgo::Salsa20;
|
||||
case static_cast<quint32>(KeePass2::ProtectedStreamAlgo::ChaCha20):
|
||||
return KeePass2::ProtectedStreamAlgo::ChaCha20;
|
||||
default:
|
||||
return KeePass2::ProtectedStreamAlgo::InvalidProtectedStreamAlgo;
|
||||
}
|
||||
}
|
@ -19,25 +19,54 @@
|
||||
#define KEEPASSX_KEEPASS2_H
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QMap>
|
||||
#include <QVariantMap>
|
||||
#include <QList>
|
||||
|
||||
#include "crypto/SymmetricCipher.h"
|
||||
#include "crypto/kdf/Kdf.h"
|
||||
#include "core/Uuid.h"
|
||||
|
||||
namespace KeePass2
|
||||
{
|
||||
const quint32 SIGNATURE_1 = 0x9AA2D903;
|
||||
const quint32 SIGNATURE_2 = 0xB54BFB67;
|
||||
const quint32 FILE_VERSION = 0x00030001;
|
||||
|
||||
const quint32 FILE_VERSION_MIN = 0x00020000;
|
||||
const quint32 FILE_VERSION_CRITICAL_MASK = 0xFFFF0000;
|
||||
const quint32 FILE_VERSION_4 = 0x00040000;
|
||||
const quint32 FILE_VERSION_3 = 0x00030001;
|
||||
|
||||
const quint16 VARIANTMAP_VERSION = 0x0100;
|
||||
const quint16 VARIANTMAP_CRITICAL_MASK = 0xFF00;
|
||||
|
||||
const QSysInfo::Endian BYTEORDER = QSysInfo::LittleEndian;
|
||||
|
||||
const Uuid CIPHER_AES = Uuid(QByteArray::fromHex("31c1f2e6bf714350be5805216afc5aff"));
|
||||
const Uuid CIPHER_TWOFISH = Uuid(QByteArray::fromHex("ad68f29f576f4bb9a36ad47af965346c"));
|
||||
extern const Uuid CIPHER_AES;
|
||||
extern const Uuid CIPHER_TWOFISH;
|
||||
extern const Uuid CIPHER_CHACHA20;
|
||||
|
||||
const QByteArray INNER_STREAM_SALSA20_IV("\xE8\x30\x09\x4B\x97\x20\x5D\x2A");
|
||||
extern const Uuid KDF_AES_KDBX3;
|
||||
extern const Uuid KDF_AES_KDBX4;
|
||||
extern const Uuid KDF_ARGON2;
|
||||
|
||||
enum HeaderFieldID
|
||||
extern const QByteArray INNER_STREAM_SALSA20_IV;
|
||||
|
||||
extern const QString KDFPARAM_UUID;
|
||||
extern const QString KDFPARAM_AES_ROUNDS;
|
||||
extern const QString KDFPARAM_AES_SEED;
|
||||
extern const QString KDFPARAM_ARGON2_SALT;
|
||||
extern const QString KDFPARAM_ARGON2_PARALLELISM;
|
||||
extern const QString KDFPARAM_ARGON2_MEMORY;
|
||||
extern const QString KDFPARAM_ARGON2_ITERATIONS;
|
||||
extern const QString KDFPARAM_ARGON2_VERSION;
|
||||
extern const QString KDFPARAM_ARGON2_SECRET;
|
||||
extern const QString KDFPARAM_ARGON2_ASSOCDATA;
|
||||
|
||||
extern const QList<QPair<Uuid, QString>> CIPHERS;
|
||||
extern const QList<QPair<Uuid, QString>> KDFS;
|
||||
|
||||
enum class HeaderFieldID
|
||||
{
|
||||
EndOfHeader = 0,
|
||||
Comment = 1,
|
||||
@ -49,14 +78,55 @@ namespace KeePass2
|
||||
EncryptionIV = 7,
|
||||
ProtectedStreamKey = 8,
|
||||
StreamStartBytes = 9,
|
||||
InnerRandomStreamID = 10
|
||||
InnerRandomStreamID = 10,
|
||||
KdfParameters = 11,
|
||||
PublicCustomData = 12
|
||||
};
|
||||
|
||||
enum ProtectedStreamAlgo
|
||||
enum class InnerHeaderFieldID : quint8
|
||||
{
|
||||
End = 0,
|
||||
InnerRandomStreamID = 1,
|
||||
InnerRandomStreamKey = 2,
|
||||
Binary = 3
|
||||
};
|
||||
|
||||
enum class ProtectedStreamAlgo
|
||||
{
|
||||
ArcFourVariant = 1,
|
||||
Salsa20 = 2
|
||||
Salsa20 = 2,
|
||||
ChaCha20 = 3,
|
||||
InvalidProtectedStreamAlgo = -1
|
||||
};
|
||||
|
||||
enum class VariantMapFieldType : quint8
|
||||
{
|
||||
End = 0,
|
||||
// Byte = 0x02,
|
||||
// UInt16 = 0x03,
|
||||
UInt32 = 0x04,
|
||||
UInt64 = 0x05,
|
||||
// Signed mask: 0x08
|
||||
Bool = 0x08,
|
||||
// SByte = 0x0A,
|
||||
// Int16 = 0x0B,
|
||||
Int32 = 0x0C,
|
||||
Int64 = 0x0D,
|
||||
// Float = 0x10,
|
||||
// Double = 0x11,
|
||||
// Decimal = 0x12,
|
||||
// Char = 0x17, // 16-bit Unicode character
|
||||
String = 0x18,
|
||||
// Array mask: 0x40
|
||||
ByteArray = 0x42
|
||||
};
|
||||
|
||||
QByteArray hmacKey(QByteArray masterSeed, QByteArray transformedMasterKey);
|
||||
QSharedPointer<Kdf> kdfFromParameters(const QVariantMap& p);
|
||||
QVariantMap kdfToParameters(QSharedPointer<Kdf> kdf);
|
||||
QSharedPointer<Kdf> uuidToKdf(const Uuid& uuid);
|
||||
Uuid kdfToUuid(QSharedPointer<Kdf> kdf);
|
||||
ProtectedStreamAlgo idToProtectedStreamAlgo(quint32 id);
|
||||
}
|
||||
|
||||
#endif // KEEPASSX_KEEPASS2_H
|
||||
|
@ -20,16 +20,27 @@
|
||||
#include "crypto/CryptoHash.h"
|
||||
#include "format/KeePass2.h"
|
||||
|
||||
KeePass2RandomStream::KeePass2RandomStream()
|
||||
: m_cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt)
|
||||
KeePass2RandomStream::KeePass2RandomStream(KeePass2::ProtectedStreamAlgo algo)
|
||||
: m_cipher(mapAlgo(algo), SymmetricCipher::Stream, SymmetricCipher::Encrypt)
|
||||
, m_offset(0)
|
||||
{
|
||||
}
|
||||
|
||||
bool KeePass2RandomStream::init(const QByteArray& key)
|
||||
{
|
||||
return m_cipher.init(CryptoHash::hash(key, CryptoHash::Sha256),
|
||||
KeePass2::INNER_STREAM_SALSA20_IV);
|
||||
switch (m_cipher.algorithm()) {
|
||||
case SymmetricCipher::Salsa20:
|
||||
return m_cipher.init(CryptoHash::hash(key, CryptoHash::Sha256),
|
||||
KeePass2::INNER_STREAM_SALSA20_IV);
|
||||
case SymmetricCipher::ChaCha20: {
|
||||
QByteArray keyIv = CryptoHash::hash(key, CryptoHash::Sha512);
|
||||
return m_cipher.init(keyIv.left(32), keyIv.mid(32, 12));
|
||||
}
|
||||
default:
|
||||
qWarning("Invalid stream algorithm (%d)", m_cipher.algorithm());
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray KeePass2RandomStream::randomBytes(int size, bool* ok)
|
||||
@ -109,3 +120,14 @@ bool KeePass2RandomStream::loadBlock()
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SymmetricCipher::Algorithm KeePass2RandomStream::mapAlgo(KeePass2::ProtectedStreamAlgo algo) {
|
||||
switch (algo) {
|
||||
case KeePass2::ProtectedStreamAlgo::ChaCha20:
|
||||
return SymmetricCipher::ChaCha20;
|
||||
case KeePass2::ProtectedStreamAlgo::Salsa20:
|
||||
return SymmetricCipher::Salsa20;
|
||||
default:
|
||||
return SymmetricCipher::InvalidAlgorithm;
|
||||
}
|
||||
}
|
@ -21,11 +21,13 @@
|
||||
#include <QByteArray>
|
||||
|
||||
#include "crypto/SymmetricCipher.h"
|
||||
#include "KeePass2.h"
|
||||
|
||||
class KeePass2RandomStream
|
||||
{
|
||||
public:
|
||||
KeePass2RandomStream();
|
||||
KeePass2RandomStream(KeePass2::ProtectedStreamAlgo algo);
|
||||
|
||||
bool init(const QByteArray& key);
|
||||
QByteArray randomBytes(int size, bool* ok);
|
||||
QByteArray process(const QByteArray& data, bool* ok);
|
||||
@ -38,6 +40,8 @@ private:
|
||||
SymmetricCipher m_cipher;
|
||||
QByteArray m_buffer;
|
||||
int m_offset;
|
||||
|
||||
static SymmetricCipher::Algorithm mapAlgo(KeePass2::ProtectedStreamAlgo algo);
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_KEEPASS2RANDOMSTREAM_H
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -15,196 +15,20 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "KeePass2Reader.h"
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QFile>
|
||||
#include <QIODevice>
|
||||
|
||||
#include "core/Database.h"
|
||||
#include "core/Endian.h"
|
||||
#include "crypto/CryptoHash.h"
|
||||
#include "format/KeePass2Reader.h"
|
||||
#include "format/KeePass1.h"
|
||||
#include "format/KeePass2.h"
|
||||
#include "format/KeePass2RandomStream.h"
|
||||
#include "format/KeePass2XmlReader.h"
|
||||
#include "streams/HashedBlockStream.h"
|
||||
#include "streams/QtIOCompressor"
|
||||
#include "streams/StoreDataStream.h"
|
||||
#include "streams/SymmetricCipherStream.h"
|
||||
#include "format/Kdbx3Reader.h"
|
||||
#include "format/Kdbx4Reader.h"
|
||||
|
||||
KeePass2Reader::KeePass2Reader()
|
||||
: m_device(nullptr)
|
||||
, m_headerStream(nullptr)
|
||||
, m_error(false)
|
||||
, m_headerEnd(false)
|
||||
, m_saveXml(false)
|
||||
, m_db(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase)
|
||||
{
|
||||
QScopedPointer<Database> db(new Database());
|
||||
m_db = db.data();
|
||||
m_device = device;
|
||||
m_error = false;
|
||||
m_errorStr.clear();
|
||||
m_headerEnd = false;
|
||||
m_xmlData.clear();
|
||||
m_masterSeed.clear();
|
||||
m_transformSeed.clear();
|
||||
m_encryptionIV.clear();
|
||||
m_streamStartBytes.clear();
|
||||
m_protectedStreamKey.clear();
|
||||
|
||||
StoreDataStream headerStream(m_device);
|
||||
headerStream.open(QIODevice::ReadOnly);
|
||||
m_headerStream = &headerStream;
|
||||
|
||||
bool ok;
|
||||
|
||||
quint32 signature1 = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok);
|
||||
if (!ok || signature1 != KeePass2::SIGNATURE_1) {
|
||||
raiseError(tr("Not a KeePass database."));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
quint32 signature2 = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok);
|
||||
if (ok && signature2 == KeePass1::SIGNATURE_2) {
|
||||
raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n"
|
||||
"You can import it by clicking on Database > 'Import KeePass 1 database...'.\n"
|
||||
"This is a one-way migration. You won't be able to open the imported "
|
||||
"database with the old KeePassX 0.4 version."));
|
||||
return nullptr;
|
||||
}
|
||||
else if (!ok || signature2 != KeePass2::SIGNATURE_2) {
|
||||
raiseError(tr("Not a KeePass database."));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
quint32 version = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok)
|
||||
& KeePass2::FILE_VERSION_CRITICAL_MASK;
|
||||
quint32 maxVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK;
|
||||
if (!ok || (version < KeePass2::FILE_VERSION_MIN) || (version > maxVersion)) {
|
||||
raiseError(tr("Unsupported KeePass database version."));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
while (readHeaderField() && !hasError()) {
|
||||
}
|
||||
|
||||
headerStream.close();
|
||||
|
||||
if (hasError()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// check if all required headers were present
|
||||
if (m_masterSeed.isEmpty() || m_transformSeed.isEmpty() || m_encryptionIV.isEmpty()
|
||||
|| m_streamStartBytes.isEmpty() || m_protectedStreamKey.isEmpty()
|
||||
|| m_db->cipher().isNull()) {
|
||||
raiseError("missing database headers");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!m_db->setKey(key, m_transformSeed, false)) {
|
||||
raiseError(tr("Unable to calculate master key"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (m_db->challengeMasterSeed(m_masterSeed) == false) {
|
||||
raiseError(tr("Unable to issue challenge-response."));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CryptoHash hash(CryptoHash::Sha256);
|
||||
hash.addData(m_masterSeed);
|
||||
hash.addData(m_db->challengeResponseKey());
|
||||
hash.addData(m_db->transformedMasterKey());
|
||||
QByteArray finalKey = hash.result();
|
||||
|
||||
SymmetricCipherStream cipherStream(m_device, SymmetricCipher::cipherToAlgorithm(m_db->cipher()),
|
||||
SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||
if (!cipherStream.init(finalKey, m_encryptionIV)) {
|
||||
raiseError(cipherStream.errorString());
|
||||
return nullptr;
|
||||
}
|
||||
if (!cipherStream.open(QIODevice::ReadOnly)) {
|
||||
raiseError(cipherStream.errorString());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QByteArray realStart = cipherStream.read(32);
|
||||
|
||||
if (realStart != m_streamStartBytes) {
|
||||
raiseError(tr("Wrong key or database file is corrupt."));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
HashedBlockStream hashedStream(&cipherStream);
|
||||
if (!hashedStream.open(QIODevice::ReadOnly)) {
|
||||
raiseError(hashedStream.errorString());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QIODevice* xmlDevice;
|
||||
QScopedPointer<QtIOCompressor> ioCompressor;
|
||||
|
||||
if (m_db->compressionAlgo() == Database::CompressionNone) {
|
||||
xmlDevice = &hashedStream;
|
||||
}
|
||||
else {
|
||||
ioCompressor.reset(new QtIOCompressor(&hashedStream));
|
||||
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
|
||||
if (!ioCompressor->open(QIODevice::ReadOnly)) {
|
||||
raiseError(ioCompressor->errorString());
|
||||
return nullptr;
|
||||
}
|
||||
xmlDevice = ioCompressor.data();
|
||||
}
|
||||
|
||||
KeePass2RandomStream randomStream;
|
||||
if (!randomStream.init(m_protectedStreamKey)) {
|
||||
raiseError(randomStream.errorString());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QScopedPointer<QBuffer> buffer;
|
||||
|
||||
if (m_saveXml) {
|
||||
m_xmlData = xmlDevice->readAll();
|
||||
buffer.reset(new QBuffer(&m_xmlData));
|
||||
buffer->open(QIODevice::ReadOnly);
|
||||
xmlDevice = buffer.data();
|
||||
}
|
||||
|
||||
KeePass2XmlReader xmlReader;
|
||||
xmlReader.readDatabase(xmlDevice, m_db, &randomStream);
|
||||
|
||||
if (xmlReader.hasError()) {
|
||||
raiseError(xmlReader.errorString());
|
||||
if (keepDatabase) {
|
||||
return db.take();
|
||||
}
|
||||
else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Q_ASSERT(version < 0x00030001 || !xmlReader.headerHash().isEmpty());
|
||||
|
||||
if (!xmlReader.headerHash().isEmpty()) {
|
||||
QByteArray headerHash = CryptoHash::hash(headerStream.storedData(), CryptoHash::Sha256);
|
||||
if (headerHash != xmlReader.headerHash()) {
|
||||
raiseError("Header doesn't match hash");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return db.take();
|
||||
}
|
||||
#include <QFile>
|
||||
|
||||
/**
|
||||
* Read database from file and detect correct file format.
|
||||
*
|
||||
* @param filename input file
|
||||
* @param key database encryption composite key
|
||||
* @return pointer to the read database, nullptr on failure
|
||||
*/
|
||||
Database* KeePass2Reader::readDatabase(const QString& filename, const CompositeKey& key)
|
||||
{
|
||||
QFile file(filename);
|
||||
@ -223,14 +47,68 @@ Database* KeePass2Reader::readDatabase(const QString& filename, const CompositeK
|
||||
return db.take();
|
||||
}
|
||||
|
||||
bool KeePass2Reader::hasError()
|
||||
/**
|
||||
* Read database from device and detect correct file format.
|
||||
*
|
||||
* @param device input device
|
||||
* @param key database encryption composite key
|
||||
* @param keepDatabase keep database in case of read failure
|
||||
* @return pointer to the read database, nullptr on failure
|
||||
*/
|
||||
Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase)
|
||||
{
|
||||
return m_error;
|
||||
m_error = false;
|
||||
m_errorStr.clear();
|
||||
|
||||
quint32 signature1, signature2;
|
||||
bool ok = KdbxReader::readMagicNumbers(device, signature1, signature2, m_version);
|
||||
|
||||
// mask out minor version
|
||||
m_version &= KeePass2::FILE_VERSION_CRITICAL_MASK;
|
||||
|
||||
if (!ok || signature1 != KeePass2::SIGNATURE_1 || signature2 != KeePass2::SIGNATURE_2) {
|
||||
raiseError(tr("Not a KeePass database."));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (signature2 == KeePass1::SIGNATURE_2) {
|
||||
raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n"
|
||||
"You can import it by clicking on Database > 'Import KeePass 1 database...'.\n"
|
||||
"This is a one-way migration. You won't be able to open the imported "
|
||||
"database with the old KeePassX 0.4 version."));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
quint32 maxVersion = KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK;
|
||||
if (m_version < KeePass2::FILE_VERSION_MIN || m_version > maxVersion) {
|
||||
raiseError(tr("Unsupported KeePass 2 database version."));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// determine file format (KDBX 2/3 or 4)
|
||||
if (m_version < KeePass2::FILE_VERSION_4) {
|
||||
m_reader.reset(new Kdbx3Reader());
|
||||
} else {
|
||||
m_reader.reset(new Kdbx4Reader());
|
||||
}
|
||||
|
||||
m_reader->setSaveXml(m_saveXml);
|
||||
return m_reader->readDatabase(device, key, keepDatabase);
|
||||
}
|
||||
|
||||
QString KeePass2Reader::errorString()
|
||||
bool KeePass2Reader::hasError() const
|
||||
{
|
||||
return m_errorStr;
|
||||
return m_error || (!m_reader.isNull() && m_reader->hasError());
|
||||
}
|
||||
|
||||
QString KeePass2Reader::errorString() const
|
||||
{
|
||||
return !m_reader.isNull() ? m_reader->errorString() : m_errorStr;
|
||||
}
|
||||
|
||||
bool KeePass2Reader::saveXml() const
|
||||
{
|
||||
return m_saveXml;
|
||||
}
|
||||
|
||||
void KeePass2Reader::setSaveXml(bool save)
|
||||
@ -238,202 +116,29 @@ void KeePass2Reader::setSaveXml(bool save)
|
||||
m_saveXml = save;
|
||||
}
|
||||
|
||||
QByteArray KeePass2Reader::xmlData()
|
||||
/**
|
||||
* @return detected KDBX version
|
||||
*/
|
||||
quint32 KeePass2Reader::version() const
|
||||
{
|
||||
return m_xmlData;
|
||||
return m_version;
|
||||
}
|
||||
|
||||
QByteArray KeePass2Reader::streamKey()
|
||||
/**
|
||||
* @return KDBX reader used for reading the input file
|
||||
*/
|
||||
QSharedPointer<KdbxReader> KeePass2Reader::reader() const
|
||||
{
|
||||
return m_protectedStreamKey;
|
||||
return m_reader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Raise an error. Use in case of an unexpected read error.
|
||||
*
|
||||
* @param errorMessage error message
|
||||
*/
|
||||
void KeePass2Reader::raiseError(const QString& errorMessage)
|
||||
{
|
||||
m_error = true;
|
||||
m_errorStr = errorMessage;
|
||||
}
|
||||
|
||||
bool KeePass2Reader::readHeaderField()
|
||||
{
|
||||
QByteArray fieldIDArray = m_headerStream->read(1);
|
||||
if (fieldIDArray.size() != 1) {
|
||||
raiseError("Invalid header id size");
|
||||
return false;
|
||||
}
|
||||
quint8 fieldID = fieldIDArray.at(0);
|
||||
|
||||
bool ok;
|
||||
quint16 fieldLen = Endian::readUInt16(m_headerStream, KeePass2::BYTEORDER, &ok);
|
||||
if (!ok) {
|
||||
raiseError("Invalid header field length");
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray fieldData;
|
||||
if (fieldLen != 0) {
|
||||
fieldData = m_headerStream->read(fieldLen);
|
||||
if (fieldData.size() != fieldLen) {
|
||||
raiseError("Invalid header data length");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
switch (fieldID) {
|
||||
case KeePass2::EndOfHeader:
|
||||
m_headerEnd = true;
|
||||
break;
|
||||
|
||||
case KeePass2::CipherID:
|
||||
setCipher(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::CompressionFlags:
|
||||
setCompressionFlags(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::MasterSeed:
|
||||
setMasterSeed(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::TransformSeed:
|
||||
setTransformSeed(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::TransformRounds:
|
||||
setTransformRounds(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::EncryptionIV:
|
||||
setEncryptionIV(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::ProtectedStreamKey:
|
||||
setProtectedStreamKey(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::StreamStartBytes:
|
||||
setStreamStartBytes(fieldData);
|
||||
break;
|
||||
|
||||
case KeePass2::InnerRandomStreamID:
|
||||
setInnerRandomStreamID(fieldData);
|
||||
break;
|
||||
|
||||
default:
|
||||
qWarning("Unknown header field read: id=%d", fieldID);
|
||||
break;
|
||||
}
|
||||
|
||||
return !m_headerEnd;
|
||||
}
|
||||
|
||||
void KeePass2Reader::setCipher(const QByteArray& data)
|
||||
{
|
||||
if (data.size() != Uuid::Length) {
|
||||
raiseError("Invalid cipher uuid length");
|
||||
}
|
||||
else {
|
||||
Uuid uuid(data);
|
||||
|
||||
if (uuid != KeePass2::CIPHER_AES && uuid != KeePass2::CIPHER_TWOFISH) {
|
||||
raiseError("Unsupported cipher");
|
||||
}
|
||||
else {
|
||||
m_db->setCipher(uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeePass2Reader::setCompressionFlags(const QByteArray& data)
|
||||
{
|
||||
if (data.size() != 4) {
|
||||
raiseError("Invalid compression flags length");
|
||||
}
|
||||
else {
|
||||
quint32 id = Endian::bytesToUInt32(data, KeePass2::BYTEORDER);
|
||||
|
||||
if (id > Database::CompressionAlgorithmMax) {
|
||||
raiseError("Unsupported compression algorithm");
|
||||
}
|
||||
else {
|
||||
m_db->setCompressionAlgo(static_cast<Database::CompressionAlgorithm>(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeePass2Reader::setMasterSeed(const QByteArray& data)
|
||||
{
|
||||
if (data.size() != 32) {
|
||||
raiseError("Invalid master seed size");
|
||||
}
|
||||
else {
|
||||
m_masterSeed = data;
|
||||
}
|
||||
}
|
||||
|
||||
void KeePass2Reader::setTransformSeed(const QByteArray& data)
|
||||
{
|
||||
if (data.size() != 32) {
|
||||
raiseError("Invalid transform seed size");
|
||||
}
|
||||
else {
|
||||
m_transformSeed = data;
|
||||
}
|
||||
}
|
||||
|
||||
void KeePass2Reader::setTransformRounds(const QByteArray& data)
|
||||
{
|
||||
if (data.size() != 8) {
|
||||
raiseError("Invalid transform rounds size");
|
||||
}
|
||||
else {
|
||||
if (!m_db->setTransformRounds(Endian::bytesToUInt64(data, KeePass2::BYTEORDER))) {
|
||||
raiseError(tr("Unable to calculate master key"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeePass2Reader::setEncryptionIV(const QByteArray& data)
|
||||
{
|
||||
if (data.size() != 16) {
|
||||
raiseError("Invalid encryption iv size");
|
||||
}
|
||||
else {
|
||||
m_encryptionIV = data;
|
||||
}
|
||||
}
|
||||
|
||||
void KeePass2Reader::setProtectedStreamKey(const QByteArray& data)
|
||||
{
|
||||
if (data.size() != 32) {
|
||||
raiseError("Invalid stream key size");
|
||||
}
|
||||
else {
|
||||
m_protectedStreamKey = data;
|
||||
}
|
||||
}
|
||||
|
||||
void KeePass2Reader::setStreamStartBytes(const QByteArray& data)
|
||||
{
|
||||
if (data.size() != 32) {
|
||||
raiseError("Invalid start bytes size");
|
||||
}
|
||||
else {
|
||||
m_streamStartBytes = data;
|
||||
}
|
||||
}
|
||||
|
||||
void KeePass2Reader::setInnerRandomStreamID(const QByteArray& data)
|
||||
{
|
||||
if (data.size() != 4) {
|
||||
raiseError("Invalid random stream id size");
|
||||
}
|
||||
else {
|
||||
quint32 id = Endian::bytesToUInt32(data, KeePass2::BYTEORDER);
|
||||
|
||||
if (id != KeePass2::Salsa20) {
|
||||
raiseError("Unsupported random stream algorithm");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -18,56 +18,43 @@
|
||||
#ifndef KEEPASSX_KEEPASS2READER_H
|
||||
#define KEEPASSX_KEEPASS2READER_H
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include "format/KeePass2.h"
|
||||
#include "core/Database.h"
|
||||
#include "keys/CompositeKey.h"
|
||||
#include "KdbxReader.h"
|
||||
|
||||
class Database;
|
||||
class QIODevice;
|
||||
#include <QtGlobal>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QCoreApplication>
|
||||
#include <QScopedPointer>
|
||||
#include <QIODevice>
|
||||
|
||||
class KeePass2Reader
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(KeePass2Reader)
|
||||
Q_DECLARE_TR_FUNCTIONS(KdbxReader)
|
||||
|
||||
public:
|
||||
KeePass2Reader();
|
||||
Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false);
|
||||
Database* readDatabase(const QString& filename, const CompositeKey& key);
|
||||
bool hasError();
|
||||
QString errorString();
|
||||
void setSaveXml(bool save);
|
||||
QByteArray xmlData();
|
||||
QByteArray streamKey();
|
||||
Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false);
|
||||
|
||||
bool hasError() const;
|
||||
QString errorString() const;
|
||||
|
||||
bool saveXml() const;
|
||||
void setSaveXml(bool save);
|
||||
|
||||
QSharedPointer<KdbxReader> reader() const;
|
||||
quint32 version() const;
|
||||
private:
|
||||
void raiseError(const QString& errorMessage);
|
||||
|
||||
bool readHeaderField();
|
||||
bool m_saveXml = false;
|
||||
bool m_error = false;
|
||||
QString m_errorStr = "";
|
||||
|
||||
void setCipher(const QByteArray& data);
|
||||
void setCompressionFlags(const QByteArray& data);
|
||||
void setMasterSeed(const QByteArray& data);
|
||||
void setTransformSeed(const QByteArray& data);
|
||||
void setTransformRounds(const QByteArray& data);
|
||||
void setEncryptionIV(const QByteArray& data);
|
||||
void setProtectedStreamKey(const QByteArray& data);
|
||||
void setStreamStartBytes(const QByteArray& data);
|
||||
void setInnerRandomStreamID(const QByteArray& data);
|
||||
|
||||
QIODevice* m_device;
|
||||
QIODevice* m_headerStream;
|
||||
bool m_error;
|
||||
QString m_errorStr;
|
||||
bool m_headerEnd;
|
||||
bool m_saveXml;
|
||||
QByteArray m_xmlData;
|
||||
|
||||
Database* m_db;
|
||||
QByteArray m_masterSeed;
|
||||
QByteArray m_transformSeed;
|
||||
QByteArray m_encryptionIV;
|
||||
QByteArray m_streamStartBytes;
|
||||
QByteArray m_protectedStreamKey;
|
||||
QSharedPointer<KdbxReader> m_reader;
|
||||
quint32 m_version = 0;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_KEEPASS2READER_H
|
||||
|
@ -19,12 +19,13 @@
|
||||
#include "KeePass2Repair.h"
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QScopedPointer>
|
||||
#include <QRegExp>
|
||||
|
||||
#include "core/Group.h"
|
||||
#include "format/KeePass2.h"
|
||||
#include "format/KeePass2RandomStream.h"
|
||||
#include "format/KeePass2Reader.h"
|
||||
#include "format/KeePass2XmlReader.h"
|
||||
#include "format/Kdbx4Reader.h"
|
||||
#include "format/KdbxXmlReader.h"
|
||||
|
||||
KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device, const CompositeKey& key)
|
||||
{
|
||||
@ -38,7 +39,7 @@ KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device,
|
||||
return qMakePair(NothingTodo, nullptr);
|
||||
}
|
||||
|
||||
QByteArray xmlData = reader.xmlData();
|
||||
QByteArray xmlData = reader.reader()->xmlData();
|
||||
if (!db || xmlData.isEmpty()) {
|
||||
m_errorStr = reader.errorString();
|
||||
return qMakePair(UnableToOpen, nullptr);
|
||||
@ -59,7 +60,7 @@ KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device,
|
||||
|
||||
// try to fix broken databases because of bug #392
|
||||
for (int i = (xmlData.size() - 1); i >= 0; i--) {
|
||||
quint8 ch = static_cast<quint8>(xmlData.at(i));
|
||||
auto ch = static_cast<quint8>(xmlData.at(i));
|
||||
if (ch < 0x20 && ch != 0x09 && ch != 0x0A && ch != 0x0D) {
|
||||
xmlData.remove(i, 1);
|
||||
repairAction = true;
|
||||
@ -71,14 +72,25 @@ KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device,
|
||||
return qMakePair(RepairFailed, nullptr);
|
||||
}
|
||||
|
||||
KeePass2RandomStream randomStream;
|
||||
randomStream.init(reader.streamKey());
|
||||
KeePass2XmlReader xmlReader;
|
||||
KeePass2RandomStream randomStream(reader.reader()->protectedStreamAlgo());
|
||||
randomStream.init(reader.reader()->streamKey());
|
||||
bool hasError;
|
||||
|
||||
QBuffer buffer(&xmlData);
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
xmlReader.readDatabase(&buffer, db.data(), &randomStream);
|
||||
if ((reader.version() & KeePass2::FILE_VERSION_CRITICAL_MASK) < KeePass2::FILE_VERSION_4) {
|
||||
KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_3);
|
||||
xmlReader.readDatabase(&buffer, db.data(), &randomStream);
|
||||
hasError = xmlReader.hasError();
|
||||
} else {
|
||||
auto reader4 = reader.reader().staticCast<Kdbx4Reader>();
|
||||
QHash<QString, QByteArray> pool = reader4->binaryPool();
|
||||
KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_4, pool);
|
||||
xmlReader.readDatabase(&buffer, db.data(), &randomStream);
|
||||
hasError = xmlReader.hasError();
|
||||
}
|
||||
|
||||
if (xmlReader.hasError()) {
|
||||
if (hasError) {
|
||||
return qMakePair(RepairFailed, nullptr);
|
||||
}
|
||||
else {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -15,196 +15,88 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "KeePass2Writer.h"
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QFile>
|
||||
#include <QIODevice>
|
||||
#include <QFile>
|
||||
|
||||
#include "core/Database.h"
|
||||
#include "core/Endian.h"
|
||||
#include "crypto/CryptoHash.h"
|
||||
#include "crypto/Random.h"
|
||||
#include "format/KeePass2RandomStream.h"
|
||||
#include "format/KeePass2XmlWriter.h"
|
||||
#include "streams/HashedBlockStream.h"
|
||||
#include "streams/QtIOCompressor"
|
||||
#include "streams/SymmetricCipherStream.h"
|
||||
#include "crypto/kdf/AesKdf.h"
|
||||
#include "format/KeePass2Writer.h"
|
||||
#include "format/Kdbx3Writer.h"
|
||||
#include "format/Kdbx4Writer.h"
|
||||
|
||||
#define CHECK_RETURN(x) if (!(x)) return;
|
||||
#define CHECK_RETURN_FALSE(x) if (!(x)) return false;
|
||||
|
||||
KeePass2Writer::KeePass2Writer()
|
||||
: m_device(0)
|
||||
, m_error(false)
|
||||
/**
|
||||
* Write a database to a KDBX file.
|
||||
*
|
||||
* @param filename output filename
|
||||
* @param db source database
|
||||
* @return true on success
|
||||
*/
|
||||
bool KeePass2Writer::writeDatabase(const QString& filename, Database* db)
|
||||
{
|
||||
QFile file(filename);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||
raiseError(file.errorString());
|
||||
return false;
|
||||
}
|
||||
return writeDatabase(&file, db);
|
||||
}
|
||||
|
||||
void KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
|
||||
{
|
||||
/**
|
||||
* Write a database to a device in KDBX format.
|
||||
*
|
||||
* @param device output device
|
||||
* @param db source database
|
||||
* @return true on success
|
||||
*/
|
||||
bool KeePass2Writer::writeDatabase(QIODevice* device, Database* db) {
|
||||
m_error = false;
|
||||
m_errorStr.clear();
|
||||
|
||||
QByteArray transformSeed = randomGen()->randomArray(32);
|
||||
QByteArray masterSeed = randomGen()->randomArray(32);
|
||||
QByteArray encryptionIV = randomGen()->randomArray(16);
|
||||
QByteArray protectedStreamKey = randomGen()->randomArray(32);
|
||||
QByteArray startBytes = randomGen()->randomArray(32);
|
||||
QByteArray endOfHeader = "\r\n\r\n";
|
||||
|
||||
if (db->challengeMasterSeed(masterSeed) == false) {
|
||||
raiseError(tr("Unable to issue challenge-response."));
|
||||
return;
|
||||
// determine KDBX3 vs KDBX4
|
||||
if (db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3 && db->publicCustomData().isEmpty()) {
|
||||
m_version = KeePass2::FILE_VERSION_3;
|
||||
m_writer.reset(new Kdbx3Writer());
|
||||
} else {
|
||||
m_version = KeePass2::FILE_VERSION_4;
|
||||
m_writer.reset(new Kdbx4Writer());
|
||||
}
|
||||
|
||||
if (!db->transformKeyWithSeed(transformSeed)) {
|
||||
raiseError(tr("Unable to calculate master key"));
|
||||
return;
|
||||
}
|
||||
|
||||
CryptoHash hash(CryptoHash::Sha256);
|
||||
hash.addData(masterSeed);
|
||||
hash.addData(db->challengeResponseKey());
|
||||
Q_ASSERT(!db->transformedMasterKey().isEmpty());
|
||||
hash.addData(db->transformedMasterKey());
|
||||
QByteArray finalKey = hash.result();
|
||||
|
||||
QBuffer header;
|
||||
header.open(QIODevice::WriteOnly);
|
||||
m_device = &header;
|
||||
|
||||
CHECK_RETURN(writeData(Endian::int32ToBytes(KeePass2::SIGNATURE_1, KeePass2::BYTEORDER)));
|
||||
CHECK_RETURN(writeData(Endian::int32ToBytes(KeePass2::SIGNATURE_2, KeePass2::BYTEORDER)));
|
||||
CHECK_RETURN(writeData(Endian::int32ToBytes(KeePass2::FILE_VERSION, KeePass2::BYTEORDER)));
|
||||
|
||||
CHECK_RETURN(writeHeaderField(KeePass2::CipherID, db->cipher().toByteArray()));
|
||||
CHECK_RETURN(writeHeaderField(KeePass2::CompressionFlags,
|
||||
Endian::int32ToBytes(db->compressionAlgo(),
|
||||
KeePass2::BYTEORDER)));
|
||||
CHECK_RETURN(writeHeaderField(KeePass2::MasterSeed, masterSeed));
|
||||
CHECK_RETURN(writeHeaderField(KeePass2::TransformSeed, db->transformSeed()));
|
||||
CHECK_RETURN(writeHeaderField(KeePass2::TransformRounds,
|
||||
Endian::int64ToBytes(db->transformRounds(),
|
||||
KeePass2::BYTEORDER)));
|
||||
CHECK_RETURN(writeHeaderField(KeePass2::EncryptionIV, encryptionIV));
|
||||
CHECK_RETURN(writeHeaderField(KeePass2::ProtectedStreamKey, protectedStreamKey));
|
||||
CHECK_RETURN(writeHeaderField(KeePass2::StreamStartBytes, startBytes));
|
||||
CHECK_RETURN(writeHeaderField(KeePass2::InnerRandomStreamID,
|
||||
Endian::int32ToBytes(KeePass2::Salsa20,
|
||||
KeePass2::BYTEORDER)));
|
||||
CHECK_RETURN(writeHeaderField(KeePass2::EndOfHeader, endOfHeader));
|
||||
|
||||
header.close();
|
||||
m_device = device;
|
||||
QByteArray headerHash = CryptoHash::hash(header.data(), CryptoHash::Sha256);
|
||||
CHECK_RETURN(writeData(header.data()));
|
||||
|
||||
SymmetricCipherStream cipherStream(device, SymmetricCipher::cipherToAlgorithm(db->cipher()),
|
||||
SymmetricCipher::Cbc, 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);
|
||||
if (!hashedStream.open(QIODevice::WriteOnly)) {
|
||||
raiseError(hashedStream.errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
QScopedPointer<QtIOCompressor> ioCompressor;
|
||||
|
||||
if (db->compressionAlgo() == Database::CompressionNone) {
|
||||
m_device = &hashedStream;
|
||||
}
|
||||
else {
|
||||
ioCompressor.reset(new QtIOCompressor(&hashedStream));
|
||||
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
|
||||
if (!ioCompressor->open(QIODevice::WriteOnly)) {
|
||||
raiseError(ioCompressor->errorString());
|
||||
return;
|
||||
}
|
||||
m_device = ioCompressor.data();
|
||||
}
|
||||
|
||||
KeePass2RandomStream randomStream;
|
||||
if (!randomStream.init(protectedStreamKey)) {
|
||||
raiseError(randomStream.errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
KeePass2XmlWriter xmlWriter;
|
||||
xmlWriter.writeDatabase(m_device, db, &randomStream, headerHash);
|
||||
|
||||
// Explicitly close/reset streams so they are flushed and we can detect
|
||||
// errors. QIODevice::close() resets errorString() etc.
|
||||
if (ioCompressor) {
|
||||
ioCompressor->close();
|
||||
}
|
||||
if (!hashedStream.reset()) {
|
||||
raiseError(hashedStream.errorString());
|
||||
return;
|
||||
}
|
||||
if (!cipherStream.reset()) {
|
||||
raiseError(cipherStream.errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (xmlWriter.hasError()) {
|
||||
raiseError(xmlWriter.errorString());
|
||||
}
|
||||
return m_writer->writeDatabase(device, db);
|
||||
}
|
||||
|
||||
bool KeePass2Writer::writeData(const QByteArray& data)
|
||||
bool KeePass2Writer::hasError() const
|
||||
{
|
||||
if (m_device->write(data) != data.size()) {
|
||||
raiseError(m_device->errorString());
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
return m_error || (m_writer && m_writer->hasError());
|
||||
}
|
||||
|
||||
bool KeePass2Writer::writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data)
|
||||
QString KeePass2Writer::errorString() const
|
||||
{
|
||||
Q_ASSERT(data.size() <= 65535);
|
||||
|
||||
QByteArray fieldIdArr;
|
||||
fieldIdArr[0] = fieldId;
|
||||
CHECK_RETURN_FALSE(writeData(fieldIdArr));
|
||||
CHECK_RETURN_FALSE(writeData(Endian::int16ToBytes(static_cast<quint16>(data.size()),
|
||||
KeePass2::BYTEORDER)));
|
||||
CHECK_RETURN_FALSE(writeData(data));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void KeePass2Writer::writeDatabase(const QString& filename, Database* db)
|
||||
{
|
||||
QFile file(filename);
|
||||
if (!file.open(QIODevice::WriteOnly|QIODevice::Truncate)) {
|
||||
raiseError(file.errorString());
|
||||
return;
|
||||
}
|
||||
writeDatabase(&file, db);
|
||||
}
|
||||
|
||||
bool KeePass2Writer::hasError()
|
||||
{
|
||||
return m_error;
|
||||
}
|
||||
|
||||
QString KeePass2Writer::errorString()
|
||||
{
|
||||
return m_errorStr;
|
||||
return m_writer ? m_writer->errorString() : m_errorStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Raise an error. Use in case of an unexpected write error.
|
||||
*
|
||||
* @param errorMessage error message
|
||||
*/
|
||||
void KeePass2Writer::raiseError(const QString& errorMessage)
|
||||
{
|
||||
m_error = true;
|
||||
m_errorStr = errorMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return KDBX writer used for writing the output file
|
||||
*/
|
||||
QSharedPointer<KdbxWriter> KeePass2Writer::writer() const
|
||||
{
|
||||
return QSharedPointer<KdbxWriter>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return KDBX version used for writing the output file
|
||||
*/
|
||||
quint32 KeePass2Writer::version() const
|
||||
{
|
||||
return m_version;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -18,33 +18,36 @@
|
||||
#ifndef KEEPASSX_KEEPASS2WRITER_H
|
||||
#define KEEPASSX_KEEPASS2WRITER_H
|
||||
|
||||
#include "KdbxWriter.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QScopedPointer>
|
||||
|
||||
#include "format/KeePass2.h"
|
||||
#include "keys/CompositeKey.h"
|
||||
|
||||
class Database;
|
||||
class QIODevice;
|
||||
class Database;
|
||||
|
||||
class KeePass2Writer
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(KeePass2Writer)
|
||||
Q_DECLARE_TR_FUNCTIONS(KeePass2Writer)
|
||||
|
||||
public:
|
||||
KeePass2Writer();
|
||||
void writeDatabase(QIODevice* device, Database* db);
|
||||
void writeDatabase(const QString& filename, Database* db);
|
||||
bool hasError();
|
||||
QString errorString();
|
||||
bool writeDatabase(const QString& filename, Database* db);
|
||||
bool writeDatabase(QIODevice* device, Database* db);
|
||||
|
||||
QSharedPointer<KdbxWriter> writer() const;
|
||||
quint32 version() const;
|
||||
|
||||
bool hasError() const;
|
||||
QString errorString() const;
|
||||
|
||||
private:
|
||||
bool writeData(const QByteArray& data);
|
||||
bool writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data);
|
||||
void raiseError(const QString& errorMessage);
|
||||
|
||||
QIODevice* m_device;
|
||||
bool m_error;
|
||||
QString m_errorStr;
|
||||
bool m_error = false;
|
||||
QString m_errorStr = "";
|
||||
|
||||
QScopedPointer<KdbxWriter> m_writer;
|
||||
quint32 m_version = 0;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_KEEPASS2WRITER_H
|
||||
#endif // KEEPASSX_KEEPASS2READER_H
|
||||
|
@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_KEEPASS2XMLREADER_H
|
||||
#define KEEPASSX_KEEPASS2XMLREADER_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QCoreApplication>
|
||||
#include <QDateTime>
|
||||
#include <QHash>
|
||||
#include <QPair>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#include "core/TimeInfo.h"
|
||||
#include "core/Uuid.h"
|
||||
|
||||
class Database;
|
||||
class Entry;
|
||||
class Group;
|
||||
class KeePass2RandomStream;
|
||||
class Metadata;
|
||||
|
||||
class KeePass2XmlReader
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(KeePass2XmlReader)
|
||||
|
||||
public:
|
||||
KeePass2XmlReader();
|
||||
Database* readDatabase(QIODevice* device);
|
||||
void readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr);
|
||||
Database* readDatabase(const QString& filename);
|
||||
bool hasError();
|
||||
QString errorString();
|
||||
QByteArray headerHash();
|
||||
void setStrictMode(bool strictMode);
|
||||
|
||||
private:
|
||||
bool parseKeePassFile();
|
||||
void parseMeta();
|
||||
void parseMemoryProtection();
|
||||
void parseCustomIcons();
|
||||
void parseIcon();
|
||||
void parseBinaries();
|
||||
void parseCustomData();
|
||||
void parseCustomDataItem();
|
||||
bool parseRoot();
|
||||
Group* parseGroup();
|
||||
void parseDeletedObjects();
|
||||
void parseDeletedObject();
|
||||
Entry* parseEntry(bool history);
|
||||
void parseEntryString(Entry* entry);
|
||||
QPair<QString, QString> parseEntryBinary(Entry* entry);
|
||||
void parseAutoType(Entry* entry);
|
||||
void parseAutoTypeAssoc(Entry* entry);
|
||||
QList<Entry*> parseEntryHistory();
|
||||
TimeInfo parseTimes();
|
||||
|
||||
QString readString();
|
||||
bool readBool();
|
||||
QDateTime readDateTime();
|
||||
QColor readColor();
|
||||
int readNumber();
|
||||
Uuid readUuid();
|
||||
QByteArray readBinary();
|
||||
QByteArray readCompressedBinary();
|
||||
|
||||
Group* getGroup(const Uuid& uuid);
|
||||
Entry* getEntry(const Uuid& uuid);
|
||||
void raiseError(const QString& errorMessage);
|
||||
void skipCurrentElement();
|
||||
|
||||
QXmlStreamReader m_xml;
|
||||
KeePass2RandomStream* m_randomStream;
|
||||
Database* m_db;
|
||||
Metadata* m_meta;
|
||||
Group* m_tmpParent;
|
||||
QHash<Uuid, Group*> m_groups;
|
||||
QHash<Uuid, Entry*> m_entries;
|
||||
QHash<QString, QByteArray> m_binaryPool;
|
||||
QHash<QString, QPair<Entry*, QString> > m_binaryMap;
|
||||
QByteArray m_headerHash;
|
||||
bool m_error;
|
||||
QString m_errorStr;
|
||||
bool m_strictMode;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_KEEPASS2XMLREADER_H
|
@ -1,4 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -17,28 +18,51 @@
|
||||
|
||||
#include "DatabaseSettingsWidget.h"
|
||||
#include "ui_DatabaseSettingsWidget.h"
|
||||
#include "ui_DatabaseSettingsWidgetGeneral.h"
|
||||
#include "ui_DatabaseSettingsWidgetEncryption.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QThread>
|
||||
|
||||
#include "core/Global.h"
|
||||
#include "core/FilePath.h"
|
||||
#include "core/AsyncTask.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "crypto/SymmetricCipher.h"
|
||||
#include "format/KeePass2.h"
|
||||
#include "keys/CompositeKey.h"
|
||||
#include "crypto/kdf/Argon2Kdf.h"
|
||||
#include "MessageBox.h"
|
||||
|
||||
DatabaseSettingsWidget::DatabaseSettingsWidget(QWidget* parent)
|
||||
: DialogyWidget(parent)
|
||||
, m_ui(new Ui::DatabaseSettingsWidget())
|
||||
, m_uiGeneral(new Ui::DatabaseSettingsWidgetGeneral())
|
||||
, m_uiEncryption(new Ui::DatabaseSettingsWidgetEncryption())
|
||||
, m_uiGeneralPage(new QWidget())
|
||||
, m_uiEncryptionPage(new QWidget())
|
||||
, m_db(nullptr)
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
m_uiGeneral->setupUi(m_uiGeneralPage);
|
||||
m_uiEncryption->setupUi(m_uiEncryptionPage);
|
||||
|
||||
connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(save()));
|
||||
connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject()));
|
||||
connect(m_ui->historyMaxItemsCheckBox, SIGNAL(toggled(bool)),
|
||||
m_ui->historyMaxItemsSpinBox, SLOT(setEnabled(bool)));
|
||||
connect(m_ui->historyMaxSizeCheckBox, SIGNAL(toggled(bool)),
|
||||
m_ui->historyMaxSizeSpinBox, SLOT(setEnabled(bool)));
|
||||
connect(m_ui->transformBenchmarkButton, SIGNAL(clicked()), SLOT(transformRoundsBenchmark()));
|
||||
connect(m_uiGeneral->historyMaxItemsCheckBox, SIGNAL(toggled(bool)),
|
||||
m_uiGeneral->historyMaxItemsSpinBox, SLOT(setEnabled(bool)));
|
||||
connect(m_uiGeneral->historyMaxSizeCheckBox, SIGNAL(toggled(bool)),
|
||||
m_uiGeneral->historyMaxSizeSpinBox, SLOT(setEnabled(bool)));
|
||||
connect(m_uiEncryption->transformBenchmarkButton, SIGNAL(clicked()), SLOT(transformRoundsBenchmark()));
|
||||
connect(m_uiEncryption->kdfComboBox, SIGNAL(currentIndexChanged(int)), SLOT(kdfChanged(int)));
|
||||
|
||||
m_ui->categoryList->addCategory(tr("General"), FilePath::instance()->icon("categories", "preferences-other"));
|
||||
m_ui->categoryList->addCategory(tr("Encryption"), FilePath::instance()->icon("actions", "document-encrypt"));
|
||||
m_ui->stackedWidget->addWidget(m_uiGeneralPage);
|
||||
m_ui->stackedWidget->addWidget(m_uiEncryptionPage);
|
||||
|
||||
connect(m_ui->categoryList, SIGNAL(categoryChanged(int)), m_ui->stackedWidget, SLOT(setCurrentIndex(int)));
|
||||
}
|
||||
|
||||
DatabaseSettingsWidget::~DatabaseSettingsWidget()
|
||||
@ -51,56 +75,113 @@ void DatabaseSettingsWidget::load(Database* db)
|
||||
|
||||
Metadata* meta = m_db->metadata();
|
||||
|
||||
m_ui->dbNameEdit->setText(meta->name());
|
||||
m_ui->dbDescriptionEdit->setText(meta->description());
|
||||
m_ui->recycleBinEnabledCheckBox->setChecked(meta->recycleBinEnabled());
|
||||
m_ui->defaultUsernameEdit->setText(meta->defaultUserName());
|
||||
m_ui->AlgorithmComboBox->setCurrentIndex(SymmetricCipher::cipherToAlgorithm(m_db->cipher()));
|
||||
m_ui->transformRoundsSpinBox->setValue(m_db->transformRounds());
|
||||
m_uiGeneral->dbNameEdit->setText(meta->name());
|
||||
m_uiGeneral->dbDescriptionEdit->setText(meta->description());
|
||||
m_uiGeneral->recycleBinEnabledCheckBox->setChecked(meta->recycleBinEnabled());
|
||||
m_uiGeneral->defaultUsernameEdit->setText(meta->defaultUserName());
|
||||
if (meta->historyMaxItems() > -1) {
|
||||
m_ui->historyMaxItemsSpinBox->setValue(meta->historyMaxItems());
|
||||
m_ui->historyMaxItemsCheckBox->setChecked(true);
|
||||
}
|
||||
else {
|
||||
m_ui->historyMaxItemsSpinBox->setValue(Metadata::DefaultHistoryMaxItems);
|
||||
m_ui->historyMaxItemsCheckBox->setChecked(false);
|
||||
m_uiGeneral->historyMaxItemsSpinBox->setValue(meta->historyMaxItems());
|
||||
m_uiGeneral->historyMaxItemsCheckBox->setChecked(true);
|
||||
} else {
|
||||
m_uiGeneral->historyMaxItemsSpinBox->setValue(Metadata::DefaultHistoryMaxItems);
|
||||
m_uiGeneral->historyMaxItemsCheckBox->setChecked(false);
|
||||
}
|
||||
int historyMaxSizeMiB = qRound(meta->historyMaxSize() / qreal(1048576));
|
||||
if (historyMaxSizeMiB > 0) {
|
||||
m_ui->historyMaxSizeSpinBox->setValue(historyMaxSizeMiB);
|
||||
m_ui->historyMaxSizeCheckBox->setChecked(true);
|
||||
}
|
||||
else {
|
||||
m_ui->historyMaxSizeSpinBox->setValue(Metadata::DefaultHistoryMaxSize);
|
||||
m_ui->historyMaxSizeCheckBox->setChecked(false);
|
||||
m_uiGeneral->historyMaxSizeSpinBox->setValue(historyMaxSizeMiB);
|
||||
m_uiGeneral->historyMaxSizeCheckBox->setChecked(true);
|
||||
} else {
|
||||
m_uiGeneral->historyMaxSizeSpinBox->setValue(Metadata::DefaultHistoryMaxSize);
|
||||
m_uiGeneral->historyMaxSizeCheckBox->setChecked(false);
|
||||
}
|
||||
|
||||
m_ui->dbNameEdit->setFocus();
|
||||
m_uiEncryption->algorithmComboBox->clear();
|
||||
for (auto& cipher: asConst(KeePass2::CIPHERS)) {
|
||||
m_uiEncryption->algorithmComboBox->addItem(cipher.second, cipher.first.toByteArray());
|
||||
}
|
||||
int cipherIndex = m_uiEncryption->algorithmComboBox->findData(m_db->cipher().toByteArray());
|
||||
if (cipherIndex > -1) {
|
||||
m_uiEncryption->algorithmComboBox->setCurrentIndex(cipherIndex);
|
||||
}
|
||||
|
||||
// Setup kdf combo box
|
||||
m_uiEncryption->kdfComboBox->blockSignals(true);
|
||||
m_uiEncryption->kdfComboBox->clear();
|
||||
for (auto& kdf: asConst(KeePass2::KDFS)) {
|
||||
m_uiEncryption->kdfComboBox->addItem(kdf.second, kdf.first.toByteArray());
|
||||
}
|
||||
m_uiEncryption->kdfComboBox->blockSignals(false);
|
||||
|
||||
auto kdfUuid = m_db->kdf()->uuid();
|
||||
int kdfIndex = m_uiEncryption->kdfComboBox->findData(kdfUuid.toByteArray());
|
||||
if (kdfIndex > -1) {
|
||||
m_uiEncryption->kdfComboBox->setCurrentIndex(kdfIndex);
|
||||
kdfChanged(kdfIndex);
|
||||
}
|
||||
|
||||
// properly initialize parallelism spin box (may be overwritten by actual KDF values)
|
||||
m_uiEncryption->parallelismSpinBox->setValue(QThread::idealThreadCount());
|
||||
|
||||
// Setup kdf parameters
|
||||
auto kdf = m_db->kdf();
|
||||
m_uiEncryption->transformRoundsSpinBox->setValue(kdf->rounds());
|
||||
if (kdfUuid == KeePass2::KDF_ARGON2) {
|
||||
auto argon2Kdf = kdf.staticCast<Argon2Kdf>();
|
||||
m_uiEncryption->memorySpinBox->setValue(static_cast<int>(argon2Kdf->memory()) / (1 << 10));
|
||||
m_uiEncryption->parallelismSpinBox->setValue(argon2Kdf->parallelism());
|
||||
}
|
||||
|
||||
m_uiGeneral->dbNameEdit->setFocus();
|
||||
m_ui->categoryList->setCurrentCategory(0);
|
||||
}
|
||||
|
||||
void DatabaseSettingsWidget::save()
|
||||
{
|
||||
// first perform safety check for KDF rounds
|
||||
auto kdf = KeePass2::uuidToKdf(Uuid(m_uiEncryption->kdfComboBox->currentData().toByteArray()));
|
||||
if (kdf->uuid() == KeePass2::KDF_ARGON2 && m_uiEncryption->transformRoundsSpinBox->value() > 10000) {
|
||||
QMessageBox warning;
|
||||
warning.setIcon(QMessageBox::Warning);
|
||||
warning.setWindowTitle(tr("Number of rounds too high"));
|
||||
warning.setText(tr("You are using a very high number of key transform rounds with Argon2.\n\n"
|
||||
"If you keep this number, your database may take hours or days (or even longer) to open!"));
|
||||
auto ok = warning.addButton(tr("Understood, keep number"), QMessageBox::ButtonRole::AcceptRole);
|
||||
auto cancel = warning.addButton(tr("Cancel"), QMessageBox::ButtonRole::RejectRole);
|
||||
warning.setDefaultButton(cancel);
|
||||
warning.exec();
|
||||
if (warning.clickedButton() != ok) {
|
||||
return;
|
||||
}
|
||||
} else if ((kdf->uuid() == KeePass2::KDF_AES_KDBX3 || kdf->uuid() == KeePass2::KDF_AES_KDBX4)
|
||||
&& m_uiEncryption->transformRoundsSpinBox->value() < 100000) {
|
||||
QMessageBox warning;
|
||||
warning.setIcon(QMessageBox::Warning);
|
||||
warning.setWindowTitle(tr("Number of rounds too low"));
|
||||
warning.setText(tr("You are using a very low number of key transform rounds with AES-KDF.\n\n"
|
||||
"If you keep this number, your database may be too easy to crack!"));
|
||||
auto ok = warning.addButton(tr("Understood, keep number"), QMessageBox::ButtonRole::AcceptRole);
|
||||
auto cancel = warning.addButton(tr("Cancel"), QMessageBox::ButtonRole::RejectRole);
|
||||
warning.setDefaultButton(cancel);
|
||||
warning.exec();
|
||||
if (warning.clickedButton() != ok) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Metadata* meta = m_db->metadata();
|
||||
|
||||
meta->setName(m_ui->dbNameEdit->text());
|
||||
meta->setDescription(m_ui->dbDescriptionEdit->text());
|
||||
meta->setDefaultUserName(m_ui->defaultUsernameEdit->text());
|
||||
m_db->setCipher(SymmetricCipher::algorithmToCipher(static_cast<SymmetricCipher::Algorithm>
|
||||
(m_ui->AlgorithmComboBox->currentIndex())));
|
||||
meta->setRecycleBinEnabled(m_ui->recycleBinEnabledCheckBox->isChecked());
|
||||
if (static_cast<quint64>(m_ui->transformRoundsSpinBox->value()) != m_db->transformRounds()) {
|
||||
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
|
||||
m_db->setTransformRounds(m_ui->transformRoundsSpinBox->value());
|
||||
QApplication::restoreOverrideCursor();
|
||||
}
|
||||
meta->setName(m_uiGeneral->dbNameEdit->text());
|
||||
meta->setDescription(m_uiGeneral->dbDescriptionEdit->text());
|
||||
meta->setDefaultUserName(m_uiGeneral->defaultUsernameEdit->text());
|
||||
meta->setRecycleBinEnabled(m_uiGeneral->recycleBinEnabledCheckBox->isChecked());
|
||||
meta->setSettingsChanged(QDateTime::currentDateTimeUtc());
|
||||
|
||||
bool truncate = false;
|
||||
|
||||
int historyMaxItems;
|
||||
if (m_ui->historyMaxItemsCheckBox->isChecked()) {
|
||||
historyMaxItems = m_ui->historyMaxItemsSpinBox->value();
|
||||
}
|
||||
else {
|
||||
if (m_uiGeneral->historyMaxItemsCheckBox->isChecked()) {
|
||||
historyMaxItems = m_uiGeneral->historyMaxItemsSpinBox->value();
|
||||
} else {
|
||||
historyMaxItems = -1;
|
||||
}
|
||||
if (historyMaxItems != meta->historyMaxItems()) {
|
||||
@ -109,10 +190,9 @@ void DatabaseSettingsWidget::save()
|
||||
}
|
||||
|
||||
int historyMaxSize;
|
||||
if (m_ui->historyMaxSizeCheckBox->isChecked()) {
|
||||
historyMaxSize = m_ui->historyMaxSizeSpinBox->value() * 1048576;
|
||||
}
|
||||
else {
|
||||
if (m_uiGeneral->historyMaxSizeCheckBox->isChecked()) {
|
||||
historyMaxSize = m_uiGeneral->historyMaxSizeSpinBox->value() * 1048576;
|
||||
} else {
|
||||
historyMaxSize = -1;
|
||||
}
|
||||
if (historyMaxSize != meta->historyMaxSize()) {
|
||||
@ -124,6 +204,28 @@ void DatabaseSettingsWidget::save()
|
||||
truncateHistories();
|
||||
}
|
||||
|
||||
m_db->setCipher(Uuid(m_uiEncryption->algorithmComboBox->currentData().toByteArray()));
|
||||
|
||||
// Save kdf parameters
|
||||
kdf->setRounds(m_uiEncryption->transformRoundsSpinBox->value());
|
||||
if (kdf->uuid() == KeePass2::KDF_ARGON2) {
|
||||
auto argon2Kdf = kdf.staticCast<Argon2Kdf>();
|
||||
argon2Kdf->setMemory(static_cast<quint64>(m_uiEncryption->memorySpinBox->value()) * (1 << 10));
|
||||
argon2Kdf->setParallelism(static_cast<quint32>(m_uiEncryption->parallelismSpinBox->value()));
|
||||
}
|
||||
|
||||
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
|
||||
// TODO: we should probably use AsyncTask::runAndWaitForFuture() here,
|
||||
// but not without making Database thread-safe
|
||||
bool ok = m_db->changeKdf(kdf);
|
||||
QApplication::restoreOverrideCursor();
|
||||
|
||||
if (!ok) {
|
||||
MessageBox::warning(this, tr("KDF unchanged"),
|
||||
tr("Failed to transform key with new KDF parameters; KDF unchanged."),
|
||||
QMessageBox::Ok);
|
||||
}
|
||||
|
||||
emit editFinished(true);
|
||||
}
|
||||
|
||||
@ -135,10 +237,29 @@ void DatabaseSettingsWidget::reject()
|
||||
void DatabaseSettingsWidget::transformRoundsBenchmark()
|
||||
{
|
||||
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
|
||||
int rounds = CompositeKey::transformKeyBenchmark(1000);
|
||||
if (rounds != -1) {
|
||||
m_ui->transformRoundsSpinBox->setValue(rounds);
|
||||
m_uiEncryption->transformBenchmarkButton->setEnabled(false);
|
||||
m_uiEncryption->transformRoundsSpinBox->setFocus();
|
||||
|
||||
// Create a new kdf with the current parameters
|
||||
auto kdf = KeePass2::uuidToKdf(Uuid(m_uiEncryption->kdfComboBox->currentData().toByteArray()));
|
||||
kdf->setRounds(m_uiEncryption->transformRoundsSpinBox->value());
|
||||
if (kdf->uuid() == KeePass2::KDF_ARGON2) {
|
||||
auto argon2Kdf = kdf.staticCast<Argon2Kdf>();
|
||||
if (!argon2Kdf->setMemory(static_cast<quint64>(m_uiEncryption->memorySpinBox->value()) * (1 << 10))) {
|
||||
m_uiEncryption->memorySpinBox->setValue(static_cast<int>(argon2Kdf->memory() / (1 << 10)));
|
||||
}
|
||||
if (!argon2Kdf->setParallelism(static_cast<quint32>(m_uiEncryption->parallelismSpinBox->value()))) {
|
||||
m_uiEncryption->parallelismSpinBox->setValue(argon2Kdf->parallelism());
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the number of rounds required to meet 1 second delay
|
||||
int rounds = AsyncTask::runAndWaitForFuture([&kdf]() {
|
||||
return kdf->benchmark(1000);
|
||||
});
|
||||
|
||||
m_uiEncryption->transformRoundsSpinBox->setValue(rounds);
|
||||
m_uiEncryption->transformBenchmarkButton->setEnabled(true);
|
||||
QApplication::restoreOverrideCursor();
|
||||
}
|
||||
|
||||
@ -149,3 +270,18 @@ void DatabaseSettingsWidget::truncateHistories()
|
||||
entry->truncateHistory();
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseSettingsWidget::kdfChanged(int index)
|
||||
{
|
||||
Uuid id(m_uiEncryption->kdfComboBox->itemData(index).toByteArray());
|
||||
|
||||
bool memoryEnabled = id == KeePass2::KDF_ARGON2;
|
||||
m_uiEncryption->memoryUsageLabel->setEnabled(memoryEnabled);
|
||||
m_uiEncryption->memorySpinBox->setEnabled(memoryEnabled);
|
||||
|
||||
bool parallelismEnabled = id == KeePass2::KDF_ARGON2;
|
||||
m_uiEncryption->parallelismLabel->setEnabled(parallelismEnabled);
|
||||
m_uiEncryption->parallelismSpinBox->setEnabled(parallelismEnabled);
|
||||
|
||||
transformRoundsBenchmark();
|
||||
}
|
||||
|
@ -19,22 +19,30 @@
|
||||
#define KEEPASSX_DATABASESETTINGSWIDGET_H
|
||||
|
||||
#include <QScopedPointer>
|
||||
#include <QWidget>
|
||||
#include <QSpinBox>
|
||||
#include <QLayout>
|
||||
|
||||
#include "gui/DialogyWidget.h"
|
||||
#include "crypto/kdf/Kdf.h"
|
||||
|
||||
class Database;
|
||||
|
||||
namespace Ui {
|
||||
class DatabaseSettingsWidget;
|
||||
namespace Ui
|
||||
{
|
||||
class DatabaseSettingsWidget;
|
||||
class DatabaseSettingsWidgetGeneral;
|
||||
class DatabaseSettingsWidgetEncryption;
|
||||
}
|
||||
|
||||
class DatabaseSettingsWidget : public DialogyWidget
|
||||
class DatabaseSettingsWidget: public DialogyWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DatabaseSettingsWidget(QWidget* parent = nullptr);
|
||||
~DatabaseSettingsWidget();
|
||||
Q_DISABLE_COPY(DatabaseSettingsWidget)
|
||||
|
||||
void load(Database* db);
|
||||
|
||||
@ -45,14 +53,17 @@ private slots:
|
||||
void save();
|
||||
void reject();
|
||||
void transformRoundsBenchmark();
|
||||
void kdfChanged(int index);
|
||||
|
||||
private:
|
||||
void truncateHistories();
|
||||
|
||||
const QScopedPointer<Ui::DatabaseSettingsWidget> m_ui;
|
||||
const QScopedPointer<Ui::DatabaseSettingsWidgetGeneral> m_uiGeneral;
|
||||
const QScopedPointer<Ui::DatabaseSettingsWidgetEncryption> m_uiEncryption;
|
||||
QWidget* m_uiGeneralPage;
|
||||
QWidget* m_uiEncryptionPage;
|
||||
Database* m_db;
|
||||
|
||||
Q_DISABLE_COPY(DatabaseSettingsWidget)
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_DATABASESETTINGSWIDGET_H
|
||||
|
@ -6,263 +6,46 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>600</width>
|
||||
<height>340</height>
|
||||
<width>1082</width>
|
||||
<height>578</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,2,5,1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0">
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5" stretch="0,1,0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,1">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
<widget class="CategoryListWidget" name="categoryList" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>800</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
<widget class="QStackedWidget" name="stackedWidget">
|
||||
<property name="currentIndex">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="3" column="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="transformRoundsSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000000000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="transformBenchmarkButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>25</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Benchmark</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="1" alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="dbNameLabel">
|
||||
<property name="text">
|
||||
<string>Database name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QCheckBox" name="historyMaxSizeCheckBox">
|
||||
<property name="text">
|
||||
<string>Max. history size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1" alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="transformRoundsLabel">
|
||||
<property name="text">
|
||||
<string>Transform rounds:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QCheckBox" name="historyMaxItemsCheckBox">
|
||||
<property name="text">
|
||||
<string>Max. history items:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLineEdit" name="dbNameEdit"/>
|
||||
</item>
|
||||
<item row="6" column="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="historyMaxItemsSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>2000000000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="1" alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="defaultUsernameLabel">
|
||||
<property name="text">
|
||||
<string>Default username:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLineEdit" name="dbDescriptionEdit"/>
|
||||
</item>
|
||||
<item row="7" column="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="historyMaxSizeSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> MiB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>2000000000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="5" column="2">
|
||||
<widget class="QCheckBox" name="recycleBinEnabledCheckBox">
|
||||
<property name="text">
|
||||
<string>Use recycle bin</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QLineEdit" name="defaultUsernameEdit">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="dbDescriptionLabel">
|
||||
<property name="text">
|
||||
<string>Database description:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QComboBox" name="AlgorithmComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>AES: 256 Bit (default)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Twofish: 256 Bit</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="AlgorithmLabel">
|
||||
<property name="text">
|
||||
<string>Algorithm:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>dbNameEdit</tabstop>
|
||||
<tabstop>dbDescriptionEdit</tabstop>
|
||||
<tabstop>transformRoundsSpinBox</tabstop>
|
||||
<tabstop>transformBenchmarkButton</tabstop>
|
||||
<tabstop>defaultUsernameEdit</tabstop>
|
||||
<tabstop>recycleBinEnabledCheckBox</tabstop>
|
||||
<tabstop>historyMaxItemsCheckBox</tabstop>
|
||||
<tabstop>historyMaxItemsSpinBox</tabstop>
|
||||
<tabstop>historyMaxSizeCheckBox</tabstop>
|
||||
<tabstop>historyMaxSizeSpinBox</tabstop>
|
||||
<tabstop>buttonBox</tabstop>
|
||||
</tabstops>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>CategoryListWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/CategoryListWidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
</ui>
|
192
src/gui/DatabaseSettingsWidgetEncryption.ui
Normal file
192
src/gui/DatabaseSettingsWidgetEncryption.ui
Normal file
@ -0,0 +1,192 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DatabaseSettingsWidgetEncryption</class>
|
||||
<widget class="QWidget" name="DatabaseSettingsWidgetEncryption">
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="algorithmLabel">
|
||||
<property name="text">
|
||||
<string>Encryption Algorithm:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="algorithmComboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>AES: 256 Bit (default)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Twofish: 256 Bit</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="kdfLabel">
|
||||
<property name="text">
|
||||
<string>Key Derivation Function:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="kdfComboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="transformRoundsLabel">
|
||||
<property name="text">
|
||||
<string>Transform rounds:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="40,40,0">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="transformRoundsSpinBox">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000000000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="transformBenchmarkButton">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::WheelFocus</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Benchmark 1-second delay</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="memoryUsageLabel">
|
||||
<property name="text">
|
||||
<string>Memory Usage:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="memorySpinBox">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> MB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1048576</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>64</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="parallelismLabel">
|
||||
<property name="text">
|
||||
<string>Parallelism:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QSpinBox" name="parallelismSpinBox">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> thread</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>128</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
154
src/gui/DatabaseSettingsWidgetGeneral.ui
Normal file
154
src/gui/DatabaseSettingsWidgetGeneral.ui
Normal file
@ -0,0 +1,154 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DatabaseSettingsWidgetGeneral</class>
|
||||
<widget class="QWidget" name="DatabaseSettingsWidgetGeneral">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Database Meta Data</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="dbNameLabel">
|
||||
<property name="text">
|
||||
<string>Database name:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="dbNameEdit"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="dbDescriptionLabel">
|
||||
<property name="text">
|
||||
<string>Database description:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="dbDescriptionEdit"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="defaultUsernameLabel">
|
||||
<property name="text">
|
||||
<string>Default username:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="defaultUsernameEdit">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>History Settings</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="historyMaxItemsCheckBox">
|
||||
<property name="text">
|
||||
<string>Max. history items:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="historyMaxSizeCheckBox">
|
||||
<property name="text">
|
||||
<string>Max. history size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="historyMaxSizeSpinBox">
|
||||
<property name="suffix">
|
||||
<string> MiB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>2000000000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="historyMaxItemsSpinBox">
|
||||
<property name="maximum">
|
||||
<number>2000000000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="recycleBinEnabledCheckBox">
|
||||
<property name="text">
|
||||
<string>Use recycle bin</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -809,7 +809,7 @@ void DatabaseWidget::updateMasterKey(bool accepted)
|
||||
|
||||
if (accepted) {
|
||||
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
|
||||
bool result = m_db->setKey(m_changeMasterKeyWidget->newMasterKey());
|
||||
bool result = m_db->setKey(m_changeMasterKeyWidget->newMasterKey(), true, true);
|
||||
QApplication::restoreOverrideCursor();
|
||||
|
||||
if (!result) {
|
||||
|
@ -17,18 +17,13 @@
|
||||
*/
|
||||
|
||||
#include "CompositeKey.h"
|
||||
#include "CompositeKey_p.h"
|
||||
#include "ChallengeResponseKey.h"
|
||||
|
||||
#include <QElapsedTimer>
|
||||
#include <QFile>
|
||||
#include <QtConcurrent>
|
||||
#include <format/KeePass2.h>
|
||||
|
||||
#include "core/Global.h"
|
||||
#include "crypto/kdf/AesKdf.h"
|
||||
#include "crypto/CryptoHash.h"
|
||||
#include "crypto/SymmetricCipher.h"
|
||||
#include "keys/FileKey.h"
|
||||
#include "keys/PasswordKey.h"
|
||||
|
||||
CompositeKey::CompositeKey()
|
||||
{
|
||||
@ -80,7 +75,31 @@ CompositeKey& CompositeKey::operator=(const CompositeKey& key)
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get raw key hash as bytes.
|
||||
*
|
||||
* The key hash does not contain contributions by challenge-response components for
|
||||
* backwards compatibility with KeePassXC's pre-KDBX4 challenge-response
|
||||
* implementation. To include challenge-response in the raw key,
|
||||
* use \link CompositeKey::rawKey(const QByteArray*) instead.
|
||||
*
|
||||
* @return key hash
|
||||
*/
|
||||
QByteArray CompositeKey::rawKey() const
|
||||
{
|
||||
return rawKey(nullptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get raw key hash as bytes.
|
||||
*
|
||||
* Challenge-response key components will use the provided <tt>transformSeed</tt>
|
||||
* as a challenge to acquire their key contribution.
|
||||
*
|
||||
* @param transformSeed transform seed to challenge or nullptr to exclude challenge-response components
|
||||
* @return key hash
|
||||
*/
|
||||
QByteArray CompositeKey::rawKey(const QByteArray* transformSeed) const
|
||||
{
|
||||
CryptoHash cryptoHash(CryptoHash::Sha256);
|
||||
|
||||
@ -88,67 +107,38 @@ QByteArray CompositeKey::rawKey() const
|
||||
cryptoHash.addData(key->rawKey());
|
||||
}
|
||||
|
||||
if (transformSeed) {
|
||||
QByteArray challengeResult;
|
||||
challenge(*transformSeed, challengeResult);
|
||||
cryptoHash.addData(challengeResult);
|
||||
}
|
||||
|
||||
return cryptoHash.result();
|
||||
}
|
||||
|
||||
QByteArray CompositeKey::transform(const QByteArray& seed, quint64 rounds,
|
||||
bool* ok, QString* errorString) const
|
||||
/**
|
||||
* Transform this composite key.
|
||||
*
|
||||
* If using AES-KDF as transform function, the transformed key will not include
|
||||
* any challenge-response components. Only static key components will be hashed
|
||||
* for backwards-compatibility with KeePassXC's KDBX3 implementation, which added
|
||||
* challenge response key components after key transformation.
|
||||
* KDBX4+ KDFs transform the whole key including challenge-response components.
|
||||
*
|
||||
* @param kdf key derivation function
|
||||
* @param result transformed key hash
|
||||
* @return true on success
|
||||
*/
|
||||
bool CompositeKey::transform(const Kdf& kdf, QByteArray& result) 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,
|
||||
&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 (kdf.uuid() == KeePass2::KDF_AES_KDBX3) {
|
||||
// legacy KDBX3 AES-KDF, challenge response is added later to the hash
|
||||
return kdf.transform(rawKey(), result);
|
||||
}
|
||||
|
||||
if (!okRight) {
|
||||
*errorString = errorStringRight;
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
return CryptoHash::hash(transformed, CryptoHash::Sha256);
|
||||
}
|
||||
|
||||
QByteArray CompositeKey::transformKeyRaw(const QByteArray& key, const QByteArray& seed,
|
||||
quint64 rounds, bool* ok, QString* errorString)
|
||||
{
|
||||
QByteArray iv(16, 0);
|
||||
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb,
|
||||
SymmetricCipher::Encrypt);
|
||||
if (!cipher.init(seed, iv)) {
|
||||
*ok = false;
|
||||
*errorString = cipher.errorString();
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
QByteArray result = key;
|
||||
|
||||
if (!cipher.processInPlace(result, rounds)) {
|
||||
*ok = false;
|
||||
*errorString = cipher.errorString();
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
*ok = true;
|
||||
return result;
|
||||
QByteArray seed = kdf.seed();
|
||||
Q_ASSERT(!seed.isEmpty());
|
||||
return kdf.transform(rawKey(&seed), result);
|
||||
}
|
||||
|
||||
bool CompositeKey::challenge(const QByteArray& seed, QByteArray& result) const
|
||||
@ -165,6 +155,7 @@ bool CompositeKey::challenge(const QByteArray& seed, QByteArray& result) const
|
||||
for (const auto key : m_challengeResponseKeys) {
|
||||
// if the device isn't present or fails, return an error
|
||||
if (!key->challenge(seed)) {
|
||||
qWarning("Failed to issue challenge");
|
||||
return false;
|
||||
}
|
||||
cryptoHash.addData(key->rawKey());
|
||||
@ -183,53 +174,3 @@ void CompositeKey::addChallengeResponseKey(QSharedPointer<ChallengeResponseKey>
|
||||
{
|
||||
m_challengeResponseKeys.append(key);
|
||||
}
|
||||
|
||||
|
||||
int CompositeKey::transformKeyBenchmark(int msec)
|
||||
{
|
||||
TransformKeyBenchmarkThread thread1(msec);
|
||||
TransformKeyBenchmarkThread thread2(msec);
|
||||
|
||||
thread1.start();
|
||||
thread2.start();
|
||||
|
||||
thread1.wait();
|
||||
thread2.wait();
|
||||
|
||||
return qMin(thread1.rounds(), thread2.rounds());
|
||||
}
|
||||
|
||||
|
||||
TransformKeyBenchmarkThread::TransformKeyBenchmarkThread(int msec)
|
||||
: m_msec(msec)
|
||||
, m_rounds(0)
|
||||
{
|
||||
Q_ASSERT(msec > 0);
|
||||
}
|
||||
|
||||
int TransformKeyBenchmarkThread::rounds()
|
||||
{
|
||||
return m_rounds;
|
||||
}
|
||||
|
||||
void TransformKeyBenchmarkThread::run()
|
||||
{
|
||||
QByteArray key = QByteArray(16, '\x7E');
|
||||
QByteArray seed = QByteArray(32, '\x4B');
|
||||
QByteArray iv(16, 0);
|
||||
|
||||
SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb,
|
||||
SymmetricCipher::Encrypt);
|
||||
cipher.init(seed, iv);
|
||||
|
||||
QElapsedTimer t;
|
||||
t.start();
|
||||
|
||||
do {
|
||||
if (!cipher.processInPlace(key, 10000)) {
|
||||
m_rounds = -1;
|
||||
return;
|
||||
}
|
||||
m_rounds += 10000;
|
||||
} while (!t.hasExpired(m_msec));
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <QString>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include "crypto/kdf/Kdf.h"
|
||||
#include "keys/Key.h"
|
||||
#include "keys/ChallengeResponseKey.h"
|
||||
|
||||
@ -31,26 +32,21 @@ class CompositeKey : public Key
|
||||
public:
|
||||
CompositeKey();
|
||||
CompositeKey(const CompositeKey& key);
|
||||
~CompositeKey();
|
||||
~CompositeKey() override;
|
||||
void clear();
|
||||
bool isEmpty() const;
|
||||
CompositeKey* clone() const;
|
||||
CompositeKey* clone() const override;
|
||||
CompositeKey& operator=(const CompositeKey& key);
|
||||
|
||||
QByteArray rawKey() const;
|
||||
QByteArray transform(const QByteArray& seed, quint64 rounds,
|
||||
bool* ok, QString* errorString) const;
|
||||
QByteArray rawKey() const override;
|
||||
QByteArray rawKey(const QByteArray* transformSeed) const;
|
||||
bool transform(const Kdf& kdf, QByteArray& result) const Q_REQUIRED_RESULT;
|
||||
bool challenge(const QByteArray& seed, QByteArray &result) const;
|
||||
|
||||
void addKey(const Key& key);
|
||||
void addChallengeResponseKey(QSharedPointer<ChallengeResponseKey> key);
|
||||
|
||||
static int transformKeyBenchmark(int msec);
|
||||
|
||||
private:
|
||||
static QByteArray transformKeyRaw(const QByteArray& key, const QByteArray& seed,
|
||||
quint64 rounds, bool* ok, QString* errorString);
|
||||
|
||||
QList<Key*> m_keys;
|
||||
QList<QSharedPointer<ChallengeResponseKey>> m_challengeResponseKeys;
|
||||
};
|
||||
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_COMPOSITEKEY_P_H
|
||||
#define KEEPASSX_COMPOSITEKEY_P_H
|
||||
|
||||
#include <QThread>
|
||||
|
||||
class TransformKeyBenchmarkThread : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TransformKeyBenchmarkThread(int msec);
|
||||
int rounds();
|
||||
|
||||
protected:
|
||||
void run();
|
||||
|
||||
private:
|
||||
int m_msec;
|
||||
int m_rounds;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_COMPOSITEKEY_P_H
|
@ -27,7 +27,6 @@
|
||||
#include "crypto/Crypto.h"
|
||||
#include "gui/Application.h"
|
||||
#include "gui/MainWindow.h"
|
||||
#include "gui/csvImport/CsvImportWizard.h"
|
||||
#include "gui/MessageBox.h"
|
||||
|
||||
#if defined(WITH_ASAN) && defined(WITH_LSAN)
|
||||
|
@ -130,7 +130,7 @@ bool HashedBlockStream::readHashedBlock()
|
||||
{
|
||||
bool ok;
|
||||
|
||||
quint32 index = Endian::readUInt32(m_baseDevice, ByteOrder, &ok);
|
||||
quint32 index = Endian::readSizedInt<quint32>(m_baseDevice, ByteOrder, &ok);
|
||||
if (!ok || index != m_blockIndex) {
|
||||
m_error = true;
|
||||
setErrorString("Invalid block index.");
|
||||
@ -144,7 +144,7 @@ bool HashedBlockStream::readHashedBlock()
|
||||
return false;
|
||||
}
|
||||
|
||||
m_blockSize = Endian::readInt32(m_baseDevice, ByteOrder, &ok);
|
||||
m_blockSize = Endian::readSizedInt<qint32>(m_baseDevice, ByteOrder, &ok);
|
||||
if (!ok || m_blockSize < 0) {
|
||||
m_error = true;
|
||||
setErrorString("Invalid block size.");
|
||||
@ -217,7 +217,7 @@ qint64 HashedBlockStream::writeData(const char* data, qint64 maxSize)
|
||||
|
||||
bool HashedBlockStream::writeHashedBlock()
|
||||
{
|
||||
if (!Endian::writeInt32(m_blockIndex, m_baseDevice, ByteOrder)) {
|
||||
if (!Endian::writeSizedInt<qint32>(m_blockIndex, m_baseDevice, ByteOrder)) {
|
||||
m_error = true;
|
||||
setErrorString(m_baseDevice->errorString());
|
||||
return false;
|
||||
@ -238,7 +238,7 @@ bool HashedBlockStream::writeHashedBlock()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Endian::writeInt32(m_buffer.size(), m_baseDevice, ByteOrder)) {
|
||||
if (!Endian::writeSizedInt<qint32>(m_buffer.size(), m_baseDevice, ByteOrder)) {
|
||||
m_error = true;
|
||||
setErrorString(m_baseDevice->errorString());
|
||||
return false;
|
||||
@ -256,3 +256,7 @@ bool HashedBlockStream::writeHashedBlock()
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HashedBlockStream::atEnd() const {
|
||||
return m_eof;
|
||||
}
|
||||
|
@ -34,6 +34,8 @@ public:
|
||||
bool reset() override;
|
||||
void close() override;
|
||||
|
||||
bool atEnd() const override;
|
||||
|
||||
protected:
|
||||
qint64 readData(char* data, qint64 maxSize) override;
|
||||
qint64 writeData(const char* data, qint64 maxSize) override;
|
||||
|
261
src/streams/HmacBlockStream.cpp
Normal file
261
src/streams/HmacBlockStream.cpp
Normal file
@ -0,0 +1,261 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "HmacBlockStream.h"
|
||||
|
||||
#include "core/Endian.h"
|
||||
#include "crypto/CryptoHash.h"
|
||||
|
||||
const QSysInfo::Endian HmacBlockStream::ByteOrder = QSysInfo::LittleEndian;
|
||||
|
||||
HmacBlockStream::HmacBlockStream(QIODevice* baseDevice, QByteArray key)
|
||||
: LayeredStream(baseDevice)
|
||||
, m_blockSize(1024 * 1024)
|
||||
, m_key(key)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
HmacBlockStream::HmacBlockStream(QIODevice* baseDevice, QByteArray key, qint32 blockSize)
|
||||
: LayeredStream(baseDevice)
|
||||
, m_blockSize(blockSize)
|
||||
, m_key(key)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
HmacBlockStream::~HmacBlockStream()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
void HmacBlockStream::init()
|
||||
{
|
||||
m_buffer.clear();
|
||||
m_bufferPos = 0;
|
||||
m_blockIndex = 0;
|
||||
m_eof = false;
|
||||
m_error = false;
|
||||
}
|
||||
|
||||
bool HmacBlockStream::reset()
|
||||
{
|
||||
// Write final block(s) only if device is writable and we haven't
|
||||
// already written a final block.
|
||||
if (isWritable() && (!m_buffer.isEmpty() || m_blockIndex != 0)) {
|
||||
if (!m_buffer.isEmpty() && !writeHashedBlock()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// write empty final block
|
||||
if (!writeHashedBlock()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void HmacBlockStream::close()
|
||||
{
|
||||
// Write final block(s) only if device is writable and we haven't
|
||||
// already written a final block.
|
||||
if (isWritable() && (!m_buffer.isEmpty() || m_blockIndex != 0)) {
|
||||
if (!m_buffer.isEmpty()) {
|
||||
writeHashedBlock();
|
||||
}
|
||||
|
||||
// write empty final block
|
||||
writeHashedBlock();
|
||||
}
|
||||
|
||||
LayeredStream::close();
|
||||
}
|
||||
|
||||
qint64 HmacBlockStream::readData(char* data, qint64 maxSize)
|
||||
{
|
||||
if (m_error) {
|
||||
return -1;
|
||||
} else if (m_eof) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
qint64 bytesRemaining = maxSize;
|
||||
qint64 offset = 0;
|
||||
|
||||
while (bytesRemaining > 0) {
|
||||
if (m_bufferPos == m_buffer.size()) {
|
||||
if (!readHashedBlock()) {
|
||||
if (m_error) {
|
||||
return -1;
|
||||
}
|
||||
return maxSize - bytesRemaining;
|
||||
}
|
||||
}
|
||||
|
||||
qint64 bytesToCopy = qMin(bytesRemaining, static_cast<qint64>(m_buffer.size() - m_bufferPos));
|
||||
|
||||
memcpy(data + offset, m_buffer.constData() + m_bufferPos, static_cast<size_t>(bytesToCopy));
|
||||
|
||||
offset += bytesToCopy;
|
||||
m_bufferPos += bytesToCopy;
|
||||
bytesRemaining -= bytesToCopy;
|
||||
}
|
||||
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
bool HmacBlockStream::readHashedBlock()
|
||||
{
|
||||
if (m_eof) {
|
||||
return false;
|
||||
}
|
||||
QByteArray hmac = m_baseDevice->read(32);
|
||||
if (hmac.size() != 32) {
|
||||
m_error = true;
|
||||
setErrorString("Invalid HMAC size.");
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray blockSizeBytes = m_baseDevice->read(4);
|
||||
if (blockSizeBytes.size() != 4) {
|
||||
m_error = true;
|
||||
setErrorString("Invalid block size size.");
|
||||
return false;
|
||||
}
|
||||
auto blockSize = Endian::bytesToSizedInt<qint32>(blockSizeBytes, ByteOrder);
|
||||
if (blockSize < 0) {
|
||||
m_error = true;
|
||||
setErrorString("Invalid block size.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_buffer = m_baseDevice->read(blockSize);
|
||||
if (m_buffer.size() != blockSize) {
|
||||
m_error = true;
|
||||
setErrorString("Block too short.");
|
||||
return false;
|
||||
}
|
||||
|
||||
CryptoHash hasher(CryptoHash::Sha256, true);
|
||||
hasher.setKey(getCurrentHmacKey());
|
||||
hasher.addData(Endian::sizedIntToBytes<quint64>(m_blockIndex, ByteOrder));
|
||||
hasher.addData(blockSizeBytes);
|
||||
hasher.addData(m_buffer);
|
||||
|
||||
if (hmac != hasher.result()) {
|
||||
m_error = true;
|
||||
setErrorString("Mismatch between hash and data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_bufferPos = 0;
|
||||
++m_blockIndex;
|
||||
|
||||
if (blockSize == 0) {
|
||||
m_eof = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
qint64 HmacBlockStream::writeData(const char* data, qint64 maxSize)
|
||||
{
|
||||
Q_ASSERT(maxSize >= 0);
|
||||
|
||||
if (m_error) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
qint64 bytesRemaining = maxSize;
|
||||
qint64 offset = 0;
|
||||
|
||||
while (bytesRemaining > 0) {
|
||||
qint64 bytesToCopy = qMin(bytesRemaining, static_cast<qint64>(m_blockSize - m_buffer.size()));
|
||||
|
||||
m_buffer.append(data + offset, static_cast<int>(bytesToCopy));
|
||||
|
||||
offset += bytesToCopy;
|
||||
bytesRemaining -= bytesToCopy;
|
||||
|
||||
if (m_buffer.size() == m_blockSize && !writeHashedBlock()) {
|
||||
if (m_error) {
|
||||
return -1;
|
||||
}
|
||||
return maxSize - bytesRemaining;
|
||||
}
|
||||
}
|
||||
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
bool HmacBlockStream::writeHashedBlock()
|
||||
{
|
||||
CryptoHash hasher(CryptoHash::Sha256, true);
|
||||
hasher.setKey(getCurrentHmacKey());
|
||||
hasher.addData(Endian::sizedIntToBytes<quint64>(m_blockIndex, ByteOrder));
|
||||
hasher.addData(Endian::sizedIntToBytes<qint32>(m_buffer.size(), ByteOrder));
|
||||
hasher.addData(m_buffer);
|
||||
QByteArray hash = hasher.result();
|
||||
|
||||
if (m_baseDevice->write(hash) != hash.size()) {
|
||||
m_error = true;
|
||||
setErrorString(m_baseDevice->errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Endian::writeSizedInt<qint32>(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;
|
||||
}
|
||||
|
||||
m_buffer.clear();
|
||||
}
|
||||
++m_blockIndex;
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray HmacBlockStream::getCurrentHmacKey() const
|
||||
{
|
||||
return getHmacKey(m_blockIndex, m_key);
|
||||
}
|
||||
|
||||
QByteArray HmacBlockStream::getHmacKey(quint64 blockIndex, QByteArray key)
|
||||
{
|
||||
Q_ASSERT(key.size() == 64);
|
||||
QByteArray indexBytes = Endian::sizedIntToBytes<quint64>(blockIndex, ByteOrder);
|
||||
CryptoHash hasher(CryptoHash::Sha512);
|
||||
hasher.addData(indexBytes);
|
||||
hasher.addData(key);
|
||||
return hasher.result();
|
||||
}
|
||||
|
||||
bool HmacBlockStream::atEnd() const
|
||||
{
|
||||
return m_eof;
|
||||
}
|
61
src/streams/HmacBlockStream.h
Normal file
61
src/streams/HmacBlockStream.h
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_HMACBLOCKSTREAM_H
|
||||
#define KEEPASSX_HMACBLOCKSTREAM_H
|
||||
|
||||
#include <QSysInfo>
|
||||
|
||||
#include "streams/LayeredStream.h"
|
||||
|
||||
class HmacBlockStream: public LayeredStream
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit HmacBlockStream(QIODevice* baseDevice, QByteArray key);
|
||||
HmacBlockStream(QIODevice* baseDevice, QByteArray key, qint32 blockSize);
|
||||
~HmacBlockStream();
|
||||
|
||||
bool reset() override;
|
||||
void close() override;
|
||||
|
||||
static QByteArray getHmacKey(quint64 blockIndex, QByteArray key);
|
||||
|
||||
bool atEnd() const override;
|
||||
|
||||
protected:
|
||||
qint64 readData(char* data, qint64 maxSize) override;
|
||||
qint64 writeData(const char* data, qint64 maxSize) override;
|
||||
|
||||
private:
|
||||
void init();
|
||||
bool readHashedBlock();
|
||||
bool writeHashedBlock();
|
||||
QByteArray getCurrentHmacKey() const;
|
||||
|
||||
static const QSysInfo::Endian ByteOrder;
|
||||
qint32 m_blockSize;
|
||||
QByteArray m_buffer;
|
||||
QByteArray m_key;
|
||||
int m_bufferPos;
|
||||
quint64 m_blockIndex;
|
||||
bool m_eof;
|
||||
bool m_error;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_HMACBLOCKSTREAM_H
|
@ -24,7 +24,7 @@ SymmetricCipherStream::SymmetricCipherStream(QIODevice* baseDevice, SymmetricCip
|
||||
, m_bufferPos(0)
|
||||
, m_bufferFilling(false)
|
||||
, m_error(false)
|
||||
, m_isInitalized(false)
|
||||
, m_isInitialized(false)
|
||||
, m_dataWritten(false)
|
||||
{
|
||||
}
|
||||
@ -36,12 +36,12 @@ SymmetricCipherStream::~SymmetricCipherStream()
|
||||
|
||||
bool SymmetricCipherStream::init(const QByteArray& key, const QByteArray& iv)
|
||||
{
|
||||
m_isInitalized = m_cipher->init(key, iv);
|
||||
if (!m_isInitalized) {
|
||||
m_isInitialized = m_cipher->init(key, iv);
|
||||
if (!m_isInitialized) {
|
||||
setErrorString(m_cipher->errorString());
|
||||
}
|
||||
|
||||
return m_isInitalized;
|
||||
m_streamCipher = m_cipher->blockSize() == 1;
|
||||
return m_isInitialized;
|
||||
}
|
||||
|
||||
void SymmetricCipherStream::resetInternalState()
|
||||
@ -56,11 +56,8 @@ void SymmetricCipherStream::resetInternalState()
|
||||
|
||||
bool SymmetricCipherStream::open(QIODevice::OpenMode mode)
|
||||
{
|
||||
if (!m_isInitalized) {
|
||||
return false;
|
||||
}
|
||||
return m_isInitialized && LayeredStream::open(mode);
|
||||
|
||||
return LayeredStream::open(mode);
|
||||
}
|
||||
|
||||
bool SymmetricCipherStream::reset()
|
||||
@ -127,11 +124,11 @@ bool SymmetricCipherStream::readBlock()
|
||||
QByteArray newData;
|
||||
|
||||
if (m_bufferFilling) {
|
||||
newData.resize(m_cipher->blockSize() - m_buffer.size());
|
||||
newData.resize(blockSize() - m_buffer.size());
|
||||
}
|
||||
else {
|
||||
m_buffer.clear();
|
||||
newData.resize(m_cipher->blockSize());
|
||||
newData.resize(blockSize());
|
||||
}
|
||||
|
||||
int readResult = m_baseDevice->read(newData.data(), newData.size());
|
||||
@ -140,12 +137,11 @@ bool SymmetricCipherStream::readBlock()
|
||||
m_error = true;
|
||||
setErrorString(m_baseDevice->errorString());
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
m_buffer.append(newData.left(readResult));
|
||||
}
|
||||
|
||||
if (m_buffer.size() != m_cipher->blockSize()) {
|
||||
if (!m_streamCipher && m_buffer.size() != blockSize()) {
|
||||
m_bufferFilling = true;
|
||||
return false;
|
||||
}
|
||||
@ -159,26 +155,28 @@ bool SymmetricCipherStream::readBlock()
|
||||
m_bufferFilling = false;
|
||||
|
||||
if (m_baseDevice->atEnd()) {
|
||||
// PKCS7 padding
|
||||
quint8 padLength = m_buffer.at(m_buffer.size() - 1);
|
||||
if (!m_streamCipher) {
|
||||
// PKCS7 padding
|
||||
quint8 padLength = m_buffer.at(m_buffer.size() - 1);
|
||||
|
||||
if (padLength == m_cipher->blockSize()) {
|
||||
Q_ASSERT(m_buffer == QByteArray(m_cipher->blockSize(), m_cipher->blockSize()));
|
||||
// full block with just padding: discard
|
||||
m_buffer.clear();
|
||||
return false;
|
||||
}
|
||||
else if (padLength > m_cipher->blockSize()) {
|
||||
// invalid padding
|
||||
m_error = true;
|
||||
setErrorString("Invalid padding.");
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
Q_ASSERT(m_buffer.right(padLength) == QByteArray(padLength, padLength));
|
||||
// resize buffer to strip padding
|
||||
m_buffer.resize(m_cipher->blockSize() - padLength);
|
||||
return true;
|
||||
if (padLength == blockSize()) {
|
||||
Q_ASSERT(m_buffer == QByteArray(blockSize(), blockSize()));
|
||||
// full block with just padding: discard
|
||||
m_buffer.clear();
|
||||
return false;
|
||||
} else if (padLength > blockSize()) {
|
||||
// invalid padding
|
||||
m_error = true;
|
||||
setErrorString("Invalid padding.");
|
||||
return false;
|
||||
} else {
|
||||
Q_ASSERT(m_buffer.right(padLength) == QByteArray(padLength, padLength));
|
||||
// resize buffer to strip padding
|
||||
m_buffer.resize(blockSize() - padLength);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return m_buffer.size() > 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -200,14 +198,14 @@ qint64 SymmetricCipherStream::writeData(const char* data, qint64 maxSize)
|
||||
qint64 offset = 0;
|
||||
|
||||
while (bytesRemaining > 0) {
|
||||
int bytesToCopy = qMin(bytesRemaining, static_cast<qint64>(m_cipher->blockSize() - m_buffer.size()));
|
||||
int bytesToCopy = qMin(bytesRemaining, static_cast<qint64>(blockSize() - m_buffer.size()));
|
||||
|
||||
m_buffer.append(data + offset, bytesToCopy);
|
||||
|
||||
offset += bytesToCopy;
|
||||
bytesRemaining -= bytesToCopy;
|
||||
|
||||
if (m_buffer.size() == m_cipher->blockSize()) {
|
||||
if (m_buffer.size() == blockSize()) {
|
||||
if (!writeBlock(false)) {
|
||||
if (m_error) {
|
||||
return -1;
|
||||
@ -224,11 +222,11 @@ qint64 SymmetricCipherStream::writeData(const char* data, qint64 maxSize)
|
||||
|
||||
bool SymmetricCipherStream::writeBlock(bool lastBlock)
|
||||
{
|
||||
Q_ASSERT(lastBlock || (m_buffer.size() == m_cipher->blockSize()));
|
||||
Q_ASSERT(m_streamCipher || lastBlock || (m_buffer.size() == blockSize()));
|
||||
|
||||
if (lastBlock) {
|
||||
if (lastBlock && !m_streamCipher) {
|
||||
// PKCS7 padding
|
||||
int padLen = m_cipher->blockSize() - m_buffer.size();
|
||||
int padLen = blockSize() - m_buffer.size();
|
||||
for (int i = 0; i < padLen; i++) {
|
||||
m_buffer.append(static_cast<char>(padLen));
|
||||
}
|
||||
@ -250,3 +248,10 @@ bool SymmetricCipherStream::writeBlock(bool lastBlock)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int SymmetricCipherStream::blockSize() const {
|
||||
if (m_streamCipher) {
|
||||
return 1024;
|
||||
}
|
||||
return m_cipher->blockSize();
|
||||
}
|
||||
|
@ -45,14 +45,16 @@ private:
|
||||
void resetInternalState();
|
||||
bool readBlock();
|
||||
bool writeBlock(bool lastBlock);
|
||||
int blockSize() const;
|
||||
|
||||
const QScopedPointer<SymmetricCipher> m_cipher;
|
||||
QByteArray m_buffer;
|
||||
int m_bufferPos;
|
||||
bool m_bufferFilling;
|
||||
bool m_error;
|
||||
bool m_isInitalized;
|
||||
bool m_isInitialized;
|
||||
bool m_dataWritten;
|
||||
bool m_streamCipher;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_SYMMETRICCIPHERSTREAM_H
|
||||
|
@ -107,7 +107,10 @@ endif()
|
||||
add_unit_test(NAME testgroup SOURCES TestGroup.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testkeepass2xmlreader SOURCES TestKeePass2XmlReader.cpp
|
||||
add_unit_test(NAME testkdbx3xmlreader SOURCES TestKeePass2XmlReader.cpp TestKdbx3XmlReader.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testkdbx4xmlreader SOURCES TestKeePass2XmlReader.cpp TestKdbx4XmlReader.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testkeys SOURCES TestKeys.cpp
|
||||
@ -181,9 +184,6 @@ add_unit_test(NAME testrandom SOURCES TestRandom.cpp
|
||||
add_unit_test(NAME testentrysearcher SOURCES TestEntrySearcher.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testexporter SOURCES TestExporter.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
add_unit_test(NAME testcsvexporter SOURCES TestCsvExporter.cpp
|
||||
LIBS ${TEST_LIBRARIES})
|
||||
|
||||
|
@ -44,4 +44,17 @@ void TestCryptoHash::test()
|
||||
cryptoHash3.addData(QString("ssX").toLatin1());
|
||||
QCOMPARE(cryptoHash3.result(),
|
||||
QByteArray::fromHex("0b56e5f65263e747af4a833bd7dd7ad26a64d7a4de7c68e52364893dca0766b4"));
|
||||
|
||||
CryptoHash cryptoHash2(CryptoHash::Sha512);
|
||||
QCOMPARE(cryptoHash2.result(),
|
||||
QByteArray::fromHex("cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"));
|
||||
|
||||
QByteArray result3 = CryptoHash::hash(source2, CryptoHash::Sha512);
|
||||
QCOMPARE(result3, QByteArray::fromHex("0d41b612584ed39ff72944c29494573e40f4bb95283455fae2e0be1e3565aa9f48057d59e6ffd777970e282871c25a549a2763e5b724794f312c97021c42f91d"));
|
||||
|
||||
CryptoHash cryptoHash4(CryptoHash::Sha512);
|
||||
cryptoHash4.addData(QString("KeePa").toLatin1());
|
||||
cryptoHash4.addData(QString("ssX").toLatin1());
|
||||
QCOMPARE(cryptoHash4.result(),
|
||||
QByteArray::fromHex("0d41b612584ed39ff72944c29494573e40f4bb95283455fae2e0be1e3565aa9f48057d59e6ffd777970e282871c25a549a2763e5b724794f312c97021c42f91d"));
|
||||
}
|
||||
|
@ -22,7 +22,8 @@
|
||||
#include "core/Database.h"
|
||||
#include "core/Group.h"
|
||||
#include "crypto/Crypto.h"
|
||||
#include "format/KeePass2XmlReader.h"
|
||||
#include "format/KeePass2.h"
|
||||
#include "format/KdbxXmlReader.h"
|
||||
#include "config-keepassx-tests.h"
|
||||
|
||||
QTEST_GUILESS_MAIN(TestDeletedObjects)
|
||||
@ -88,7 +89,7 @@ void TestDeletedObjects::createAndDelete(Database* db, int delObjectsSize)
|
||||
|
||||
void TestDeletedObjects::testDeletedObjectsFromFile()
|
||||
{
|
||||
KeePass2XmlReader reader;
|
||||
KdbxXmlReader reader(KeePass2::FILE_VERSION_3);
|
||||
reader.setStrictMode(true);
|
||||
QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml");
|
||||
Database* db = reader.readDatabase(xmlFile);
|
||||
|
22
tests/TestKdbx3XmlReader.cpp
Normal file
22
tests/TestKdbx3XmlReader.cpp
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QTest>
|
||||
|
||||
#include "TestKeePass2XmlReader.h"
|
||||
|
||||
QTEST_GUILESS_MAIN(TestKdbx3XmlReader)
|
22
tests/TestKdbx4XmlReader.cpp
Normal file
22
tests/TestKdbx4XmlReader.cpp
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <QTest>
|
||||
|
||||
#include "TestKeePass2XmlReader.h"
|
||||
|
||||
QTEST_GUILESS_MAIN(TestKdbx4XmlReader)
|
@ -110,7 +110,7 @@ void TestKeePass1Reader::testBasic()
|
||||
void TestKeePass1Reader::testMasterKey()
|
||||
{
|
||||
QVERIFY(m_db->hasKey());
|
||||
QCOMPARE(m_db->transformRounds(), static_cast<quint64>(713));
|
||||
QCOMPARE(m_db->kdf()->rounds(), 713);
|
||||
}
|
||||
|
||||
void TestKeePass1Reader::testCustomIcons()
|
||||
|
@ -58,7 +58,7 @@ void TestKeePass2RandomStream::test()
|
||||
}
|
||||
|
||||
|
||||
KeePass2RandomStream randomStream;
|
||||
KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::Salsa20);
|
||||
bool ok;
|
||||
QVERIFY(randomStream.init(key));
|
||||
QByteArray randomStreamData;
|
||||
|
@ -155,3 +155,26 @@ void TestKeePass2Reader::testFormat300()
|
||||
|
||||
delete db;
|
||||
}
|
||||
|
||||
void TestKeePass2Reader::testFormat400()
|
||||
{
|
||||
QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/Format400.kdbx");
|
||||
CompositeKey key;
|
||||
key.addKey(PasswordKey("t"));
|
||||
KeePass2Reader reader;
|
||||
Database* db = reader.readDatabase(filename, key);
|
||||
QVERIFY(db);
|
||||
QVERIFY(!reader.hasError());
|
||||
|
||||
QCOMPARE(db->rootGroup()->name(), QString("Format400"));
|
||||
QCOMPARE(db->metadata()->name(), QString("Format400"));
|
||||
QCOMPARE(db->rootGroup()->entries().size(), 1);
|
||||
Entry* entry = db->rootGroup()->entries().at(0);
|
||||
|
||||
QCOMPARE(entry->title(), QString("Format400"));
|
||||
QCOMPARE(entry->username(), QString("Format400"));
|
||||
QCOMPARE(entry->attributes()->keys().size(), 6);
|
||||
QCOMPARE(entry->attributes()->value("Format400"), QString("Format400"));
|
||||
QCOMPARE(entry->attachments()->keys().size(), 1);
|
||||
QCOMPARE(entry->attachments()->value("Format400"), QByteArray("Format400\n"));
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ private slots:
|
||||
void testBrokenHeaderHash();
|
||||
void testFormat200();
|
||||
void testFormat300();
|
||||
void testFormat400();
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_TESTKEEPASS2READER_H
|
||||
|
@ -30,7 +30,6 @@
|
||||
#include "format/KeePass2Reader.h"
|
||||
#include "format/KeePass2Repair.h"
|
||||
#include "format/KeePass2Writer.h"
|
||||
#include "format/KeePass2XmlWriter.h"
|
||||
#include "keys/PasswordKey.h"
|
||||
|
||||
QTEST_GUILESS_MAIN(TestKeePass2Writer)
|
||||
@ -66,12 +65,15 @@ void TestKeePass2Writer::initTestCase()
|
||||
buffer.open(QBuffer::ReadWrite);
|
||||
|
||||
KeePass2Writer writer;
|
||||
writer.writeDatabase(&buffer, m_dbOrg);
|
||||
bool writeSuccess = writer.writeDatabase(&buffer, m_dbOrg);
|
||||
QVERIFY(writeSuccess);
|
||||
QVERIFY(!writer.hasError());
|
||||
buffer.seek(0);
|
||||
KeePass2Reader reader;
|
||||
m_dbTest = reader.readDatabase(&buffer, key);
|
||||
QVERIFY(!reader.hasError());
|
||||
if (reader.hasError()) {
|
||||
QFAIL(reader.errorString().toUtf8().constData());
|
||||
}
|
||||
QVERIFY(m_dbTest);
|
||||
}
|
||||
|
||||
|
@ -20,17 +20,16 @@
|
||||
#include <QBuffer>
|
||||
#include <QFile>
|
||||
#include <QTest>
|
||||
#include <format/KeePass2.h>
|
||||
|
||||
#include "core/Database.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "crypto/Crypto.h"
|
||||
#include "format/KeePass2XmlReader.h"
|
||||
#include "format/KeePass2XmlWriter.h"
|
||||
#include "format/KdbxXmlReader.h"
|
||||
#include "format/KdbxXmlWriter.h"
|
||||
#include "config-keepassx-tests.h"
|
||||
|
||||
QTEST_GUILESS_MAIN(TestKeePass2XmlReader)
|
||||
|
||||
namespace QTest {
|
||||
template<>
|
||||
char* toString(const Uuid& uuid)
|
||||
@ -79,11 +78,11 @@ QByteArray TestKeePass2XmlReader::strToBytes(const QString& str)
|
||||
return result;
|
||||
}
|
||||
|
||||
void TestKeePass2XmlReader::initTestCase()
|
||||
void TestKdbx3XmlReader::initTestCase()
|
||||
{
|
||||
QVERIFY(Crypto::init());
|
||||
|
||||
KeePass2XmlReader reader;
|
||||
KdbxXmlReader reader(KeePass2::FILE_VERSION_3);
|
||||
reader.setStrictMode(true);
|
||||
QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml");
|
||||
m_db = reader.readDatabase(xmlFile);
|
||||
@ -91,6 +90,70 @@ void TestKeePass2XmlReader::initTestCase()
|
||||
QVERIFY(!reader.hasError());
|
||||
}
|
||||
|
||||
void TestKdbx4XmlReader::initTestCase()
|
||||
{
|
||||
QVERIFY(Crypto::init());
|
||||
|
||||
KdbxXmlReader reader(KeePass2::FILE_VERSION_3);
|
||||
reader.setStrictMode(true);
|
||||
QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml");
|
||||
m_db = reader.readDatabase(xmlFile);
|
||||
QVERIFY(m_db);
|
||||
QVERIFY(!reader.hasError());
|
||||
}
|
||||
|
||||
void TestKdbx3XmlReader::readDatabase(QString path, bool strictMode, Database*& db, bool& hasError, QString& errorString)
|
||||
{
|
||||
KdbxXmlReader reader(KeePass2::FILE_VERSION_3);
|
||||
reader.setStrictMode(strictMode);
|
||||
db = reader.readDatabase(path);
|
||||
hasError = reader.hasError();
|
||||
errorString = reader.errorString();
|
||||
}
|
||||
|
||||
void TestKdbx3XmlReader::readDatabase(QBuffer* buf, bool strictMode, Database*& db, bool& hasError, QString& errorString)
|
||||
{
|
||||
KdbxXmlReader reader(KeePass2::FILE_VERSION_3);
|
||||
reader.setStrictMode(strictMode);
|
||||
db = reader.readDatabase(buf);
|
||||
hasError = reader.hasError();
|
||||
errorString = reader.errorString();
|
||||
}
|
||||
|
||||
void TestKdbx3XmlReader::writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString)
|
||||
{
|
||||
KdbxXmlWriter writer(KeePass2::FILE_VERSION_3);
|
||||
writer.writeDatabase(buf, db);
|
||||
hasError = writer.hasError();
|
||||
errorString = writer.errorString();
|
||||
}
|
||||
|
||||
void TestKdbx4XmlReader::readDatabase(QString path, bool strictMode, Database*& db, bool& hasError, QString& errorString)
|
||||
{
|
||||
KdbxXmlReader reader(KeePass2::FILE_VERSION_3);
|
||||
reader.setStrictMode(strictMode);
|
||||
db = reader.readDatabase(path);
|
||||
hasError = reader.hasError();
|
||||
errorString = reader.errorString();
|
||||
}
|
||||
|
||||
void TestKdbx4XmlReader::readDatabase(QBuffer* buf, bool strictMode, Database*& db, bool& hasError, QString& errorString)
|
||||
{
|
||||
KdbxXmlReader reader(KeePass2::FILE_VERSION_3);
|
||||
reader.setStrictMode(strictMode);
|
||||
db = reader.readDatabase(buf);
|
||||
hasError = reader.hasError();
|
||||
errorString = reader.errorString();
|
||||
}
|
||||
|
||||
void TestKdbx4XmlReader::writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString)
|
||||
{
|
||||
KdbxXmlWriter writer(KeePass2::FILE_VERSION_3);
|
||||
writer.writeDatabase(buf, db);
|
||||
hasError = writer.hasError();
|
||||
errorString = writer.errorString();
|
||||
}
|
||||
|
||||
void TestKeePass2XmlReader::testMetadata()
|
||||
{
|
||||
QCOMPARE(m_db->metadata()->generator(), QString("KeePass"));
|
||||
@ -374,15 +437,20 @@ void TestKeePass2XmlReader::testBroken()
|
||||
QFETCH(bool, strictMode);
|
||||
QFETCH(bool, expectError);
|
||||
|
||||
KeePass2XmlReader reader;
|
||||
reader.setStrictMode(strictMode);
|
||||
|
||||
QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, baseName);
|
||||
QVERIFY(QFile::exists(xmlFile));
|
||||
QScopedPointer<Database> db(reader.readDatabase(xmlFile));
|
||||
if (reader.hasError()) {
|
||||
qWarning("Reader error: %s", qPrintable(reader.errorString()));
|
||||
bool hasError;
|
||||
QString errorString;
|
||||
Database* db;
|
||||
readDatabase(xmlFile, strictMode, db, hasError, errorString);
|
||||
if (hasError) {
|
||||
qWarning("Reader error: %s", qPrintable(errorString));
|
||||
}
|
||||
QCOMPARE(hasError, expectError);
|
||||
if (db) {
|
||||
delete db;
|
||||
}
|
||||
QCOMPARE(reader.hasError(), expectError);
|
||||
}
|
||||
|
||||
void TestKeePass2XmlReader::testBroken_data()
|
||||
@ -412,15 +480,20 @@ void TestKeePass2XmlReader::testBroken_data()
|
||||
|
||||
void TestKeePass2XmlReader::testEmptyUuids()
|
||||
{
|
||||
KeePass2XmlReader reader;
|
||||
reader.setStrictMode(true);
|
||||
|
||||
QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, "EmptyUuids");
|
||||
QVERIFY(QFile::exists(xmlFile));
|
||||
QScopedPointer<Database> db(reader.readDatabase(xmlFile));
|
||||
if (reader.hasError()) {
|
||||
qWarning("Reader error: %s", qPrintable(reader.errorString()));
|
||||
Database* dbp;
|
||||
bool hasError;
|
||||
QString errorString;
|
||||
readDatabase(xmlFile, true, dbp, hasError, errorString);
|
||||
if (hasError) {
|
||||
qWarning("Reader error: %s", qPrintable(errorString));
|
||||
}
|
||||
QVERIFY(!hasError);
|
||||
if (dbp) {
|
||||
delete dbp;
|
||||
}
|
||||
QVERIFY(!reader.hasError());
|
||||
}
|
||||
|
||||
void TestKeePass2XmlReader::testInvalidXmlChars()
|
||||
@ -459,19 +532,19 @@ void TestKeePass2XmlReader::testInvalidXmlChars()
|
||||
|
||||
QBuffer buffer;
|
||||
buffer.open(QIODevice::ReadWrite);
|
||||
KeePass2XmlWriter writer;
|
||||
writer.writeDatabase(&buffer, dbWrite.data());
|
||||
QVERIFY(!writer.hasError());
|
||||
bool hasError;
|
||||
QString errorString;
|
||||
writeDatabase(&buffer, dbWrite.data(), hasError, errorString);
|
||||
QVERIFY(!hasError);
|
||||
buffer.seek(0);
|
||||
|
||||
KeePass2XmlReader reader;
|
||||
reader.setStrictMode(true);
|
||||
QScopedPointer<Database> dbRead(reader.readDatabase(&buffer));
|
||||
if (reader.hasError()) {
|
||||
qWarning("Database read error: %s", qPrintable(reader.errorString()));
|
||||
Database* dbRead;
|
||||
readDatabase(&buffer, true, dbRead, hasError, errorString);
|
||||
if (hasError) {
|
||||
qWarning("Database read error: %s", qPrintable(errorString));
|
||||
}
|
||||
QVERIFY(!reader.hasError());
|
||||
QVERIFY(!dbRead.isNull());
|
||||
QVERIFY(!hasError);
|
||||
QVERIFY(dbRead);
|
||||
QCOMPARE(dbRead->rootGroup()->entries().size(), 1);
|
||||
Entry* entryRead = dbRead->rootGroup()->entries().at(0);
|
||||
EntryAttributes* attrRead = entryRead->attributes();
|
||||
@ -486,22 +559,28 @@ void TestKeePass2XmlReader::testInvalidXmlChars()
|
||||
QCOMPARE(strToBytes(attrRead->value("LowLowSurrogate")), QByteArray());
|
||||
QCOMPARE(strToBytes(attrRead->value("SurrogateValid1")), strToBytes(strSurrogateValid1));
|
||||
QCOMPARE(strToBytes(attrRead->value("SurrogateValid2")), strToBytes(strSurrogateValid2));
|
||||
|
||||
if (dbRead) {
|
||||
delete dbRead;
|
||||
}
|
||||
}
|
||||
|
||||
void TestKeePass2XmlReader::testRepairUuidHistoryItem()
|
||||
{
|
||||
KeePass2XmlReader reader;
|
||||
QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, "BrokenDifferentEntryHistoryUuid");
|
||||
QVERIFY(QFile::exists(xmlFile));
|
||||
QScopedPointer<Database> db(reader.readDatabase(xmlFile));
|
||||
if (reader.hasError()) {
|
||||
qWarning("Database read error: %s", qPrintable(reader.errorString()));
|
||||
Database* db;
|
||||
bool hasError;
|
||||
QString errorString;
|
||||
readDatabase(xmlFile, true, db, hasError, errorString);
|
||||
if (hasError) {
|
||||
qWarning("Database read error: %s", qPrintable(errorString));
|
||||
}
|
||||
QVERIFY(!reader.hasError());
|
||||
QVERIFY(!hasError);
|
||||
|
||||
|
||||
|
||||
QList<Entry*> entries = db.data()->rootGroup()->entries();
|
||||
QList<Entry*> entries = db->rootGroup()->entries();
|
||||
QCOMPARE(entries.size(), 1);
|
||||
Entry* entry = entries.at(0);
|
||||
|
||||
@ -512,6 +591,10 @@ void TestKeePass2XmlReader::testRepairUuidHistoryItem()
|
||||
QVERIFY(!entry->uuid().isNull());
|
||||
QVERIFY(!historyItem->uuid().isNull());
|
||||
QCOMPARE(historyItem->uuid(), entry->uuid());
|
||||
|
||||
if (db) {
|
||||
delete db;
|
||||
}
|
||||
}
|
||||
|
||||
void TestKeePass2XmlReader::cleanupTestCase()
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
#include <QBuffer>
|
||||
|
||||
class Database;
|
||||
|
||||
@ -27,8 +28,8 @@ class TestKeePass2XmlReader : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
protected slots:
|
||||
virtual void initTestCase() = 0;
|
||||
void testMetadata();
|
||||
void testCustomIcons();
|
||||
void testCustomData();
|
||||
@ -46,11 +47,40 @@ private slots:
|
||||
void testRepairUuidHistoryItem();
|
||||
void cleanupTestCase();
|
||||
|
||||
private:
|
||||
protected:
|
||||
virtual void readDatabase(QBuffer* buf, bool strictMode, Database*& db, bool& hasError, QString& errorString) = 0;
|
||||
virtual void readDatabase(QString path, bool strictMode, Database*& db, bool& hasError, QString& errorString) = 0;
|
||||
virtual void writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString) = 0;
|
||||
static QDateTime genDT(int year, int month, int day, int hour, int min, int second);
|
||||
static QByteArray strToBytes(const QString& str);
|
||||
|
||||
Database* m_db;
|
||||
};
|
||||
|
||||
class TestKdbx3XmlReader : public TestKeePass2XmlReader
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
virtual void initTestCase() override;
|
||||
|
||||
protected:
|
||||
virtual void readDatabase(QBuffer* buf, bool strictMode, Database*& db, bool& hasError, QString& errorString) override;
|
||||
virtual void readDatabase(QString path, bool strictMode, Database*& db, bool& hasError, QString& errorString) override;
|
||||
virtual void writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString) override;
|
||||
};
|
||||
|
||||
class TestKdbx4XmlReader : public TestKeePass2XmlReader
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
virtual void initTestCase() override;
|
||||
|
||||
protected:
|
||||
virtual void readDatabase(QBuffer* buf, bool strictMode, Database*& db, bool& hasError, QString& errorString) override;
|
||||
virtual void readDatabase(QString path, bool strictMode, Database*& db, bool& hasError, QString& errorString) override;
|
||||
virtual void writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString) override;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_TESTKEEPASS2XMLREADER_H
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "core/Metadata.h"
|
||||
#include "core/Tools.h"
|
||||
#include "crypto/Crypto.h"
|
||||
#include "crypto/kdf/AesKdf.h"
|
||||
#include "crypto/CryptoHash.h"
|
||||
#include "format/KeePass2Reader.h"
|
||||
#include "format/KeePass2Writer.h"
|
||||
@ -45,21 +46,23 @@ void TestKeys::testComposite()
|
||||
QScopedPointer<CompositeKey> compositeKey1(new CompositeKey());
|
||||
QScopedPointer<PasswordKey> passwordKey1(new PasswordKey());
|
||||
QScopedPointer<PasswordKey> passwordKey2(new PasswordKey("test"));
|
||||
bool ok;
|
||||
QString errorString;
|
||||
|
||||
// make sure that addKey() creates a copy of the keys
|
||||
compositeKey1->addKey(*passwordKey1);
|
||||
compositeKey1->addKey(*passwordKey2);
|
||||
|
||||
QByteArray transformed = compositeKey1->transform(QByteArray(32, '\0'), 1, &ok, &errorString);
|
||||
QVERIFY(ok);
|
||||
QCOMPARE(transformed.size(), 32);
|
||||
AesKdf kdf;
|
||||
kdf.setRounds(1);
|
||||
QByteArray transformed1;
|
||||
QVERIFY(compositeKey1->transform(kdf, transformed1));
|
||||
QCOMPARE(transformed1.size(), 32);
|
||||
|
||||
// make sure the subkeys are copied
|
||||
QScopedPointer<CompositeKey> compositeKey2(compositeKey1->clone());
|
||||
QCOMPARE(compositeKey2->transform(QByteArray(32, '\0'), 1, &ok, &errorString), transformed);
|
||||
QVERIFY(ok);
|
||||
QByteArray transformed2;
|
||||
QVERIFY(compositeKey2->transform(kdf, transformed2));
|
||||
QCOMPARE(transformed2.size(), 32);
|
||||
QCOMPARE(transformed1, transformed2);
|
||||
|
||||
QScopedPointer<CompositeKey> compositeKey3(new CompositeKey());
|
||||
QScopedPointer<CompositeKey> compositeKey4(new CompositeKey());
|
||||
@ -150,12 +153,19 @@ void TestKeys::testCreateAndOpenFileKey()
|
||||
|
||||
KeePass2Writer writer;
|
||||
writer.writeDatabase(&dbBuffer, dbOrg.data());
|
||||
bool writeSuccess = writer.writeDatabase(&dbBuffer, dbOrg.data());
|
||||
if (writer.hasError()) {
|
||||
QFAIL(writer.errorString().toUtf8().constData());
|
||||
}
|
||||
QVERIFY(writeSuccess);
|
||||
dbBuffer.reset();
|
||||
|
||||
KeePass2Reader reader;
|
||||
QScopedPointer<Database> dbRead(reader.readDatabase(&dbBuffer, compositeKey));
|
||||
if (reader.hasError()) {
|
||||
QFAIL(reader.errorString().toUtf8().constData());
|
||||
}
|
||||
QVERIFY(dbRead);
|
||||
QVERIFY(!reader.hasError());
|
||||
QCOMPARE(dbRead->metadata()->name(), dbName);
|
||||
}
|
||||
|
||||
@ -208,10 +218,12 @@ void TestKeys::benchmarkTransformKey()
|
||||
|
||||
QByteArray seed(32, '\x4B');
|
||||
|
||||
bool ok;
|
||||
QString errorString;
|
||||
QByteArray result;
|
||||
AesKdf kdf;
|
||||
kdf.setSeed(seed);
|
||||
kdf.setRounds(1e6);
|
||||
|
||||
QBENCHMARK {
|
||||
compositeKey.transform(seed, 1e6, &ok, &errorString);
|
||||
}
|
||||
Q_UNUSED(compositeKey.transform(kdf, result));
|
||||
};
|
||||
}
|
||||
|
@ -35,29 +35,29 @@ void TestRandom::testUInt()
|
||||
{
|
||||
QByteArray nextBytes;
|
||||
|
||||
nextBytes = Endian::int32ToBytes(42, QSysInfo::ByteOrder);
|
||||
nextBytes = Endian::sizedIntToBytes(42, QSysInfo::ByteOrder);
|
||||
m_backend->setNextBytes(nextBytes);
|
||||
QCOMPARE(randomGen()->randomUInt(100), 42U);
|
||||
|
||||
nextBytes = Endian::int32ToBytes(117, QSysInfo::ByteOrder);
|
||||
nextBytes = Endian::sizedIntToBytes(117, QSysInfo::ByteOrder);
|
||||
m_backend->setNextBytes(nextBytes);
|
||||
QCOMPARE(randomGen()->randomUInt(100), 17U);
|
||||
|
||||
nextBytes = Endian::int32ToBytes(1001, QSysInfo::ByteOrder);
|
||||
nextBytes = Endian::sizedIntToBytes(1001, QSysInfo::ByteOrder);
|
||||
m_backend->setNextBytes(nextBytes);
|
||||
QCOMPARE(randomGen()->randomUInt(1), 0U);
|
||||
|
||||
nextBytes.clear();
|
||||
nextBytes.append(Endian::int32ToBytes(QUINT32_MAX, QSysInfo::ByteOrder));
|
||||
nextBytes.append(Endian::int32ToBytes(QUINT32_MAX - 70000U, QSysInfo::ByteOrder));
|
||||
nextBytes.append(Endian::sizedIntToBytes(QUINT32_MAX, QSysInfo::ByteOrder));
|
||||
nextBytes.append(Endian::sizedIntToBytes(QUINT32_MAX - 70000U, QSysInfo::ByteOrder));
|
||||
m_backend->setNextBytes(nextBytes);
|
||||
QCOMPARE(randomGen()->randomUInt(100000U), (QUINT32_MAX - 70000U) % 100000U);
|
||||
|
||||
nextBytes.clear();
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
nextBytes.append(Endian::int32ToBytes((QUINT32_MAX / 2U) + 1U + i, QSysInfo::ByteOrder));
|
||||
nextBytes.append(Endian::sizedIntToBytes((QUINT32_MAX / 2U) + 1U + i, QSysInfo::ByteOrder));
|
||||
}
|
||||
nextBytes.append(Endian::int32ToBytes(QUINT32_MAX / 2U, QSysInfo::ByteOrder));
|
||||
nextBytes.append(Endian::sizedIntToBytes(QUINT32_MAX / 2U, QSysInfo::ByteOrder));
|
||||
m_backend->setNextBytes(nextBytes);
|
||||
QCOMPARE(randomGen()->randomUInt((QUINT32_MAX / 2U) + 1U), QUINT32_MAX / 2U);
|
||||
}
|
||||
@ -66,7 +66,7 @@ void TestRandom::testUIntRange()
|
||||
{
|
||||
QByteArray nextBytes;
|
||||
|
||||
nextBytes = Endian::int32ToBytes(42, QSysInfo::ByteOrder);
|
||||
nextBytes = Endian::sizedIntToBytes(42, QSysInfo::ByteOrder);
|
||||
m_backend->setNextBytes(nextBytes);
|
||||
QCOMPARE(randomGen()->randomUIntRange(100, 200), 142U);
|
||||
}
|
||||
|
@ -342,6 +342,56 @@ void TestSymmetricCipher::testSalsa20()
|
||||
QCOMPARE(cipherTextB.mid(448, 64), expectedCipherText4);
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testChaCha20()
|
||||
{
|
||||
// https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#section-7
|
||||
bool ok;
|
||||
|
||||
{
|
||||
QByteArray key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000");
|
||||
QByteArray iv = QByteArray::fromHex("0000000000000000");
|
||||
SymmetricCipher cipher(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, SymmetricCipher::Encrypt);
|
||||
QVERIFY(cipher.init(key, iv));
|
||||
QCOMPARE(cipher.process(QByteArray(64, 0), &ok),
|
||||
QByteArray::fromHex(
|
||||
"76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586"));
|
||||
QVERIFY(ok);
|
||||
}
|
||||
|
||||
{
|
||||
QByteArray key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000001");
|
||||
QByteArray iv = QByteArray::fromHex("0000000000000000");
|
||||
SymmetricCipher cipher(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, SymmetricCipher::Encrypt);
|
||||
QVERIFY(cipher.init(key, iv));
|
||||
QCOMPARE(cipher.process(QByteArray(64, 0), &ok),
|
||||
QByteArray::fromHex(
|
||||
"4540f05a9f1fb296d7736e7b208e3c96eb4fe1834688d2604f450952ed432d41bbe2a0b6ea7566d2a5d1e7e20d42af2c53d792b1c43fea817e9ad275ae546963"));
|
||||
QVERIFY(ok);
|
||||
}
|
||||
|
||||
{
|
||||
QByteArray key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000");
|
||||
QByteArray iv = QByteArray::fromHex("0000000000000001");
|
||||
SymmetricCipher cipher(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, SymmetricCipher::Encrypt);
|
||||
QVERIFY(cipher.init(key, iv));
|
||||
QCOMPARE(cipher.process(QByteArray(60, 0), &ok),
|
||||
QByteArray::fromHex(
|
||||
"de9cba7bf3d69ef5e786dc63973f653a0b49e015adbff7134fcb7df137821031e85a050278a7084527214f73efc7fa5b5277062eb7a0433e445f41e3"));
|
||||
QVERIFY(ok);
|
||||
}
|
||||
|
||||
{
|
||||
QByteArray key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000");
|
||||
QByteArray iv = QByteArray::fromHex("0100000000000000");
|
||||
SymmetricCipher cipher(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, SymmetricCipher::Encrypt);
|
||||
QVERIFY(cipher.init(key, iv));
|
||||
QCOMPARE(cipher.process(QByteArray(64, 0), &ok),
|
||||
QByteArray::fromHex(
|
||||
"ef3fdfd6c61578fbf5cf35bd3dd33b8009631634d21e42ac33960bd138e50d32111e4caf237ee53ca8ad6426194a88545ddc497a0b466e7d6bbdb0041b2f586b"));
|
||||
QVERIFY(ok);
|
||||
}
|
||||
}
|
||||
|
||||
void TestSymmetricCipher::testPadding()
|
||||
{
|
||||
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
|
||||
|
@ -34,6 +34,7 @@ private slots:
|
||||
void testTwofish256CbcEncryption();
|
||||
void testTwofish256CbcDecryption();
|
||||
void testSalsa20();
|
||||
void testChaCha20();
|
||||
void testPadding();
|
||||
void testStreamReset();
|
||||
};
|
||||
|
BIN
tests/data/Format400.kdbx
Normal file
BIN
tests/data/Format400.kdbx
Normal file
Binary file not shown.
@ -45,6 +45,7 @@
|
||||
#include "core/Metadata.h"
|
||||
#include "core/Tools.h"
|
||||
#include "crypto/Crypto.h"
|
||||
#include "crypto/kdf/AesKdf.h"
|
||||
#include "format/KeePass2Reader.h"
|
||||
#include "gui/DatabaseTabWidget.h"
|
||||
#include "gui/DatabaseWidget.h"
|
||||
@ -116,7 +117,14 @@ void TestGui::cleanup()
|
||||
triggerAction("actionDatabaseClose");
|
||||
Tools::wait(100);
|
||||
|
||||
if (m_db) {
|
||||
delete m_db;
|
||||
}
|
||||
m_db = nullptr;
|
||||
|
||||
if (m_dbWidget) {
|
||||
delete m_dbWidget;
|
||||
}
|
||||
m_dbWidget = nullptr;
|
||||
}
|
||||
|
||||
@ -898,11 +906,12 @@ void TestGui::testDatabaseSettings()
|
||||
triggerAction("actionChangeDatabaseSettings");
|
||||
QWidget* dbSettingsWidget = m_dbWidget->findChild<QWidget*>("databaseSettingsWidget");
|
||||
QSpinBox* transformRoundsSpinBox = dbSettingsWidget->findChild<QSpinBox*>("transformRoundsSpinBox");
|
||||
transformRoundsSpinBox->setValue(100);
|
||||
QVERIFY(transformRoundsSpinBox != nullptr);
|
||||
transformRoundsSpinBox->setValue(123456);
|
||||
QTest::keyClick(transformRoundsSpinBox, Qt::Key_Enter);
|
||||
// wait for modified timer
|
||||
QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("Save*"));
|
||||
QCOMPARE(m_db->transformRounds(), Q_UINT64_C(100));
|
||||
QCOMPARE(m_db->kdf()->rounds(), 123456);
|
||||
|
||||
triggerAction("actionDatabaseSave");
|
||||
QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("Save"));
|
||||
@ -1058,7 +1067,7 @@ void TestGui::dragAndDropGroup(const QModelIndex& sourceIndex, const QModelIndex
|
||||
QVERIFY(sourceIndex.isValid());
|
||||
QVERIFY(targetIndex.isValid());
|
||||
|
||||
GroupModel* groupModel = qobject_cast<GroupModel*>(m_dbWidget->findChild<GroupView*>("groupView")->model());
|
||||
auto groupModel = qobject_cast<GroupModel*>(m_dbWidget->findChild<GroupView*>("groupView")->model());
|
||||
|
||||
QMimeData mimeData;
|
||||
QByteArray encoded;
|
||||
|
@ -23,6 +23,7 @@
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
|
||||
class Database;
|
||||
class DatabaseTabWidget;
|
||||
@ -71,14 +72,14 @@ private:
|
||||
void clickIndex(const QModelIndex& index, QAbstractItemView* view, Qt::MouseButton button,
|
||||
Qt::KeyboardModifiers stateKey = 0);
|
||||
|
||||
MainWindow* m_mainWindow;
|
||||
DatabaseTabWidget* m_tabWidget;
|
||||
DatabaseWidget* m_dbWidget;
|
||||
QPointer<MainWindow> m_mainWindow;
|
||||
QPointer<DatabaseTabWidget> m_tabWidget;
|
||||
QPointer<DatabaseWidget> m_dbWidget;
|
||||
QPointer<Database> m_db;
|
||||
QByteArray m_dbData;
|
||||
TemporaryFile m_dbFile;
|
||||
QString m_dbFileName;
|
||||
QString m_dbFilePath;
|
||||
Database* m_db;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_TESTGUI_H
|
||||
|
Loading…
Reference in New Issue
Block a user