mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2026-03-11 08:54:48 +00:00
Original source of icons is the icon8 library (http://icons8.com/c/flat-color-icons) and Paomedia (https://github.com/paomedia/small-n-flat). All icons used are licensed MIT or CC0; annotated in COPYING. * Closes #4071 * Increase default size of database icons to 24px and entry preview panel to 48px * Add shell script to assemble the database icons * Use QIcon to seamlessly support High DPI displays and pixmap caching * Add badge support for KeeShare groups and expired entries. * Guard against use of QPixmap::fromImage without a GUI * Add SVG minify and improve `make icons` Co-authored-by: Wolfram Rösler <wolfram@roesler-ac.de>
232 lines
11 KiB
C++
232 lines
11 KiB
C++
/*
|
|
* Copyright (C) 2019 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 "ShareExport.h"
|
|
#include "config-keepassx.h"
|
|
#include "core/Group.h"
|
|
#include "core/Metadata.h"
|
|
#include "format/KeePass2Writer.h"
|
|
#include "keeshare/KeeShare.h"
|
|
#include "keeshare/Signature.h"
|
|
#include "keys/PasswordKey.h"
|
|
|
|
#if defined(WITH_XC_KEESHARE_SECURE)
|
|
#include <quazip.h>
|
|
#include <quazipfile.h>
|
|
#endif
|
|
|
|
namespace
|
|
{
|
|
void resolveReferenceAttributes(Entry* targetEntry, const Database* sourceDb)
|
|
{
|
|
for (const auto& attribute : EntryAttributes::DefaultAttributes) {
|
|
const auto standardValue = targetEntry->attributes()->value(attribute);
|
|
const auto type = targetEntry->placeholderType(standardValue);
|
|
if (type != Entry::PlaceholderType::Reference) {
|
|
// No reference to resolve
|
|
continue;
|
|
}
|
|
const auto* referencedTargetEntry = targetEntry->resolveReference(standardValue);
|
|
if (referencedTargetEntry) {
|
|
// References is within scope, no resolving needed
|
|
continue;
|
|
}
|
|
// We could do more sophisticated **** trying to point the reference to the next in-scope reference
|
|
// but those cases with high propability constructed examples and very rare in real usage
|
|
const auto* sourceReference = sourceDb->rootGroup()->findEntryByUuid(targetEntry->uuid());
|
|
const auto resolvedValue = sourceReference->resolveMultiplePlaceholders(standardValue);
|
|
targetEntry->setUpdateTimeinfo(false);
|
|
targetEntry->attributes()->set(attribute, resolvedValue, targetEntry->attributes()->isProtected(attribute));
|
|
targetEntry->setUpdateTimeinfo(true);
|
|
}
|
|
}
|
|
|
|
Database* extractIntoDatabase(const KeeShareSettings::Reference& reference, const Group* sourceRoot)
|
|
{
|
|
const auto* sourceDb = sourceRoot->database();
|
|
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);
|
|
const bool updateTimeinfo = targetRoot->canUpdateTimeinfo();
|
|
targetRoot->setUpdateTimeinfo(false);
|
|
KeeShare::setReferenceTo(targetRoot, KeeShareSettings::Reference());
|
|
targetRoot->setUpdateTimeinfo(updateTimeinfo);
|
|
const auto sourceEntries = sourceRoot->entriesRecursive(false);
|
|
for (const Entry* sourceEntry : sourceEntries) {
|
|
auto* targetEntry = sourceEntry->clone(Entry::CloneIncludeHistory);
|
|
const bool updateTimeinfo = targetEntry->canUpdateTimeinfo();
|
|
targetEntry->setUpdateTimeinfo(false);
|
|
targetEntry->setGroup(targetRoot);
|
|
targetEntry->setUpdateTimeinfo(updateTimeinfo);
|
|
const auto iconUuid = targetEntry->iconUuid();
|
|
if (!iconUuid.isNull() && !targetMetadata->hasCustomIcon(iconUuid)) {
|
|
targetMetadata->addCustomIcon(iconUuid, sourceEntry->icon());
|
|
}
|
|
}
|
|
|
|
targetDb->setKey(key);
|
|
auto* obsoleteRoot = targetDb->rootGroup();
|
|
targetDb->setRootGroup(targetRoot);
|
|
delete obsoleteRoot;
|
|
|
|
targetDb->metadata()->setName(sourceRoot->name());
|
|
|
|
// Push all deletions of the source database to the target
|
|
// simple moving out of a share group will not trigger a deletion in the
|
|
// target - a more elaborate mechanism may need the use of another custom
|
|
// attribute to share unshared entries from the target db
|
|
for (const auto& object : sourceDb->deletedObjects()) {
|
|
targetDb->addDeletedObject(object);
|
|
}
|
|
for (auto* targetEntry : targetRoot->entriesRecursive(false)) {
|
|
if (targetEntry->hasReferences()) {
|
|
resolveReferenceAttributes(targetEntry, sourceDb);
|
|
}
|
|
}
|
|
return targetDb;
|
|
}
|
|
|
|
ShareObserver::Result
|
|
intoSignedContainer(const QString& resolvedPath, const KeeShareSettings::Reference& reference, Database* targetDb)
|
|
{
|
|
#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;
|
|
auto sshKey = own.key.sshKey();
|
|
sshKey.openKey(QString());
|
|
sign.signature = Signature::create(bytes, sshKey);
|
|
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
|
|
}
|
|
|
|
ShareObserver::Result
|
|
intoUnsignedContainer(const QString& resolvedPath, const KeeShareSettings::Reference& reference, Database* targetDb)
|
|
{
|
|
#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};
|
|
}
|
|
|
|
} // 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());
|
|
}
|
|
if (KeeShare::isContainerType(info, KeeShare::unsignedContainerFileType())) {
|
|
return intoUnsignedContainer(resolvedPath, reference, targetDb.data());
|
|
}
|
|
Q_ASSERT(false);
|
|
return {reference.path, ShareObserver::Result::Error, tr("Unexpected export error occurred")};
|
|
}
|