KeeShare: Remove checking signed container

* Remove QuaZip dependency in favor of minizip
* Remove signature checks, but maintain signatures for backwards compatibility
* Remove UI components related to certificates except for personal certificate for backwards compatibility
* Default to unsigned containers (*.kdbx)
This commit is contained in:
Jonathan White 2021-05-16 20:16:01 -04:00
parent c88d8c870f
commit 12990e59ad
28 changed files with 298 additions and 1665 deletions

View File

@ -50,7 +50,7 @@ option(WITH_XC_NETWORKING "Include networking code (e.g. for downloading website
option(WITH_XC_BROWSER "Include browser integration with keepassxc-browser." OFF)
option(WITH_XC_YUBIKEY "Include YubiKey support." OFF)
option(WITH_XC_SSHAGENT "Include SSH agent support." OFF)
option(WITH_XC_KEESHARE "Sharing integration with KeeShare (requires quazip5 for secure containers)" OFF)
option(WITH_XC_KEESHARE "Sharing integration with KeeShare" OFF)
option(WITH_XC_UPDATECHECK "Include automatic update checks; disable for controlled distributions" ON)
if(UNIX AND NOT APPLE)
option(WITH_XC_FDOSECRETS "Implement freedesktop.org Secret Storage Spec server side API." OFF)

View File

@ -102,7 +102,6 @@ These steps place the compiled KeePassXC binary inside the `./build/src/` direct
-DWITH_XC_TOUCHID=[ON|OFF] (macOS Only) Enable/Disable Touch ID unlock (default:OFF)
-DWITH_XC_FDOSECRETS=[ON|OFF] (Linux Only) Enable/Disable Freedesktop.org Secrets Service support (default:OFF)
-DWITH_XC_KEESHARE=[ON|OFF] Enable/Disable KeeShare group synchronization extension (default: OFF)
-DWITH_XC_KEESHARE_SECURE=[ON|OFF] Enable/Disable KeeShare signed containers, requires libquazip5 (default: OFF)
-DWITH_XC_ALL=[ON|OFF] Enable/Disable compiling all plugins above (default: OFF)
-DWITH_XC_UPDATECHECK=[ON|OFF] Enable/Disable automatic updating checking (requires WITH_XC_NETWORKING) (default: ON)

9
cmake/FindMinizip.cmake Normal file
View File

@ -0,0 +1,9 @@
# MINIZIP_FOUND - Minizip library was found
# MINIZIP_INCLUDE_DIR - Path to Minizip include dir
# MINIZIP_LIBRARIES - List of Minizip libraries
find_library(MINIZIP_LIBRARIES NAMES minizip libminizip)
find_path(MINIZIP_INCLUDE_DIR zip.h PATH_SUFFIXES minizip)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Minizip DEFAULT_MSG MINIZIP_LIBRARIES MINIZIP_INCLUDE_DIR)

View File

@ -1,24 +0,0 @@
# QUAZIP_FOUND - QuaZip library was found
# QUAZIP_INCLUDE_DIR - Path to QuaZip include dir
# QUAZIP_INCLUDE_DIRS - Path to QuaZip and zlib include dir (combined from QUAZIP_INCLUDE_DIR + ZLIB_INCLUDE_DIR)
# QUAZIP_LIBRARIES - List of QuaZip libraries
# QUAZIP_ZLIB_INCLUDE_DIR - The include dir of zlib headers
if(WIN32)
find_library(QUAZIP_LIBRARIES libquazip5)
find_path(QUAZIP_INCLUDE_DIR quazip.h PATH_SUFFIXES quazip5)
find_path(QUAZIP_ZLIB_INCLUDE_DIR zlib.h)
else()
find_library(QUAZIP_LIBRARIES
NAMES quazip5 quazip
PATHS /usr/lib /usr/lib64 /usr/local/lib
)
find_path(QUAZIP_INCLUDE_DIR quazip.h
PATHS /usr/include /usr/local/include
PATH_SUFFIXES quazip5 quazip
)
find_path(QUAZIP_ZLIB_INCLUDE_DIR zlib.h PATHS /usr/include /usr/local/include)
endif()
include(FindPackageHandleStandardArgs)
set(QUAZIP_INCLUDE_DIRS ${QUAZIP_INCLUDE_DIR} ${QUAZIP_ZLIB_INCLUDE_DIR})
find_package_handle_standard_args(QUAZIP DEFAULT_MSG QUAZIP_LIBRARIES QUAZIP_INCLUDE_DIR QUAZIP_ZLIB_INCLUDE_DIR QUAZIP_INCLUDE_DIRS)

View File

@ -3083,10 +3083,6 @@ Would you like to correct it?</source>
<source>Inactive</source>
<translation>Inactive</translation>
</message>
<message>
<source>KeeShare unsigned container</source>
<translation>KeeShare unsigned container</translation>
</message>
<message>
<source>KeeShare signed container</source>
<translation>KeeShare signed container</translation>
@ -3173,6 +3169,10 @@ Supported extensions are: %1.</source>
<source>Browse</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>KeeShare container</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>EditGroupWidgetMain</name>
@ -6742,18 +6742,6 @@ Kernel: %3 %4</source>
<source>Auto-Type</source>
<translation type="unfinished">Auto-Type</translation>
</message>
<message>
<source>KeeShare (signed and unsigned sharing)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>KeeShare (only signed sharing)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>KeeShare (only unsigned sharing)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>YubiKey</source>
<translation type="unfinished"></translation>
@ -7641,6 +7629,10 @@ Please consider generating a new key file.</source>
<source>%1 characters</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>KeeShare</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QtIOCompressor</name>
@ -8243,91 +8235,14 @@ Please consider generating a new key file.</source>
<source>Fingerprint:</source>
<translation>Fingerprint:</translation>
</message>
<message>
<source>Signer</source>
<translation>Signer</translation>
</message>
<message>
<source>Generate</source>
<translation>Generate</translation>
</message>
<message>
<source>Import</source>
<translation>Import</translation>
</message>
<message>
<source>Export</source>
<translation>Export</translation>
</message>
<message>
<source>Imported certificates</source>
<translation>Imported certificates</translation>
</message>
<message>
<source>Trust</source>
<translation>Trust</translation>
</message>
<message>
<source>Ask</source>
<translation>Ask</translation>
</message>
<message>
<source>Untrust</source>
<translation>Untrust</translation>
</message>
<message>
<source>Remove</source>
<translation>Remove</translation>
</message>
<message>
<source>Path</source>
<translation>Path</translation>
</message>
<message>
<source>Status</source>
<translation>Status</translation>
</message>
<message>
<source>Fingerprint</source>
<translation>Fingerprint</translation>
</message>
<message>
<source>Trusted</source>
<translation>Trusted</translation>
</message>
<message>
<source>Untrusted</source>
<translation>Untrusted</translation>
</message>
<message>
<source>Unknown</source>
<translation>Unknown</translation>
</message>
<message>
<source>key.share</source>
<comment>Filetype for KeeShare key</comment>
<translation>key.share</translation>
</message>
<message>
<source>KeeShare key file</source>
<translation>KeeShare key file</translation>
</message>
<message>
<source>All files</source>
<translation>All files</translation>
</message>
<message>
<source>Select path</source>
<translation>Select path</translation>
</message>
<message>
<source>Exporting changed certificate</source>
<translation>Exporting changed certificate</translation>
</message>
<message>
<source>The exported certificate is not the same as the one in use. Do you want to export the current certificate?</source>
<translation>The exported certificate is not the same as the one in use. Do you want to export the current certificate?</translation>
</message>
<message>
<source>Signer:</source>
<translation type="unfinished"></translation>
@ -8352,132 +8267,18 @@ Please consider generating a new key file.</source>
<source>Generate new certificate</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Import existing certificate</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Export own certificate</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Known shares</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Trust selected certificate</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Ask whether to trust the selected certificate every time</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Untrust selected certificate</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Remove selected certificate</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ShareExport</name>
<message>
<source>Overwriting signed share container is not supported - export prevented</source>
<translation type="unfinished">Overwriting signed share container is not supported - export prevented</translation>
</message>
<message>
<source>Could not write export container (%1)</source>
<translation type="unfinished">Could not write export container (%1)</translation>
</message>
<message>
<source>Could not embed signature: Could not open file to write (%1)</source>
<source>Could not write export container.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Could not embed signature: Could not write file (%1)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Could not embed database: Could not open file to write (%1)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Could not embed database: Could not write file (%1)</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Overwriting unsigned share container is not supported - export prevented</source>
<translation type="unfinished">Overwriting unsigned share container is not supported - export prevented</translation>
</message>
<message>
<source>Could not write export container</source>
<translation type="unfinished">Could not write export container</translation>
</message>
</context>
<context>
<name>ShareImport</name>
<message>
<source>Not this time</source>
<translation type="unfinished">Not this time</translation>
</message>
<message>
<source>Never</source>
<translation type="unfinished">Never</translation>
</message>
<message>
<source>Always</source>
<translation type="unfinished">Always</translation>
</message>
<message>
<source>Just this time</source>
<translation type="unfinished">Just this time</translation>
</message>
<message>
<source>Signed share container are not supported - import prevented</source>
<translation type="unfinished">Signed share container are not supported - import prevented</translation>
</message>
<message>
<source>File is not readable</source>
<translation type="unfinished">File is not readable</translation>
</message>
<message>
<source>Invalid sharing container</source>
<translation type="unfinished">Invalid sharing container</translation>
</message>
<message>
<source>Untrusted import prevented</source>
<translation type="unfinished">Untrusted import prevented</translation>
</message>
<message>
<source>Successful signed import</source>
<translation type="unfinished">Successful signed import</translation>
</message>
<message>
<source>Unsigned share container are not supported - import prevented</source>
<translation type="unfinished">Unsigned share container are not supported - import prevented</translation>
</message>
<message>
<source>Successful unsigned import</source>
<translation type="unfinished">Successful unsigned import</translation>
</message>
<message>
<source>File does not exist</source>
<translation type="unfinished">File does not exist</translation>
</message>
<message>
<source>KeeShare Import</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>The source of the shared container cannot be verified because it is not signed. Do you really want to import from %1?</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Do you want to trust %1 with certificate fingerprint:
%2
%3</source>
<source>Successful import</source>
<translation type="unfinished"></translation>
</message>
</context>

View File

@ -53,7 +53,6 @@ parts:
- -DKEEPASSXC_BUILD_TYPE=Release
- -DWITH_TESTS=OFF
- -DWITH_XC_ALL=ON
- -DWITH_XC_KEESHARE_SECURE=ON
build-packages:
- g++
- libgcrypt20-dev
@ -71,7 +70,7 @@ parts:
- libsodium-dev
- libargon2-0-dev
- libqrencode-dev
- libquazip5-dev
- libminizip-dev
- asciidoctor
stage-packages:
- dbus

View File

@ -226,7 +226,7 @@ add_feature_info(Auto-Type WITH_XC_AUTOTYPE "Automatic password typing")
add_feature_info(Networking WITH_XC_NETWORKING "Compile KeePassXC with network access code (e.g. for downloading website icons)")
add_feature_info(KeePassXC-Browser WITH_XC_BROWSER "Browser integration with KeePassXC-Browser")
add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible with KeeAgent")
add_feature_info(KeeShare WITH_XC_KEESHARE "Sharing integration with KeeShare (requires quazip5 for secure containers)")
add_feature_info(KeeShare WITH_XC_KEESHARE "Sharing integration with KeeShare")
add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
add_feature_info(UpdateCheck WITH_XC_UPDATECHECK "Automatic update checking")
if(UNIX AND NOT APPLE)

