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

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