View File

@ -18,8 +18,6 @@
#cmakedefine WITH_XC_YUBIKEY
#cmakedefine WITH_XC_SSHAGENT
#cmakedefine WITH_XC_KEESHARE
#cmakedefine WITH_XC_KEESHARE_INSECURE
#cmakedefine WITH_XC_KEESHARE_SECURE
#cmakedefine WITH_XC_UPDATECHECK
#cmakedefine WITH_XC_TOUCHID
#cmakedefine WITH_XC_FDOSECRETS

View File

@ -92,12 +92,8 @@ namespace Tools
#ifdef WITH_XC_SSHAGENT
extensions += "\n- " + QObject::tr("SSH Agent");
#endif
#if defined(WITH_XC_KEESHARE_SECURE) && defined(WITH_XC_KEESHARE_INSECURE)
extensions += "\n- " + QObject::tr("KeeShare (signed and unsigned sharing)");
#elif defined(WITH_XC_KEESHARE_SECURE)
extensions += "\n- " + QObject::tr("KeeShare (only signed sharing)");
#elif defined(WITH_XC_KEESHARE_INSECURE)
extensions += "\n- " + QObject::tr("KeeShare (only unsigned sharing)");
#ifdef WITH_XC_KEESHARE
extensions += "\n- " + QObject::tr("KeeShare");
#endif
#ifdef WITH_XC_YUBIKEY
extensions += "\n- " + QObject::tr("YubiKey");

View File

@ -1,8 +1,4 @@
if(WITH_XC_KEESHARE)
set(WITH_XC_KEESHARE_INSECURE ON PARENT_SCOPE)
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
set(keeshare_SOURCES
SettingsPageKeeShare.cpp
SettingsWidgetKeeShare.cpp
@ -15,23 +11,12 @@ if(WITH_XC_KEESHARE)
ShareImport.cpp
ShareExport.cpp
ShareObserver.cpp
Signature.cpp
)
)
find_package(Minizip REQUIRED)
add_library(keeshare STATIC ${keeshare_SOURCES})
target_link_libraries(keeshare PUBLIC Qt5::Core Qt5::Widgets ${BOTAN2_LIBRARIES})
# Try to find libquazip5, if found, enable secure sharing
find_package(QuaZip)
if(QUAZIP_FOUND)
set(WITH_XC_KEESHARE_SECURE ON PARENT_SCOPE)
target_include_directories(keeshare SYSTEM PRIVATE ${QUAZIP_INCLUDE_DIR})
target_link_libraries(keeshare PRIVATE ${QUAZIP_LIBRARIES})
else()
set(WITH_XC_KEESHARE_SECURE OFF PARENT_SCOPE)
message(STATUS "KeeShare: Secure container support is DISABLED; quazip library not found")
endif()
else(WITH_XC_KEESHARE)
set(WITH_XC_KEESHARE_INSECURE OFF PARENT_SCOPE)
set(WITH_XC_KEESHARE_SECURE OFF PARENT_SCOPE)
target_link_libraries(keeshare PUBLIC Qt5::Core Qt5::Widgets ${BOTAN2_LIBRARIES} ${ZLIB_LIBRARIES} PRIVATE ${MINIZIP_LIBRARIES})
target_include_directories(keeshare SYSTEM PRIVATE ${MINIZIP_INCLUDE_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
endif(WITH_XC_KEESHARE)

View File

@ -53,7 +53,13 @@ void KeeShare::init(QObject* parent)
KeeShareSettings::Own KeeShare::own()
{
return KeeShareSettings::Own::deserialize(config()->get(Config::KeeShare_Own).toString());
// Read existing own certificate or generate a new one if none available
auto own = KeeShareSettings::Own::deserialize(config()->get(Config::KeeShare_Own).toString());
if (own.key.isNull()) {
own = KeeShareSettings::Own::generate();
setOwn(own);
}
return own;
}
KeeShareSettings::Active KeeShare::active()
@ -61,16 +67,6 @@ KeeShareSettings::Active KeeShare::active()
return KeeShareSettings::Active::deserialize(config()->get(Config::KeeShare_Active).toString());
}
KeeShareSettings::Foreign KeeShare::foreign()
{
return KeeShareSettings::Foreign::deserialize(config()->get(Config::KeeShare_Foreign).toString());
}
void KeeShare::setForeign(const KeeShareSettings::Foreign& foreign)
{
config()->set(Config::KeeShare_Foreign, KeeShareSettings::Foreign::serialize(foreign));
}
void KeeShare::setActive(const KeeShareSettings::Active& active)
{
config()->set(Config::KeeShare_Active, KeeShareSettings::Active::serialize(active));
@ -117,16 +113,6 @@ void KeeShare::setReferenceTo(Group* group, const KeeShareSettings::Reference& r
bool KeeShare::isEnabled(const Group* group)
{
const auto reference = KeeShare::referenceOf(group);
#if !defined(WITH_XC_KEESHARE_SECURE)
if (reference.path.endsWith(signedContainerFileType(), Qt::CaseInsensitive)) {
return false;
}
#endif
#if !defined(WITH_XC_KEESHARE_INSECURE)
if (reference.path.endsWith(unsignedContainerFileType(), Qt::CaseInsensitive)) {
return false;
}
#endif
const auto active = KeeShare::active();
return (reference.isImporting() && active.in) || (reference.isExporting() && active.out);
}

View File

@ -55,12 +55,11 @@ public:
static QString sharingLabel(const Group* group);
static KeeShareSettings::Own own();
static KeeShareSettings::Active active();
static KeeShareSettings::Foreign foreign();
static void setForeign(const KeeShareSettings::Foreign& foreign);
static void setActive(const KeeShareSettings::Active& active);
static void setOwn(const KeeShareSettings::Own& own);
static KeeShareSettings::Active active();
static void setActive(const KeeShareSettings::Active& active);
static KeeShareSettings::Reference referenceOf(const Group* group);
static void setReferenceTo(Group* group, const KeeShareSettings::Reference& reference);
static QString referenceTypeLabel(const KeeShareSettings::Reference& reference);

View File

@ -23,8 +23,8 @@
#include "core/Metadata.h"
#include "crypto/Random.h"
#include "gui/DatabaseIcons.h"
#include "keeshare/Signature.h"
#include <QDataStream>
#include <QTextCodec>
#include <QXmlStreamWriter>
@ -261,82 +261,6 @@ namespace KeeShareSettings
return own;
}
bool ScopedCertificate::operator==(const ScopedCertificate& other) const
{
return trust == other.trust && path == other.path && certificate == other.certificate;
}
bool ScopedCertificate::operator!=(const ScopedCertificate& other) const
{
return !operator==(other);
}
void ScopedCertificate::serialize(QXmlStreamWriter& writer, const ScopedCertificate& scopedCertificate)
{
writer.writeAttribute("Path", scopedCertificate.path);
QString trust = "Ask";
if (scopedCertificate.trust == KeeShareSettings::Trust::Trusted) {
trust = "Trusted";
}
if (scopedCertificate.trust == KeeShareSettings::Trust::Untrusted) {
trust = "Untrusted";
}
writer.writeAttribute("Trust", trust);
Certificate::serialize(writer, scopedCertificate.certificate);
}
ScopedCertificate ScopedCertificate::deserialize(QXmlStreamReader& reader)
{
ScopedCertificate scopedCertificate;
scopedCertificate.path = reader.attributes().value("Path").toString();
scopedCertificate.trust = KeeShareSettings::Trust::Ask;
auto trust = reader.attributes().value("Trust").toString();
if (trust.compare("Trusted", Qt::CaseInsensitive) == 0) {
scopedCertificate.trust = KeeShareSettings::Trust::Trusted;
}
if (trust.compare("Untrusted", Qt::CaseInsensitive) == 0) {
scopedCertificate.trust = KeeShareSettings::Trust::Untrusted;
}
scopedCertificate.certificate = Certificate::deserialize(reader);
return scopedCertificate;
}
QString Foreign::serialize(const Foreign& foreign)
{
return xmlSerialize([&](QXmlStreamWriter& writer) {
writer.writeStartElement("Foreign");
for (const ScopedCertificate& scopedCertificate : foreign.certificates) {
writer.writeStartElement("Certificate");
ScopedCertificate::serialize(writer, scopedCertificate);
writer.writeEndElement();
}
writer.writeEndElement();
});
}
Foreign Foreign::deserialize(const QString& raw)
{
Foreign foreign;
xmlDeserialize(raw, [&](QXmlStreamReader& reader) {
while (!reader.error() && reader.readNextStartElement()) {
if (reader.name() == "Foreign") {
while (!reader.error() && reader.readNextStartElement()) {
if (reader.name() == "Certificate") {
foreign.certificates << ScopedCertificate::deserialize(reader);
} else {
qWarning("Unknown Certificates element %s", qPrintable(reader.name().toString()));
reader.skipCurrentElement();
}
}
} else {
qWarning("Unknown KeeShareSettings element %s", qPrintable(reader.name().toString()));
reader.skipCurrentElement();
}
}
});
return foreign;
}
Reference::Reference()
: type(Inactive)
, uuid(QUuid::createUuid())
@ -433,31 +357,38 @@ namespace KeeShareSettings
QString Sign::serialize(const Sign& sign)
{
if (sign.certificate.isNull()) {
return {};
}
// Extract RSA key data to serialize an ssh-rsa public key.
// ssh-rsa keys are currently not built into Botan
const auto rsaKey = static_cast<Botan::RSA_PrivateKey*>(sign.certificate.key.data());
std::vector<uint8_t> rsaE(rsaKey->get_e().bytes());
rsaKey->get_e().binary_encode(rsaE.data());
std::vector<uint8_t> rsaN(rsaKey->get_n().bytes());
rsaKey->get_n().binary_encode(rsaN.data());
QByteArray rsaKeySerialized;
QDataStream stream(&rsaKeySerialized, QIODevice::WriteOnly);
stream.writeBytes("ssh-rsa", 7);
stream.writeBytes(reinterpret_cast<const char*>(rsaE.data()), rsaE.size());
stream.writeBytes(reinterpret_cast<const char*>(rsaN.data()), rsaN.size());
return xmlSerialize([&](QXmlStreamWriter& writer) {
writer.writeStartElement("Signature");
writer.writeCharacters(sign.signature);
writer.writeEndElement();
writer.writeStartElement("Certificate");
Certificate::serialize(writer, sign.certificate);
writer.writeStartElement("Signer");
writer.writeCharacters(sign.certificate.signer);
writer.writeEndElement();
writer.writeStartElement("Key");
writer.writeCharacters(rsaKeySerialized.toBase64());
writer.writeEndElement();
writer.writeEndElement();
});
}
Sign Sign::deserialize(const QString& raw)
{
Sign sign;
xmlDeserialize(raw, [&](QXmlStreamReader& reader) {
while (!reader.error() && reader.readNextStartElement()) {
if (reader.name() == "Signature") {
sign.signature = reader.readElementText();
} else if (reader.name() == "Certificate") {
sign.certificate = KeeShareSettings::Certificate::deserialize(reader);
} else {
qWarning("Unknown Sign element %s", qPrintable(reader.name().toString()));
reader.skipCurrentElement();
}
}
});
return sign;
}
} // namespace KeeShareSettings

View File

@ -55,7 +55,6 @@ namespace KeeShareSettings
bool operator!=(const Key& other) const;
bool isNull() const;
QString privateKey() const;
static void serialize(QXmlStreamWriter& writer, const Key& key);
static Key deserialize(QXmlStreamReader& reader);
@ -99,42 +98,6 @@ namespace KeeShareSettings
static Own generate();
};
enum class Trust
{
Ask,
Untrusted,
Trusted
};
struct ScopedCertificate
{
QString path;
Certificate certificate;
Trust trust;
bool operator==(const ScopedCertificate& other) const;
bool operator!=(const ScopedCertificate& other) const;
bool isUnknown() const
{
return certificate.isNull();
}
bool isKnown() const
{
return !certificate.isNull();
}
static void serialize(QXmlStreamWriter& writer, const ScopedCertificate& certificate);
static ScopedCertificate deserialize(QXmlStreamReader& reader);
};
struct Foreign
{
QList<ScopedCertificate> certificates;
static QString serialize(const Foreign& foreign);
static Foreign deserialize(const QString& raw);
};
struct Sign
{
QString signature;
@ -146,7 +109,6 @@ namespace KeeShareSettings
}
static QString serialize(const Sign& sign);
static Sign deserialize(const QString& raw);
};
enum TypeFlag

View File

@ -33,21 +33,8 @@ SettingsWidgetKeeShare::SettingsWidgetKeeShare(QWidget* parent)
{
m_ui->setupUi(this);
#if !defined(WITH_XC_KEESHARE_SECURE)
// Setting does not help the user of Version without signed export
m_ui->ownCertificateGroupBox->setVisible(false);
#endif
connect(m_ui->ownCertificateSignerEdit, SIGNAL(textChanged(QString)), SLOT(setVerificationExporter(QString)));
connect(m_ui->generateOwnCerticateButton, SIGNAL(clicked(bool)), SLOT(generateCertificate()));
connect(m_ui->importOwnCertificateButton, SIGNAL(clicked(bool)), SLOT(importCertificate()));
connect(m_ui->exportOwnCertificateButton, SIGNAL(clicked(bool)), SLOT(exportCertificate()));
connect(m_ui->trustImportedCertificateButton, SIGNAL(clicked(bool)), SLOT(trustSelectedCertificates()));
connect(m_ui->askImportedCertificateButton, SIGNAL(clicked(bool)), SLOT(askSelectedCertificates()));
connect(m_ui->untrustImportedCertificateButton, SIGNAL(clicked(bool)), SLOT(untrustSelectedCertificates()));
connect(m_ui->removeImportedCertificateButton, SIGNAL(clicked(bool)), SLOT(removeSelectedCertificates()));
}
SettingsWidgetKeeShare::~SettingsWidgetKeeShare()
@ -62,46 +49,6 @@ void SettingsWidgetKeeShare::loadSettings()
m_own = KeeShare::own();
updateOwnCertificate();
m_foreign = KeeShare::foreign();
updateForeignCertificates();
}
void SettingsWidgetKeeShare::updateForeignCertificates()
{
auto headers = QStringList() << tr("Path") << tr("Status");
#if defined(WITH_XC_KEESHARE_SECURE)
headers << tr("Signer") << tr("Fingerprint");
#endif
m_importedCertificateModel.reset(new QStandardItemModel());
m_importedCertificateModel->setHorizontalHeaderLabels(headers);
for (const auto& scopedCertificate : m_foreign.certificates) {
QList<QStandardItem*> items;
items << new QStandardItem(scopedCertificate.path);
switch (scopedCertificate.trust) {
case KeeShareSettings::Trust::Ask:
items << new QStandardItem(tr("Ask"));
break;
case KeeShareSettings::Trust::Trusted:
items << new QStandardItem(tr("Trusted"));
break;
case KeeShareSettings::Trust::Untrusted:
items << new QStandardItem(tr("Untrusted"));
break;
}
#if defined(WITH_XC_KEESHARE_SECURE)
items << new QStandardItem(scopedCertificate.isKnown() ? scopedCertificate.certificate.signer : tr("Unknown"));
items << new QStandardItem(scopedCertificate.certificate.fingerprint());
#endif
m_importedCertificateModel->appendRow(items);
}
m_ui->importedCertificateTableView->setModel(m_importedCertificateModel.data());
m_ui->importedCertificateTableView->resizeColumnsToContents();
}
void SettingsWidgetKeeShare::updateOwnCertificate()
@ -119,7 +66,6 @@ void SettingsWidgetKeeShare::saveSettings()
// store changes to the settings in a temporary object and check on the final values
// of this object (similar scheme to Entry) - this way we could validate the settings before save
KeeShare::setOwn(m_own);
KeeShare::setForeign(m_foreign);
KeeShare::setActive(active);
config()->set(Config::KeeShare_QuietSuccess, m_ui->quietSuccessCheckBox->isChecked());
@ -137,99 +83,3 @@ void SettingsWidgetKeeShare::generateCertificate()
m_ui->ownCertificateSignerEdit->setText(m_own.certificate.signer);
m_ui->ownCertificateFingerprintEdit->setText(m_own.certificate.fingerprint());
}
void SettingsWidgetKeeShare::importCertificate()
{
auto defaultDirPath = FileDialog::getLastDir("keeshare_key");
const auto filetype = tr("key.share", "Filetype for KeeShare key");
const auto filters = QString("%1 (*." + filetype + ");;%2 (*)").arg(tr("KeeShare key file"), tr("All files"));
QString filename = fileDialog()->getOpenFileName(this, tr("Select path"), defaultDirPath, filters);
if (filename.isEmpty()) {
return;
}
QFile file(filename);
file.open(QIODevice::ReadOnly);
QTextStream stream(&file);
m_own = KeeShareSettings::Own::deserialize(stream.readAll());
file.close();
FileDialog::saveLastDir("keeshare_key", filename);
updateOwnCertificate();
}
void SettingsWidgetKeeShare::exportCertificate()
{
if (KeeShare::own() != m_own) {
auto ans = MessageBox::warning(
this,
tr("Exporting changed certificate"),
tr("The exported certificate is not the same as the one in use. Do you want to export the "
"current certificate?"),
MessageBox::Yes | MessageBox::No,
MessageBox::No);
if (ans != MessageBox::Yes) {
return;
}
}
auto defaultDirPath = FileDialog::getLastDir("keeshare_key");
const auto filetype = tr("key.share", "Filetype for KeeShare key");
const auto filters = QString("%1 (*." + filetype + ");;%2 (*)").arg(tr("KeeShare key file"), tr("All files"));
QString filename = QString("%1.%2").arg(m_own.certificate.signer).arg(filetype);
filename = fileDialog()->getSaveFileName(this, tr("Select path"), defaultDirPath, filters);
if (filename.isEmpty()) {
return;
}
QFile file(filename);
file.open(QIODevice::Truncate | QIODevice::WriteOnly);
QTextStream stream(&file);
stream << KeeShareSettings::Own::serialize(m_own);
stream.flush();
file.close();
FileDialog::saveLastDir("keeshare_key", filename);
}
void SettingsWidgetKeeShare::trustSelectedCertificates()
{
const auto* selectionModel = m_ui->importedCertificateTableView->selectionModel();
Q_ASSERT(selectionModel);
for (const auto& index : selectionModel->selectedRows()) {
m_foreign.certificates[index.row()].trust = KeeShareSettings::Trust::Trusted;
}
updateForeignCertificates();
}
void SettingsWidgetKeeShare::askSelectedCertificates()
{
const auto* selectionModel = m_ui->importedCertificateTableView->selectionModel();
Q_ASSERT(selectionModel);
for (const auto& index : selectionModel->selectedRows()) {
m_foreign.certificates[index.row()].trust = KeeShareSettings::Trust::Ask;
}
updateForeignCertificates();
}
void SettingsWidgetKeeShare::untrustSelectedCertificates()
{
const auto* selectionModel = m_ui->importedCertificateTableView->selectionModel();
Q_ASSERT(selectionModel);
for (const auto& index : selectionModel->selectedRows()) {
m_foreign.certificates[index.row()].trust = KeeShareSettings::Trust::Untrusted;
}
updateForeignCertificates();
}
void SettingsWidgetKeeShare::removeSelectedCertificates()
{
auto certificates = m_foreign.certificates;
const auto* selectionModel = m_ui->importedCertificateTableView->selectionModel();
Q_ASSERT(selectionModel);
for (const auto& index : selectionModel->selectedRows()) {
certificates.removeOne(m_foreign.certificates[index.row()]);
}
m_foreign.certificates = certificates;
updateForeignCertificates();
}

View File

@ -50,23 +50,13 @@ private slots:
void setVerificationExporter(const QString& signer);
void generateCertificate();
void importCertificate();
void exportCertificate();
void trustSelectedCertificates();
void askSelectedCertificates();
void untrustSelectedCertificates();
void removeSelectedCertificates();
private:
void updateOwnCertificate();
void updateForeignCertificates();
QScopedPointer<Ui::SettingsWidgetKeeShare> m_ui;
KeeShareSettings::Own m_own;
KeeShareSettings::Foreign m_foreign;
QScopedPointer<QStandardItemModel> m_importedCertificateModel;
};
#endif // KEEPASSXC_SETTINGSWIDGETKEESHARE_H

View File

@ -6,11 +6,11 @@
<rect>
<x>0</x>
<y>0</y>
<width>378</width>
<height>511</height>
<width>425</width>
<height>251</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0,0">
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0">
<property name="leftMargin">
<number>0</number>
</property>
@ -70,67 +70,55 @@
</item>
<item>
<widget class="QGroupBox" name="ownCertificateGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Own certificate</string>
</property>
<layout class="QGridLayout" name="gridLayout_2" columnstretch="0,0,0">
<property name="horizontalSpacing">
<number>10</number>
</property>
<item row="0" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout">
<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>
<item>
<widget class="QPushButton" name="generateOwnCerticateButton">
<property name="accessibleName">
<string>Generate new certificate</string>
</property>
<property name="text">
<string>Generate</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="importOwnCertificateButton">
<property name="accessibleName">
<string>Import existing certificate</string>
</property>
<property name="text">
<string>Import</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="exportOwnCertificateButton">
<property name="accessibleName">
<string>Export own certificate</string>
</property>
<property name="text">
<string>Export</string>
</property>
</widget>
</item>
</layout>
<item row="0" column="0">
<widget class="QLabel" name="ownCertificateSignerLabel">
<property name="text">
<string>Signer:</string>
</property>
</widget>
</item>
<item row="3" column="1" colspan="2">
<item row="2" column="1">
<widget class="QPushButton" name="generateOwnCerticateButton">
<property name="accessibleName">
<string>Generate new certificate</string>
</property>
<property name="text">
<string>Generate</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="ownCertificateFingerprintLabel">
<property name="text">
<string>Fingerprint:</string>
</property>
</widget>
</item>
<item row="2" column="2">
<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>
<item row="0" column="1" colspan="2">
<widget class="QLineEdit" name="ownCertificateSignerEdit">
<property name="accessibleName">
<string>Signer name field</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QLineEdit" name="ownCertificateFingerprintEdit">
<property name="accessibleName">
<string>Fingerprint</string>
@ -140,133 +128,6 @@
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QLineEdit" name="ownCertificateSignerEdit">
<property name="accessibleName">
<string>Signer name field</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="ownCertificateSignerLabel">
<property name="text">
<string>Signer:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="ownCertificateFingerprintLabel">
<property name="text">
<string>Fingerprint:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="importedCertificatesGroupBox">
<property name="title">
<string>Imported certificates</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="0" rowspan="2">
<widget class="QTableView" name="importedCertificateTableView">
<property name="accessibleName">
<string>Known shares</string>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="dragDropOverwriteMode">
<bool>false</bool>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::MultiSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="horizontalHeaderVisible">
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item row="0" column="0">
<layout class="QHBoxLayout" name="certificatesActionLayout">
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="trustImportedCertificateButton">
<property name="accessibleName">
<string>Trust selected certificate</string>
</property>
<property name="text">
<string>Trust</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="askImportedCertificateButton">
<property name="accessibleName">
<string>Ask whether to trust the selected certificate every time</string>
</property>
<property name="text">
<string>Ask</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="untrustImportedCertificateButton">
<property name="accessibleName">
<string>Untrust selected certificate</string>
</property>
<property name="text">
<string>Untrust</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeImportedCertificateButton">
<property name="accessibleName">
<string>Remove selected certificate</string>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
@ -289,16 +150,8 @@
<tabstop>enableImportCheckBox</tabstop>
<tabstop>quietSuccessCheckBox</tabstop>
<tabstop>enableExportCheckBox</tabstop>
<tabstop>generateOwnCerticateButton</tabstop>
<tabstop>importOwnCertificateButton</tabstop>
<tabstop>exportOwnCertificateButton</tabstop>
<tabstop>ownCertificateSignerEdit</tabstop>
<tabstop>ownCertificateFingerprintEdit</tabstop>
<tabstop>trustImportedCertificateButton</tabstop>
<tabstop>askImportedCertificateButton</tabstop>
<tabstop>untrustImportedCertificateButton</tabstop>
<tabstop>removeImportedCertificateButton</tabstop>
<tabstop>importedCertificateTableView</tabstop>
</tabstops>
<resources/>
<connections/>

View File

@ -14,23 +14,20 @@
* 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 "ShareExport.h"
#include "config-keepassx.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "crypto/Random.h"
#include "format/KeePass2Writer.h"
#include "gui/Icons.h"
#include "gui/MessageBox.h"
#include "keeshare/KeeShare.h"
#include "keeshare/Signature.h"
#include "keys/PasswordKey.h"
#include <QBuffer>
#include <QTextStream>
#if defined(WITH_XC_KEESHARE_SECURE)
#include <botan/pk_keys.h>
#include <quazipfile.h>
#endif
#include <botan/pubkey.h>
#include <zip.h>
namespace
{
@ -64,8 +61,6 @@ namespace
auto* targetDb = new Database();
auto* targetMetadata = targetDb->metadata();
targetMetadata->setRecycleBinEnabled(false);
auto key = QSharedPointer<CompositeKey>::create();
key->addKey(QSharedPointer<PasswordKey>::create(reference.password));
// Copy the source root as the root of the export database, memory manage the old root node
auto* targetRoot = sourceRoot->clone(Entry::CloneNoFlags, Group::CloneNoFlags);
@ -86,7 +81,10 @@ namespace
}
}
auto key = QSharedPointer<CompositeKey>::create();
key->addKey(QSharedPointer<PasswordKey>::create(reference.password));
targetDb->setKey(key);
auto* obsoleteRoot = targetDb->rootGroup();
targetDb->setRootGroup(targetRoot);
delete obsoleteRoot;
@ -108,124 +106,106 @@ namespace
return targetDb;
}
ShareObserver::Result
intoSignedContainer(const QString& resolvedPath, const KeeShareSettings::Reference& reference, Database* targetDb)
bool writeZipFile(void* zf, const QString& fileName, const QByteArray& data)
{
#if !defined(WITH_XC_KEESHARE_SECURE)
Q_UNUSED(targetDb);
Q_UNUSED(resolvedPath);
return {reference.path,
ShareObserver::Result::Warning,
ShareExport::tr("Overwriting signed share container is not supported - export prevented")};
#else
QByteArray bytes;
{
QBuffer buffer(&bytes);
buffer.open(QIODevice::WriteOnly);
KeePass2Writer writer;
writer.writeDatabase(&buffer, targetDb);
if (writer.hasError()) {
qWarning("Serializing export dabase failed: %s.", writer.errorString().toLatin1().data());
return {reference.path, ShareObserver::Result::Error, writer.errorString()};
}
}
const auto own = KeeShare::own();
QuaZip zip(resolvedPath);
zip.setFileNameCodec("UTF-8");
const bool zipOpened = zip.open(QuaZip::mdCreate);
if (!zipOpened) {
::qWarning("Opening export file failed: %d", zip.getZipError());
return {reference.path,
ShareObserver::Result::Error,
ShareExport::tr("Could not write export container (%1)").arg(zip.getZipError())};
}
{
QuaZipFile file(&zip);
const auto signatureOpened = file.open(QIODevice::WriteOnly, QuaZipNewInfo(KeeShare::signatureFileName()));
if (!signatureOpened) {
::qWarning("Embedding signature failed: Could not open file to write (%d)", zip.getZipError());
return {reference.path,
ShareObserver::Result::Error,
ShareExport::tr("Could not embed signature: Could not open file to write (%1)")
.arg(file.getZipError())};
}
QTextStream stream(&file);
KeeShareSettings::Sign sign;
// TODO: check for false return
Signature::create(bytes, own.key.key, sign.signature);
sign.certificate = own.certificate;
stream << KeeShareSettings::Sign::serialize(sign);
stream.flush();
if (file.getZipError() != ZIP_OK) {
::qWarning("Embedding signature failed: Could not write file (%d)", zip.getZipError());
return {
reference.path,
ShareObserver::Result::Error,
ShareExport::tr("Could not embed signature: Could not write file (%1)").arg(file.getZipError())};
}
file.close();
}
{
QuaZipFile file(&zip);
const auto dbOpened = file.open(QIODevice::WriteOnly, QuaZipNewInfo(KeeShare::containerFileName()));
if (!dbOpened) {
::qWarning("Embedding database failed: Could not open file to write (%d)", zip.getZipError());
return {reference.path,
ShareObserver::Result::Error,
ShareExport::tr("Could not embed database: Could not open file to write (%1)")
.arg(file.getZipError())};
}
file.write(bytes);
if (file.getZipError() != ZIP_OK) {
::qWarning("Embedding database failed: Could not write file (%d)", zip.getZipError());
return {reference.path,
ShareObserver::Result::Error,
ShareExport::tr("Could not embed database: Could not write file (%1)").arg(file.getZipError())};
}
file.close();
}
zip.close();
return {reference.path};
#endif
zipOpenNewFileInZip64(zf,
fileName.toLatin1().data(),
nullptr,
nullptr,
0,
nullptr,
0,
nullptr,
Z_DEFLATED,
Z_BEST_COMPRESSION,
1);
int pos = 0;
do {
auto len = qMin(data.size() - pos, 8192);
zipWriteInFileInZip(zf, data.data() + pos, len);
pos += len;
} while (pos < data.size());
zipCloseFileInZip(zf);
return true;
}
ShareObserver::Result
intoUnsignedContainer(const QString& resolvedPath, const KeeShareSettings::Reference& reference, Database* targetDb)
bool signData(const QByteArray& data, const KeeShareSettings::Key& key, QString& signature)
{
#if !defined(WITH_XC_KEESHARE_INSECURE)
Q_UNUSED(targetDb);
Q_UNUSED(resolvedPath);
return {reference.path,
ShareObserver::Result::Warning,
ShareExport::tr("Overwriting unsigned share container is not supported - export prevented")};
#else
QFile file(resolvedPath);
const bool fileOpened = file.open(QIODevice::WriteOnly);
if (!fileOpened) {
::qWarning("Opening export file failed");
return {reference.path, ShareObserver::Result::Error, ShareExport::tr("Could not write export container")};
}
KeePass2Writer writer;
writer.writeDatabase(&file, targetDb);
if (writer.hasError()) {
qWarning("Exporting dabase failed: %s.", writer.errorString().toLatin1().data());
return {reference.path, ShareObserver::Result::Error, writer.errorString()};
}
file.close();
#endif
return {reference.path};
}
if (key.key->algo_name() == "RSA") {
try {
Botan::PK_Signer signer(*key.key, "EMSA3(SHA-256)");
signer.update(reinterpret_cast<const uint8_t*>(data.constData()), data.size());
auto s = signer.signature(*randomGen()->getRng());
auto hex = QByteArray(reinterpret_cast<char*>(s.data()), s.size()).toHex();
signature = QString("rsa|%1").arg(QString::fromLatin1(hex));
return true;
} catch (std::exception& e) {
qWarning("KeeShare: Failed to sign data: %s", e.what());
return false;
}
}
qWarning("Unsupported Public/Private key format");
return false;
}
} // namespace
ShareObserver::Result ShareExport::intoContainer(const QString& resolvedPath,
const KeeShareSettings::Reference& reference,
const Group* group)
{
QScopedPointer<Database> targetDb(extractIntoDatabase(reference, group));
const QFileInfo info(resolvedPath);
if (KeeShare::isContainerType(info, KeeShare::signedContainerFileType())) {
return intoSignedContainer(resolvedPath, reference, targetDb.data());
QFile file(resolvedPath);
const bool fileOpened = file.open(QIODevice::WriteOnly);
if (!fileOpened) {
qWarning("Opening export file failed");
return {resolvedPath, ShareObserver::Result::Error, file.errorString()};
}
return intoUnsignedContainer(resolvedPath, reference, targetDb.data());
QScopedPointer<Database> targetDb(extractIntoDatabase(reference, group));
if (resolvedPath.endsWith(".kdbx.share")) {
// Write database to memory and sign it
QByteArray dbData, signatureData;
QBuffer buffer;
buffer.setBuffer(&dbData);
buffer.open(QIODevice::WriteOnly);
KeePass2Writer writer;
if (!writer.writeDatabase(&buffer, targetDb.data())) {
qWarning("Serializing export dabase failed: %s.", writer.errorString().toLatin1().data());
return {reference.path, ShareObserver::Result::Error, writer.errorString()};
}
buffer.close();
// Get Own Certificate for signing
const auto own = KeeShare::own();
Q_ASSERT(!own.isNull());
// Sign the database data
KeeShareSettings::Sign sign;
sign.certificate = own.certificate;
signData(dbData, own.key, sign.signature);
signatureData = KeeShareSettings::Sign::serialize(sign).toLatin1();
auto zf = zipOpen64(resolvedPath.toLatin1().data(), 0);
if (!zf) {
return {reference.path, ShareObserver::Result::Error, ShareExport::tr("Could not write export container.")};
}
writeZipFile(zf, KeeShare::signatureFileName().toLatin1().data(), signatureData);
writeZipFile(zf, KeeShare::containerFileName().toLatin1().data(), dbData);
zipClose(zf, nullptr);
} else {
QString error;
if (!targetDb->saveAs(resolvedPath, Database::Atomic, {}, &error)) {
qWarning("Exporting dabase failed: %s.", error.toLatin1().data());
return {resolvedPath, ShareObserver::Result::Error, error};
}
}
return {resolvedPath};
}

View File

@ -15,324 +15,87 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ShareImport.h"
#include "config-keepassx.h"
#include "core/Merger.h"
#include "format/KeePass2Reader.h"
#include "keeshare/KeeShare.h"
#include "keeshare/Signature.h"
#include "keys/PasswordKey.h"
#include <QBuffer>
#include <QMessageBox>
#include <QPushButton>
#include <QTextStream>
#if defined(WITH_XC_KEESHARE_SECURE)
#include <botan/pk_keys.h>
#include <quazipfile.h>
#endif
#include <unzip.h>
namespace
{
enum Trust
QByteArray readZipFile(void* uf)
{
Invalid,
Own,
UntrustedForever,
UntrustedOnce,
TrustedOnce,
TrustedForever,
};
QPair<Trust, KeeShareSettings::Certificate>
check(QByteArray& data,
const KeeShareSettings::Reference& reference,
const KeeShareSettings::Certificate& ownCertificate,
const QList<KeeShareSettings::ScopedCertificate>& knownCertificates,
const KeeShareSettings::Sign& sign)
{
KeeShareSettings::Certificate certificate;
if (!sign.signature.isEmpty()) {
certificate = sign.certificate;
if (!Signature::verify(data, sign.certificate.key, sign.signature)) {
qCritical("Invalid signature for shared container %s.", qPrintable(reference.path));
return {Invalid, KeeShareSettings::Certificate()};
QByteArray data;
int bytes, bytesRead = 0;
unzOpenCurrentFile(uf);
do {
data.resize(data.size() + 8192);
bytes = unzReadCurrentFile(uf, data.data() + bytesRead, 8192);
if (bytes > 0) {
bytesRead += bytes;
}
// Automatically trust your own certificate
if (ownCertificate == sign.certificate) {
return {Own, ownCertificate};
}
}
for (const auto& scopedCertificate : knownCertificates) {
if (scopedCertificate.certificate == certificate && scopedCertificate.path == reference.path) {
if (scopedCertificate.trust == KeeShareSettings::Trust::Trusted) {
return {TrustedForever, certificate};
} else if (scopedCertificate.trust == KeeShareSettings::Trust::Untrusted) {
return {UntrustedForever, certificate};
}
// Default to ask
break;
}
}
// Ask the user if they want to trust the certificate
QMessageBox warning;
warning.setWindowTitle(ShareImport::tr("KeeShare Import"));
if (sign.signature.isEmpty()) {
warning.setIcon(QMessageBox::Warning);
warning.setText(ShareImport::tr("The source of the shared container cannot be verified because it is not "
"signed. Do you really want to import from %1?")
.arg(reference.path));
} else {
warning.setIcon(QMessageBox::Question);
warning.setText(ShareImport::tr("Do you want to trust %1 with certificate fingerprint:\n%2\n%3")
.arg(reference.path)
.arg(certificate.signer)
.arg(certificate.fingerprint()));
}
auto untrustedOnce = warning.addButton(ShareImport::tr("Not this time"), QMessageBox::ButtonRole::NoRole);
auto untrustedForever = warning.addButton(ShareImport::tr("Never"), QMessageBox::ButtonRole::NoRole);
auto trustedForever = warning.addButton(ShareImport::tr("Always"), QMessageBox::ButtonRole::YesRole);
auto trustedOnce = warning.addButton(ShareImport::tr("Just this time"), QMessageBox::ButtonRole::YesRole);
warning.setDefaultButton(untrustedOnce);
warning.exec();
if (warning.clickedButton() == trustedForever) {
return {TrustedForever, certificate};
}
if (warning.clickedButton() == trustedOnce) {
return {TrustedOnce, certificate};
}
if (warning.clickedButton() == untrustedOnce) {
return {UntrustedOnce, certificate};
}
if (warning.clickedButton() == untrustedForever) {
return {UntrustedForever, certificate};
}
return {UntrustedOnce, certificate};
} while (bytes > 0);
unzCloseCurrentFile(uf);
data.truncate(bytesRead);
return data;
}
ShareObserver::Result
signedContainerInto(const QString& resolvedPath, const KeeShareSettings::Reference& reference, Group* targetGroup)
{
#if !defined(WITH_XC_KEESHARE_SECURE)
Q_UNUSED(targetGroup);
Q_UNUSED(resolvedPath);
return {reference.path,
ShareObserver::Result::Warning,
ShareImport::tr("Signed share container are not supported - import prevented")};
#else
QuaZip zip(resolvedPath);
if (!zip.open(QuaZip::mdUnzip)) {
qCritical("Unable to open file %s.", qPrintable(reference.path));
return {reference.path, ShareObserver::Result::Error, ShareImport::tr("File is not readable")};
}
const auto expected = QSet<QString>() << KeeShare::signatureFileName() << KeeShare::containerFileName();
const auto files = zip.getFileInfoList();
QSet<QString> actual;
for (const auto& file : files) {
actual << file.name;
}
if (expected != actual) {
qCritical("Invalid sharing container %s.", qPrintable(reference.path));
return {reference.path, ShareObserver::Result::Error, ShareImport::tr("Invalid sharing container")};
}
zip.setCurrentFile(KeeShare::signatureFileName());
QuaZipFile signatureFile(&zip);
signatureFile.open(QuaZipFile::ReadOnly);
QTextStream stream(&signatureFile);
const auto sign = KeeShareSettings::Sign::deserialize(stream.readAll());
signatureFile.close();
zip.setCurrentFile(KeeShare::containerFileName());
QuaZipFile databaseFile(&zip);
databaseFile.open(QuaZipFile::ReadOnly);
auto payload = databaseFile.readAll();
databaseFile.close();
QBuffer buffer(&payload);
buffer.open(QIODevice::ReadOnly);
KeePass2Reader reader;
auto key = QSharedPointer<CompositeKey>::create();
key->addKey(QSharedPointer<PasswordKey>::create(reference.password));
auto sourceDb = QSharedPointer<Database>::create();
if (!reader.readDatabase(&buffer, key, sourceDb.data())) {
qCritical("Error while parsing the database: %s", qPrintable(reader.errorString()));
return {reference.path, ShareObserver::Result::Error, reader.errorString()};
}
auto foreign = KeeShare::foreign();
auto own = KeeShare::own();
auto trust = check(payload, reference, own.certificate, foreign.certificates, sign);
switch (trust.first) {
case Invalid:
qWarning("Prevent untrusted import");
return {reference.path, ShareObserver::Result::Error, ShareImport::tr("Untrusted import prevented")};
case UntrustedForever:
case TrustedForever: {
bool found = false;
const auto trusted =
trust.first == TrustedForever ? KeeShareSettings::Trust::Trusted : KeeShareSettings::Trust::Untrusted;
for (KeeShareSettings::ScopedCertificate& scopedCertificate : foreign.certificates) {
if (scopedCertificate.certificate == trust.second && scopedCertificate.path == reference.path) {
scopedCertificate.certificate.signer = trust.second.signer;
scopedCertificate.path = reference.path;
scopedCertificate.trust = trusted;
found = true;
break;
}
}
if (!found) {
foreign.certificates << KeeShareSettings::ScopedCertificate{reference.path, trust.second, trusted};
}
// update foreign certificates with new settings
KeeShare::setForeign(foreign);
if (trust.first == TrustedForever) {
qDebug("Synchronize %s %s with %s",
qPrintable(reference.path),
qPrintable(targetGroup->name()),
qPrintable(sourceDb->rootGroup()->name()));
Merger merger(sourceDb->rootGroup(), targetGroup);
merger.setForcedMergeMode(Group::Synchronize);
auto changelist = merger.merge();
if (!changelist.isEmpty()) {
return {
reference.path, ShareObserver::Result::Success, ShareImport::tr("Successful signed import")};
}
}
// Silent ignore of untrusted import or unchanging import
return {};
}
case TrustedOnce:
case Own: {
qDebug("Synchronize %s %s with %s",
qPrintable(reference.path),
qPrintable(targetGroup->name()),
qPrintable(sourceDb->rootGroup()->name()));
Merger merger(sourceDb->rootGroup(), targetGroup);
merger.setForcedMergeMode(Group::Synchronize);
auto changelist = merger.merge();
if (!changelist.isEmpty()) {
return {reference.path, ShareObserver::Result::Success, ShareImport::tr("Successful signed import")};
}
return {};
}
default:
qWarning("Prevented untrusted import of signed KeeShare database %s", qPrintable(reference.path));
return {reference.path, ShareObserver::Result::Warning, ShareImport::tr("Untrusted import prevented")};
}
#endif
}
ShareObserver::Result
unsignedContainerInto(const QString& resolvedPath, const KeeShareSettings::Reference& reference, Group* targetGroup)
{
#if !defined(WITH_XC_KEESHARE_INSECURE)
Q_UNUSED(targetGroup);
Q_UNUSED(resolvedPath);
return {reference.path,
ShareObserver::Result::Warning,
ShareImport::tr("Unsigned share container are not supported - import prevented")};
#else
QFile file(resolvedPath);
if (!file.open(QIODevice::ReadOnly)) {
qCritical("Unable to open file %s.", qPrintable(reference.path));
return {reference.path, ShareObserver::Result::Error, ShareImport::tr("File is not readable")};
}
auto payload = file.readAll();
file.close();
QBuffer buffer(&payload);
buffer.open(QIODevice::ReadOnly);
KeePass2Reader reader;
auto key = QSharedPointer<CompositeKey>::create();
key->addKey(QSharedPointer<PasswordKey>::create(reference.password));
auto sourceDb = QSharedPointer<Database>::create();
if (!reader.readDatabase(&buffer, key, sourceDb.data())) {
qCritical("Error while parsing the database: %s", qPrintable(reader.errorString()));
return {reference.path, ShareObserver::Result::Error, reader.errorString()};
}
auto foreign = KeeShare::foreign();
const auto own = KeeShare::own();
const auto sign = KeeShareSettings::Sign(); // invalid sign
auto trust = check(payload, reference, own.certificate, foreign.certificates, sign);
switch (trust.first) {
case UntrustedForever:
case TrustedForever: {
bool found = false;
const auto trusted =
trust.first == TrustedForever ? KeeShareSettings::Trust::Trusted : KeeShareSettings::Trust::Untrusted;
for (KeeShareSettings::ScopedCertificate& scopedCertificate : foreign.certificates) {
if (scopedCertificate.certificate == trust.second && scopedCertificate.path == reference.path) {
scopedCertificate.certificate.signer = trust.second.signer;
scopedCertificate.path = reference.path;
scopedCertificate.trust = trusted;
found = true;
break;
}
}
if (!found) {
foreign.certificates << KeeShareSettings::ScopedCertificate{reference.path, trust.second, trusted};
}
// update foreign certificates with new settings
KeeShare::setForeign(foreign);
if (trust.first == TrustedForever) {
qDebug("Synchronize %s %s with %s",
qPrintable(reference.path),
qPrintable(targetGroup->name()),
qPrintable(sourceDb->rootGroup()->name()));
Merger merger(sourceDb->rootGroup(), targetGroup);
merger.setForcedMergeMode(Group::Synchronize);
auto changelist = merger.merge();
if (!changelist.isEmpty()) {
return {
reference.path, ShareObserver::Result::Success, ShareImport::tr("Successful signed import")};
}
}
return {};
}
case TrustedOnce: {
qDebug("Synchronize %s %s with %s",
qPrintable(reference.path),
qPrintable(targetGroup->name()),
qPrintable(sourceDb->rootGroup()->name()));
Merger merger(sourceDb->rootGroup(), targetGroup);
merger.setForcedMergeMode(Group::Synchronize);
auto changelist = merger.merge();
if (!changelist.isEmpty()) {
return {reference.path, ShareObserver::Result::Success, ShareImport::tr("Successful unsigned import")};
}
return {};
}
default:
qWarning("Prevented untrusted import of unsigned KeeShare database %s", qPrintable(reference.path));
return {reference.path, ShareObserver::Result::Warning, ShareImport::tr("Untrusted import prevented")};
}
#endif
}
} // namespace
ShareObserver::Result ShareImport::containerInto(const QString& resolvedPath,
const KeeShareSettings::Reference& reference,
Group* targetGroup)
{
const QFileInfo info(resolvedPath);
if (!info.exists()) {
qCritical("File %s does not exist.", qPrintable(info.absoluteFilePath()));
return {reference.path, ShareObserver::Result::Warning, tr("File does not exist")};
// TODO: Read signing certificate as well, but don't check validity
QByteArray dbData;
auto uf = unzOpen64(resolvedPath.toLatin1().constData());
if (uf) {
// Open zip share, extract database portion, ignore signature file
char zipFileName[256];
auto err = unzGoToFirstFile(uf);
while (err == UNZ_OK) {
unzGetCurrentFileInfo64(uf, nullptr, zipFileName, sizeof(zipFileName), nullptr, 0, nullptr, 0);
if (QString(zipFileName).compare(KeeShare::containerFileName()) == 0) {
dbData = readZipFile(uf);
}
err = unzGoToNextFile(uf);
}
unzClose(uf);
} else {
// Open KDBX file directly
QFile file(resolvedPath);
if (!file.open(QIODevice::ReadOnly)) {
qCritical("Unable to open file %s.", qPrintable(reference.path));
return {reference.path, ShareObserver::Result::Error, file.errorString()};
}
dbData = file.readAll();
}
if (KeeShare::isContainerType(info, KeeShare::signedContainerFileType())) {
return signedContainerInto(resolvedPath, reference, targetGroup);
QBuffer buffer(&dbData);
buffer.open(QIODevice::ReadOnly);
KeePass2Reader reader;
auto key = QSharedPointer<CompositeKey>::create();
key->addKey(QSharedPointer<PasswordKey>::create(reference.password));
auto sourceDb = QSharedPointer<Database>::create();
if (!reader.readDatabase(&buffer, key, sourceDb.data())) {
qCritical("Error while parsing the database: %s", qPrintable(reader.errorString()));
return {reference.path, ShareObserver::Result::Error, reader.errorString()};
}
return unsignedContainerInto(resolvedPath, reference, targetGroup);
qDebug("Synchronize %s %s with %s",
qPrintable(reference.path),
qPrintable(targetGroup->name()),
qPrintable(sourceDb->rootGroup()->name()));
Merger merger(sourceDb->rootGroup(), targetGroup);
merger.setForcedMergeMode(Group::Synchronize);
auto changelist = merger.merge();
if (!changelist.isEmpty()) {
return {reference.path, ShareObserver::Result::Success, ShareImport::tr("Successful import")};
}
return {};
}

View File

@ -278,13 +278,14 @@ QList<ShareObserver::Result> ShareObserver::exportShares()
}
for (auto it = references.cbegin(); it != references.cend(); ++it) {
const auto& reference = it.value().first();
auto reference = it.value().first();
const QString resolvedPath = resolvePath(reference.config.path, m_db);
auto watcher = m_fileWatchers.value(resolvedPath);
if (watcher) {
watcher->stop();
}
// TODO: save new path into group settings if not saving to signed container anymore
results << ShareExport::intoContainer(resolvedPath, reference.config, reference.group);
if (watcher) {

View File

@ -1,70 +0,0 @@
/*
* 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 "Signature.h"
#include "crypto/Random.h"
#include <botan/pubkey.h>
bool Signature::create(const QByteArray& data, QSharedPointer<Botan::Private_Key> key, QString& signature)
{
// TODO HNH: currently we publish the signature in our own non-standard format - it would
// be better to use a standard format (like ASN1 - but this would be more easy
// when we integrate a proper library)
// Even more, we could publish standard self signed certificates with the container
// instead of the custom certificates
if (key->algo_name() == "RSA") {
try {
Botan::PK_Signer signer(*key, "EMSA3(SHA-256)");
signer.update(reinterpret_cast<const uint8_t*>(data.constData()), data.size());
auto s = signer.signature(*randomGen()->getRng());
auto hex = QByteArray(reinterpret_cast<char*>(s.data()), s.size()).toHex();
signature = QString("rsa|%1").arg(QString::fromLatin1(hex));
return true;
} catch (std::exception& e) {
qWarning("KeeShare: Failed to sign data: %s", e.what());
return false;
}
}
qWarning("Unsupported Public/Private key format");
return false;
}
bool Signature::verify(const QByteArray& data, QSharedPointer<Botan::Public_Key> key, const QString& signature)
{
if (key && key->algo_name() == "RSA") {
QRegExp extractor("rsa\\|([a-f0-9]+)", Qt::CaseInsensitive);
if (!extractor.exactMatch(signature) || extractor.captureCount() != 1) {
qWarning("Could not unpack signature parts");
return false;
}
const QByteArray sig_s = QByteArray::fromHex(extractor.cap(1).toLatin1());
try {
Botan::PK_Verifier verifier(*key, "EMSA3(SHA-256)");
verifier.update(reinterpret_cast<const uint8_t*>(data.constData()), data.size());
return verifier.check_signature(reinterpret_cast<const uint8_t*>(sig_s.constData()), sig_s.size());
} catch (std::exception& e) {
qWarning("KeeShare: Failed to verify signature: %s", e.what());
return false;
}
}
qWarning("Unsupported Public/Private key format");
return false;
}

View File

@ -1,31 +0,0 @@
/*
* 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_SIGNATURE_H
#define KEEPASSXC_SIGNATURE_H
#include <QSharedPointer>
#include <QString>
#include <botan/pubkey.h>
namespace Signature
{
bool create(const QByteArray& data, QSharedPointer<Botan::Private_Key> key, QString& signature);
bool verify(const QByteArray& data, QSharedPointer<Botan::Public_Key> key, const QString& signature);
}; // namespace Signature
#endif // KEEPASSXC_SIGNATURE_H

View File

@ -103,13 +103,8 @@ void EditGroupWidgetKeeShare::updateSharingState()
return;
}
auto supportedExtensions = QStringList();
#if defined(WITH_XC_KEESHARE_INSECURE)
supportedExtensions << KeeShare::unsignedContainerFileType();
#endif
#if defined(WITH_XC_KEESHARE_SECURE)
supportedExtensions << KeeShare::signedContainerFileType();
#endif
QStringList supportedExtensions;
supportedExtensions << KeeShare::unsignedContainerFileType() << KeeShare::signedContainerFileType();
// Custom message for active KeeShare reference
const auto reference = KeeShare::referenceOf(m_temporaryGroup);
@ -225,26 +220,15 @@ void EditGroupWidgetKeeShare::launchPathSelectionDialog()
return;
}
auto reference = KeeShare::referenceOf(m_temporaryGroup);
QString defaultFiletype = "";
auto supportedExtensions = QStringList();
auto unsupportedExtensions = QStringList();
auto knownFilters = QStringList() << QString("%1 (*)").arg("All files");
#if defined(WITH_XC_KEESHARE_INSECURE)
defaultFiletype = KeeShare::unsignedContainerFileType();
supportedExtensions << KeeShare::unsignedContainerFileType();
knownFilters.prepend(
QString("%1 (*.%2)").arg(tr("KeeShare unsigned container"), KeeShare::unsignedContainerFileType()));
#else
unsupportedExtensions << KeeShare::unsignedContainerFileType();
#endif
#if defined(WITH_XC_KEESHARE_SECURE)
defaultFiletype = KeeShare::signedContainerFileType();
supportedExtensions << KeeShare::signedContainerFileType();
knownFilters.prepend(
QString("%1 (*.%2)").arg(tr("KeeShare signed container"), KeeShare::signedContainerFileType()));
#else
unsupportedExtensions << KeeShare::signedContainerFileType();
#endif
QString defaultFiletype = KeeShare::unsignedContainerFileType();
QStringList supportedExtensions;
supportedExtensions << KeeShare::unsignedContainerFileType() << KeeShare::signedContainerFileType();
QStringList knownFilters;
knownFilters << QString("%1 (*.%2)").arg(tr("KeeShare container"), KeeShare::unsignedContainerFileType());
knownFilters << QString("%1 (*.%2)").arg(tr("KeeShare signed container"), KeeShare::signedContainerFileType());
knownFilters << QString("%1 (*)").arg("All files");
const auto filters = knownFilters.join(";;");
auto defaultDirPath = FileDialog::getLastDir("keeshare");
@ -269,7 +253,7 @@ void EditGroupWidgetKeeShare::launchPathSelectionDialog()
return;
}
bool validFilename = false;
for (const auto& extension : supportedExtensions + unsupportedExtensions) {
for (const auto& extension : supportedExtensions) {
if (filename.endsWith(extension, Qt::CaseInsensitive)) {
validFilename = true;
break;

View File

@ -125,11 +125,6 @@ add_unit_test(NAME testcryptohash SOURCES TestCryptoHash.cpp
add_unit_test(NAME testsymmetriccipher SOURCES TestSymmetricCipher.cpp
LIBS ${TEST_LIBRARIES})
if(WITH_XC_KEESHARE)
add_unit_test(NAME testsignature SOURCES TestSignature.cpp
LIBS ${TEST_LIBRARIES})
endif()
add_unit_test(NAME testhashedblockstream SOURCES TestHashedBlockStream.cpp
LIBS testsupport ${TEST_LIBRARIES})

View File

@ -31,9 +31,6 @@ QTEST_GUILESS_MAIN(TestSharing)
Q_DECLARE_METATYPE(KeeShareSettings::Type)
Q_DECLARE_METATYPE(KeeShareSettings::Key)
Q_DECLARE_METATYPE(KeeShareSettings::Certificate)
Q_DECLARE_METATYPE(KeeShareSettings::Trust)
Q_DECLARE_METATYPE(KeeShareSettings::ScopedCertificate)
Q_DECLARE_METATYPE(QList<KeeShareSettings::ScopedCertificate>)
void TestSharing::initTestCase()
{
@ -65,55 +62,12 @@ void TestSharing::testNullObjects()
const auto xmlActive = KeeShareSettings::Active::deserialize(empty);
QVERIFY(xmlActive.isNull());
const auto foreign = KeeShareSettings::Foreign();
QVERIFY(foreign.certificates.isEmpty());
const auto xmlForeign = KeeShareSettings::Foreign::deserialize(empty);
QVERIFY(xmlForeign.certificates.isEmpty());
const auto reference = KeeShareSettings::Reference();
QVERIFY(reference.isNull());
const auto xmlReference = KeeShareSettings::Reference::deserialize(empty);
QVERIFY(xmlReference.isNull());
}
void TestSharing::testCertificateSerialization()
{
QFETCH(KeeShareSettings::Trust, trusted);
auto key = stubkey();
KeeShareSettings::ScopedCertificate original;
original.path = "/path";
original.certificate = KeeShareSettings::Certificate{key, "Some <!> &#_\"\" weird string"};
original.trust = trusted;
QString buffer;
QXmlStreamWriter writer(&buffer);
writer.writeStartDocument();
writer.writeStartElement("Certificate");
KeeShareSettings::ScopedCertificate::serialize(writer, original);
writer.writeEndElement();
writer.writeEndDocument();
QXmlStreamReader reader(buffer);
reader.readNextStartElement();
QVERIFY(reader.name() == "Certificate");
KeeShareSettings::ScopedCertificate restored = KeeShareSettings::ScopedCertificate::deserialize(reader);
QCOMPARE(restored.certificate.key->private_key_bits(), original.certificate.key->private_key_bits());
QCOMPARE(restored.certificate.signer, original.certificate.signer);
QCOMPARE(restored.trust, original.trust);
QCOMPARE(restored.path, original.path);
QCOMPARE(restored.certificate.key->public_key_bits(), key->public_key_bits());
}
void TestSharing::testCertificateSerialization_data()
{
QTest::addColumn<KeeShareSettings::Trust>("trusted");
QTest::newRow("Ask") << KeeShareSettings::Trust::Ask;
QTest::newRow("Trusted") << KeeShareSettings::Trust::Trusted;
QTest::newRow("Untrusted") << KeeShareSettings::Trust::Untrusted;
}
void TestSharing::testKeySerialization()
{
auto key = stubkey();
@ -177,16 +131,13 @@ void TestSharing::testSettingsSerialization()
QFETCH(bool, exporting);
QFETCH(KeeShareSettings::Certificate, ownCertificate);
QFETCH(KeeShareSettings::Key, ownKey);
QFETCH(QList<KeeShareSettings::ScopedCertificate>, foreignCertificates);
KeeShareSettings::Own originalOwn;
KeeShareSettings::Foreign originalForeign;
KeeShareSettings::Active originalActive;
originalActive.in = importing;
originalActive.out = exporting;
originalOwn.certificate = ownCertificate;
originalOwn.key = ownKey;
originalForeign.certificates = foreignCertificates;
const QString serializedActive = KeeShareSettings::Active::serialize(originalActive);
KeeShareSettings::Active restoredActive = KeeShareSettings::Active::deserialize(serializedActive);
@ -194,9 +145,6 @@ void TestSharing::testSettingsSerialization()
const QString serializedOwn = KeeShareSettings::Own::serialize(originalOwn);
KeeShareSettings::Own restoredOwn = KeeShareSettings::Own::deserialize(serializedOwn);
const QString serializedForeign = KeeShareSettings::Foreign::serialize(originalForeign);
KeeShareSettings::Foreign restoredForeign = KeeShareSettings::Foreign::deserialize(serializedForeign);
QCOMPARE(restoredActive.in, importing);
QCOMPARE(restoredActive.out, exporting);
if (ownCertificate.key) {
@ -205,44 +153,29 @@ void TestSharing::testSettingsSerialization()
if (ownKey.key) {
QCOMPARE(restoredOwn.key, ownKey);
}
QCOMPARE(restoredForeign.certificates.count(), foreignCertificates.count());
for (int i = 0; i < foreignCertificates.count(); ++i) {
QCOMPARE(restoredForeign.certificates[i].certificate, foreignCertificates[i].certificate);
}
}
void TestSharing::testSettingsSerialization_data()
{
auto sshKey0 = stubkey(0);
KeeShareSettings::ScopedCertificate certificate0;
certificate0.path = "/path/0";
certificate0.certificate = KeeShareSettings::Certificate{sshKey0, "Some <!> &#_\"\" weird string"};
certificate0.trust = KeeShareSettings::Trust::Trusted;
KeeShareSettings::Certificate certificate0;
certificate0.key = sshKey0;
certificate0.signer = "signer";
KeeShareSettings::Key key0;
key0.key = sshKey0;
auto sshKey1 = stubkey(1);
KeeShareSettings::ScopedCertificate certificate1;
certificate1.path = "/path/1";
certificate1.certificate = KeeShareSettings::Certificate{sshKey1, "Another "};
certificate1.trust = KeeShareSettings::Trust::Untrusted;
QTest::addColumn<bool>("importing");
QTest::addColumn<bool>("exporting");
QTest::addColumn<KeeShareSettings::Certificate>("ownCertificate");
QTest::addColumn<KeeShareSettings::Key>("ownKey");
QTest::addColumn<QList<KeeShareSettings::ScopedCertificate>>("foreignCertificates");
QTest::newRow("1") << false << false << KeeShareSettings::Certificate() << KeeShareSettings::Key()
<< QList<KeeShareSettings::ScopedCertificate>();
QTest::newRow("2") << true << false << KeeShareSettings::Certificate() << KeeShareSettings::Key()
<< QList<KeeShareSettings::ScopedCertificate>();
QTest::newRow("3") << true << true << KeeShareSettings::Certificate() << KeeShareSettings::Key()
<< QList<KeeShareSettings::ScopedCertificate>({certificate0, certificate1});
QTest::newRow("4") << false << true << certificate0.certificate << key0
<< QList<KeeShareSettings::ScopedCertificate>();
QTest::newRow("5") << false << false << certificate0.certificate << key0
<< QList<KeeShareSettings::ScopedCertificate>({certificate1});
QTest::newRow("1") << false << false << KeeShareSettings::Certificate() << KeeShareSettings::Key();
QTest::newRow("2") << true << false << KeeShareSettings::Certificate() << KeeShareSettings::Key();
QTest::newRow("3") << true << true << KeeShareSettings::Certificate() << KeeShareSettings::Key();
QTest::newRow("4") << false << true << certificate0 << key0;
QTest::newRow("5") << false << false << certificate0 << key0;
}
const QSharedPointer<Botan::RSA_PrivateKey> TestSharing::stubkey(int index)

View File

@ -31,8 +31,6 @@ class TestSharing : public QObject
private slots:
void initTestCase();
void testNullObjects();
void testCertificateSerialization();
void testCertificateSerialization_data();
void testKeySerialization();
void testReferenceSerialization();
void testReferenceSerialization_data();

View File

@ -1,214 +0,0 @@
/*
* 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
* 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 "TestSignature.h"
#include <QTest>
#include "crypto/Crypto.h"
#include "crypto/Random.h"
#include "keeshare/Signature.h"
#include <botan/pem.h>
#include <botan/rsa.h>
QTEST_GUILESS_MAIN(TestSignature)
static const char* rsa_2_private = "-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEowIBAAKCAQEAwGdladnqFfcDy02Gubx4sdBT8NYEg2YKXfcKLSwca5gV4X7I\n"
"z/+QR51LAfPCkj+QjWpj3DD1/6P7s6jOJ4BNd6CSDukv18DOsIsFn2D+zLmVoir2\n"
"lki3sTsmiEz65KvHE8EnQ8IzZCqZDC40tZOcz2bnkZrmcsEibKoxYsmQJk95NwdR\n"
"teFymp1qH3zq85xdNWw2u6c5CKzLgI5BjInttO98iSxL0KuY/JmzMx0gTbRiqc0x\n"
"22EODtdVsBoNL/pt8v6Q+WLpRg4/Yq7YurAngxk4h38NWvufj2vJvbcRqX4cupcu\n"
"92T9SWwSwZmd4Xy3bt+AUlq4XRMa1MlKfPvXmwIDAQABAoIBAGbWnRD/xaup1OBU\n"
"dr9N6qD3/fXLHqxw3PeudEUCv8oOhxt43bK3IZH1k8LeXFA5I3VCuU9W6BWUu5Mi\n"
"ldXtMPrQ22CW6NiEGLWqCP5QJMCeLUl5d0WKZoyXVhgiNTQGUKjRY8BGy5stXZJy\n"
"HAA1fuooUXu09Jm/ezvjl/P6Uk722nZns4g6cc8aUSQDSVoeuCvwDaix5o4Z4RGY\n"
"4biIKGj5qYxoe+rbgYH/2zlEcAiSJIuqjYY+Xk4IdB89DYZBYnO/xPkRaDeiY2xl\n"
"QM7Inr7PQC8PWJc9zYYvlGnnmIRCkO15mWau70N1Y1rUAsyW61g2GyFhdsIIODH/\n"
"878Kc9ECgYEA+2JaUqRWr6dqE+uVPpGkbGiAaRQ79olTcRmxXCnM+Y3c88z9G7kC\n"
"2S5UKPRDl7EzwmMJqqb8BZbdSWoAxO4++F6ylSz7TqowPw+13Wxwm3wApvr2Q1Mo\n"
"rkq4ltgyHMR+iXvKqOYa2GqZNmRwh7JGLIJ7Y0Z77nwBkkgDc/3ey8MCgYEAw+/N\n"
"fxv2t+r6VKxEtjdy3sfn8LLjiWqghPngJzcYH9NdB8fmMN1WHqX075hbKjI9TyJw\n"
"77p8onjZI0opLexHHUmepEa6Ijo1zynJJ7XPXnyruiTXXqz49io6lFOLcXi/i+DZ\n"
"B2vQcMGWA4qwJxz7KA6EZ/HimjuysV1guvlKf0kCgYA6+JGTvXWQc0eRMLysFuJp\n"
"hAJLpDGE3iYy7AINSskI6dyhXL8rl7UxWYroqJSKq0knGrCT1eRdM0zqAfH4QKOJ\n"
"BD4EfK7ff1EeGgNh1CR+dRJ6GXlXxdRPPrwattDaqsW8Xsvl30UA69DRT7KOQqXv\n"
"nxRu74P3KCP+OuKEfVOcnQKBgQC+/2r1Zj/5huBhW9BbQ/ABBSOuueMeGEfDeIUu\n"
"FQG6PGKqbA2TQp9pnuMGECGGH5UuJ+epeMN36Y/ZW7iKoJGuFg7EGoHlTZMYj6Yb\n"
"xJoRhDwuZy1eiATkicOyxUHf6hHme9dz6YA1+i+O4knWxuR5ZrVhUiRPrrQBO4JI\n"
"oSwiqQKBgHblgOVfOJrG3HDg6bo+qmxQsGFRCD0mehsg9YpokuZVX0UJtGx/RJHU\n"
"vIBL00An6YcNfPTSlNJeG83APtk/tdgsXvQd3pmIkeY78x6xWKSieZXv4lyDv7lX\n"
"r28lCTj2Ez2gEzEohZgf4V1uzBvTdJefarpQ00ep34UZ9FsNfUwD\n"
"-----END RSA PRIVATE KEY-----\n";
static const char* rsa_2_public = "-----BEGIN RSA PUBLIC KEY-----\n"
"MIIBCgKCAQEAwGdladnqFfcDy02Gubx4sdBT8NYEg2YKXfcKLSwca5gV4X7Iz/+Q\n"
"R51LAfPCkj+QjWpj3DD1/6P7s6jOJ4BNd6CSDukv18DOsIsFn2D+zLmVoir2lki3\n"
"sTsmiEz65KvHE8EnQ8IzZCqZDC40tZOcz2bnkZrmcsEibKoxYsmQJk95NwdRteFy\n"
"mp1qH3zq85xdNWw2u6c5CKzLgI5BjInttO98iSxL0KuY/JmzMx0gTbRiqc0x22EO\n"
"DtdVsBoNL/pt8v6Q+WLpRg4/Yq7YurAngxk4h38NWvufj2vJvbcRqX4cupcu92T9\n"
"SWwSwZmd4Xy3bt+AUlq4XRMa1MlKfPvXmwIDAQAB\n"
"-----END RSA PUBLIC KEY-----\n";
static const char* rsa_2_sign = "4fda1b39f93f174cdc79ac2bd6118155359830c90e2c39b60a1a548852f2c87a"
"cd61b2a378259a38befad35dbf208a2c1332ab74faf2cee2ff2e8be49c4c5f41"
"dc10e5a5fafb53d3c54e2b7640d7bfee6bb0f24c5a1fb934150a144c2b465fe4"
"8a1579e666a097fb1609ae9abc5760f6e6d6e73acb610fb11dd1c409ca284a72"
"0be64dd56a28ab257e8721f5bade58816382581ac08d932098dd200d836fe897"
"f78a5f02095ac3b21cca2a47b2afd282ce075c6450cba8c85b08b58c5bacb75d"
"e1a73bdec4321193d4a3ce653d8e3aa8a4f2beac6a44497328f8855f7e28e15d"
"f63b21f8bc7204bf6e202c9cb08be050379be5ad88d8e695a38440a50e75dfdf";
static const char* rsa_1_private = "-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEpAIBAAKCAQEAsCHtJicDPWnvHSIKbnTZaJkIB9vgE0pmLdK580JUqBuonVbB\n"
"y1QTy0ZQ7/TtqvLPgwPK88TR46OLO/QGCzo2+XxgJ85uy0xfuyUYRmSuw0drsErN\n"
"mH8vU91lSBxsGDp9LtBbgHKoR23vMWZ34IxFRc55XphrIH48ijsMaL6bXBwF/3tD\n"
"9T3lm2MpP1huyVNnIY9+GRRWCy4f9LMj/UGu/n4RtwwfpOZBBRwYkq5QkzA9lPm/\n"
"VzF3MP1rKTMkvAw+Nfb383mkmc6MRnsa6uh6iDa9aVB7naegM13UJQX/PY1Ks6pO\n"
"XDpy/MQ7iCh+HmYNq5dRmARyaNl9xIXJNhz1cQIDAQABAoIBAQCnEUc1LUQxeM5K\n"
"wANNCqE+SgoIClPdeHC7fmrLh1ttqe6ib6ybBUFRS31yXs0hnfefunVEDKlaV8K2\n"
"N52UAMAsngFHQNRvGh6kEWeZPd9Xc+N98TZbNCjcT+DGKc+Om8wqH5DrodZlCq4c\n"
"GaoT4HnE4TjWtZTH2XXrWF9I66PKFWf070R44nvyVcvaZi4pC2YmURRPuGF6K1iK\n"
"dH8zM6HHG1UGu2W6hLNn+K01IulG0Lb8eWNaNYMmtQWaxyp7I2IWkkecUs3nCuiR\n"
"byFOoomCjdh8r9yZFvwxjGUhgtkALN9GCU0Mwve+s11IB2gevruN+q9/Qejbyfdm\n"
"IlgLAeTRAoGBANRcVzW9CYeobCf+U9hKJFEOur8XO+J2mTMaELA0EjWpTJFAeIT7\n"
"KeRpCRG4/vOSklxxRF6vP1EACA4Z+5BlN+FTipHHs+bSEgqkPZiiANDH7Zot5Iqv\n"
"1q0fRyldNRZNZK7DWp08BPNVWGA/EnEuKJiURxnxBaxNXbUyMCdjxvMvAoGBANRT\n"
"utbrqS/bAa/DcHKn3V6DRqBl3TDOfvCNjiKC84a67F2uXgzLIdMktr4d1NyCZVJd\n"
"7/zVgWORLIdg1eAi6rYGoOvNV39wwga7CF+m9sBY0wAaKYCELe6L26r4aQHVCX6n\n"
"rnIgUv+4o4itmU2iP0r3wlmDC9pDRQP82vfvQPlfAoGASwhleANW/quvq2HdViq8\n"
"Mje2HBalfhrRfpDTHK8JUBSFjTzuWG42GxJRtgVbb8x2ElujAKGDCaetMO5VSGu7\n"
"Fs5hw6iAFCpdXY0yhl+XUi2R8kwM2EPQ4lKO3jqkq0ClNmqn9a5jQWcCVt9yMLNS\n"
"fLbHeI8EpiCf34ngIcrLXNkCgYEAzlcEZuKkC46xB+dNew8pMTUwSKZVm53BfPKD\n"
"44QRN6imFbBjU9mAaJnwQbfp6dWKs834cGPolyM4++MeVfB42iZ88ksesgmZdUMD\n"
"szkl6O0pOJs0I+HQZVdjRbadDZvD22MHQ3+oST1dJ3FVXz3Cdo9qPuT8esMO6f4r\n"
"qfDH2s8CgYAXC/lWWHQ//PGP0pH4oiEXisx1K0X1u0xMGgrChxBRGRiKZUwNMIvJ\n"
"TqUu7IKizK19cLHF/NBvxHYHFw+m7puNjn6T1RtRCUjRZT7Dx1VHfVosL9ih5DA8\n"
"tpbZA5KGKcvHtB5DDgT0MHwzBZnb4Q//Rhovzn+HXZPsJTTgHHy3NQ==\n"
"-----END RSA PRIVATE KEY-----\n";
static const char* rsa_1_public = "-----BEGIN RSA PUBLIC KEY-----\n"
"MIIBCgKCAQEAsCHtJicDPWnvHSIKbnTZaJkIB9vgE0pmLdK580JUqBuonVbBy1QT\n"
"y0ZQ7/TtqvLPgwPK88TR46OLO/QGCzo2+XxgJ85uy0xfuyUYRmSuw0drsErNmH8v\n"
"U91lSBxsGDp9LtBbgHKoR23vMWZ34IxFRc55XphrIH48ijsMaL6bXBwF/3tD9T3l\n"
"m2MpP1huyVNnIY9+GRRWCy4f9LMj/UGu/n4RtwwfpOZBBRwYkq5QkzA9lPm/VzF3\n"
"MP1rKTMkvAw+Nfb383mkmc6MRnsa6uh6iDa9aVB7naegM13UJQX/PY1Ks6pOXDpy\n"
"/MQ7iCh+HmYNq5dRmARyaNl9xIXJNhz1cQIDAQAB\n"
"-----END RSA PUBLIC KEY-----\n";
static QByteArray data("Some trivial test with a longer .... ................................. longer text");
QSharedPointer<Botan::Private_Key> loadPrivateKey(const QString& pem)
{
try {
std::string label;
auto der = Botan::PEM_Code::decode(pem.toStdString(), label);
auto key = new Botan::RSA_PrivateKey(
Botan::AlgorithmIdentifier("RSA", Botan::AlgorithmIdentifier::USE_NULL_PARAM), der);
return QSharedPointer<Botan::Private_Key>(key);
} catch (std::exception& e) {
qWarning("Failed to load key: %s", e.what());
return {};
}
}
QSharedPointer<Botan::Public_Key> loadPublicKey(const QString& pem)
{
try {
std::string label;
auto der = Botan::PEM_Code::decode(pem.toStdString(), label);
auto key =
new Botan::RSA_PublicKey(Botan::AlgorithmIdentifier("RSA", Botan::AlgorithmIdentifier::USE_NULL_PARAM),
std::vector<uint8_t>(der.begin(), der.end()));
return QSharedPointer<Botan::Public_Key>(key);
} catch (std::exception& e) {
qWarning("Failed to load key: %s", e.what());
return {};
}
}
void TestSignature::initTestCase()
{
QVERIFY(Crypto::init());
}
void TestSignature::testSigningOpenSSH_RSA_PrivateOnly()
{
auto rsaKey = loadPrivateKey(rsa_2_private);
QVERIFY(rsaKey);
QString sign;
Signature::create(data, rsaKey, sign);
QCOMPARE(sign, QString("rsa|%1").arg(QString::fromLatin1(rsa_2_sign)));
const bool verified = Signature::verify(data, rsaKey, sign);
QCOMPARE(verified, true);
}
void TestSignature::testSigningOpenSSH_RSA()
{
auto privateKey = loadPrivateKey(rsa_2_private);
QVERIFY(privateKey);
QString sign;
Signature::create(data, privateKey, sign);
QVERIFY(!sign.isEmpty());
auto publicKey = loadPublicKey(rsa_2_public);
QVERIFY(publicKey);
const bool verified = Signature::verify(data, publicKey, sign);
QCOMPARE(verified, true);
}
void TestSignature::testSigningGenerated_RSA_PrivateOnly()
{
QSharedPointer<Botan::Private_Key> key(new Botan::RSA_PrivateKey(*randomGen()->getRng(), 2048));
QString sign;
Signature::create(data, key, sign);
QVERIFY(!sign.isEmpty());
const bool verified = Signature::verify(data, key, sign);
QCOMPARE(verified, true);
}
void TestSignature::testSigningTest_RSA_PrivateOnly()
{
auto rsaKey = loadPrivateKey(rsa_2_private);
QVERIFY(rsaKey);
QString sign;
Signature::create(data, rsaKey, sign);
QVERIFY(!sign.isEmpty());
const bool verified = Signature::verify(data, rsaKey, sign);
QCOMPARE(verified, true);
}
void TestSignature::testSigningTest_RSA()
{
auto privateKey = loadPrivateKey(rsa_1_private);
QVERIFY(privateKey);
QString sign;
Signature::create(data, privateKey, sign);
QVERIFY(!sign.isEmpty());
auto publicKey = loadPublicKey(rsa_1_public);
QVERIFY(publicKey);
const bool verified = Signature::verify(data, publicKey, sign);
QCOMPARE(verified, true);
}

View File

@ -1,40 +0,0 @@
/*
* 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
* 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_TESTSIGNATURE_H
#define KEEPASSXC_TESTSIGNATURE_H
#include <QObject>
class TestSignature : public QObject
{
Q_OBJECT
private slots:
void initTestCase();
void testSigningOpenSSH_RSA_PrivateOnly();
void testSigningOpenSSH_RSA();
void testSigningGenerated_RSA_PrivateOnly();
void testSigningTest_RSA_PrivateOnly();
void testSigningTest_RSA();
};
#endif // KEEPASSX_TESTSIGNATURE_H