mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-04-03 04:27:39 +03:00
* 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)
394 lines
13 KiB
C++
394 lines
13 KiB
C++
/*
|
|
* 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 "KeeShareSettings.h"
|
|
|
|
#include "core/CustomData.h"
|
|
#include "core/Database.h"
|
|
#include "core/Group.h"
|
|
#include "core/Metadata.h"
|
|
#include "crypto/Random.h"
|
|
#include "gui/DatabaseIcons.h"
|
|
|
|
#include <QDataStream>
|
|
#include <QTextCodec>
|
|
#include <QXmlStreamWriter>
|
|
|
|
#include <botan/data_src.h>
|
|
#include <botan/pkcs8.h>
|
|
#include <botan/rsa.h>
|
|
|
|
namespace KeeShareSettings
|
|
{
|
|
namespace
|
|
{
|
|
QString xmlSerialize(std::function<void(QXmlStreamWriter& writer)> specific)
|
|
{
|
|
QString buffer;
|
|
QXmlStreamWriter writer(&buffer);
|
|
|
|
writer.setCodec(QTextCodec::codecForName("UTF-8"));
|
|
writer.writeStartDocument();
|
|
writer.writeStartElement("KeeShare");
|
|
specific(writer);
|
|
writer.writeEndElement();
|
|
writer.writeEndDocument();
|
|
return buffer;
|
|
}
|
|
|
|
void xmlDeserialize(const QString& raw, std::function<void(QXmlStreamReader& reader)> specific)
|
|
{
|
|
QXmlStreamReader reader(raw);
|
|
if (!reader.readNextStartElement() || reader.qualifiedName() != "KeeShare") {
|
|
return;
|
|
}
|
|
specific(reader);
|
|
}
|
|
} // namespace
|
|
|
|
void Certificate::serialize(QXmlStreamWriter& writer, const Certificate& certificate)
|
|
{
|
|
if (certificate.isNull()) {
|
|
return;
|
|
}
|
|
auto berKey = Botan::PKCS8::BER_encode(*certificate.key);
|
|
auto baKey = QByteArray::fromRawData(reinterpret_cast<const char*>(berKey.data()), berKey.size());
|
|
|
|
writer.writeStartElement("Signer");
|
|
writer.writeCharacters(certificate.signer);
|
|
writer.writeEndElement();
|
|
writer.writeStartElement("Key");
|
|
writer.writeCharacters(baKey.toBase64());
|
|
writer.writeEndElement();
|
|
}
|
|
|
|
bool Certificate::operator==(const Certificate& other) const
|
|
{
|
|
if (isNull() || other.isNull()) {
|
|
return isNull() == other.isNull();
|
|
}
|
|
return key->private_key_bits() == other.key->private_key_bits() && signer == other.signer;
|
|
}
|
|
|
|
bool Certificate::operator!=(const Certificate& other) const
|
|
{
|
|
return !operator==(other);
|
|
}
|
|
|
|
bool Certificate::isNull() const
|
|
{
|
|
return !key || signer.isEmpty();
|
|
}
|
|
|
|
QString Certificate::fingerprint() const
|
|
{
|
|
if (isNull()) {
|
|
return {};
|
|
}
|
|
return QString::fromStdString(key->fingerprint_public());
|
|
}
|
|
|
|
Certificate Certificate::deserialize(QXmlStreamReader& reader)
|
|
{
|
|
Certificate certificate;
|
|
while (!reader.error() && reader.readNextStartElement()) {
|
|
if (reader.name() == "Signer") {
|
|
certificate.signer = reader.readElementText();
|
|
} else if (reader.name() == "Key") {
|
|
auto rawKey = QByteArray::fromBase64(reader.readElementText().toLatin1());
|
|
if (!rawKey.isEmpty()) {
|
|
try {
|
|
Botan::DataSource_Memory dataSource(reinterpret_cast<const uint8_t*>(rawKey.constData()),
|
|
rawKey.size());
|
|
certificate.key.reset(Botan::PKCS8::load_key(dataSource).release());
|
|
} catch (std::exception& e) {
|
|
qWarning("KeeShare: Failed to deserialize key data: %s", e.what());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return certificate;
|
|
}
|
|
|
|
bool Key::operator==(const Key& other) const
|
|
{
|
|
if (isNull() || other.isNull()) {
|
|
return isNull() == other.isNull();
|
|
}
|
|
return key->private_key_bits() == other.key->private_key_bits();
|
|
}
|
|
|
|
bool Key::operator!=(const Key& other) const
|
|
{
|
|
return !operator==(other);
|
|
}
|
|
|
|
bool Key::isNull() const
|
|
{
|
|
return !key;
|
|
}
|
|
|
|
void Key::serialize(QXmlStreamWriter& writer, const Key& key)
|
|
{
|
|
if (key.isNull()) {
|
|
return;
|
|
}
|
|
auto berKey = Botan::PKCS8::BER_encode(*key.key);
|
|
auto baKey = QByteArray::fromRawData(reinterpret_cast<const char*>(berKey.data()), berKey.size());
|
|
writer.writeCharacters(baKey.toBase64());
|
|
}
|
|
|
|
Key Key::deserialize(QXmlStreamReader& reader)
|
|
{
|
|
Key key;
|
|
auto rawKey = QByteArray::fromBase64(reader.readElementText().toLatin1());
|
|
if (!rawKey.isEmpty()) {
|
|
try {
|
|
Botan::DataSource_Memory dataSource(reinterpret_cast<const uint8_t*>(rawKey.constData()),
|
|
rawKey.size());
|
|
key.key.reset(Botan::PKCS8::load_key(dataSource).release());
|
|
} catch (std::exception& e) {
|
|
qWarning("KeeShare: Failed to deserialize key data: %s", e.what());
|
|
}
|
|
}
|
|
return key;
|
|
}
|
|
|
|
Own Own::generate()
|
|
{
|
|
Own own;
|
|
own.key.key.reset(new Botan::RSA_PrivateKey(*randomGen()->getRng(), 2048));
|
|
auto name = qgetenv("USER");
|
|
if (name.isEmpty()) {
|
|
name = qgetenv("USERNAME");
|
|
}
|
|
own.certificate.signer = name;
|
|
own.certificate.key = own.key.key;
|
|
return own;
|
|
}
|
|
|
|
QString Active::serialize(const Active& active)
|
|
{
|
|
return xmlSerialize([&](QXmlStreamWriter& writer) {
|
|
writer.writeStartElement("Active");
|
|
if (active.in) {
|
|
writer.writeEmptyElement("Import");
|
|
}
|
|
if (active.out) {
|
|
writer.writeEmptyElement("Export");
|
|
}
|
|
writer.writeEndElement();
|
|
});
|
|
}
|
|
|
|
Active Active::deserialize(const QString& raw)
|
|
{
|
|
Active active;
|
|
xmlDeserialize(raw, [&](QXmlStreamReader& reader) {
|
|
while (!reader.error() && reader.readNextStartElement()) {
|
|
if (reader.name() == "Active") {
|
|
while (reader.readNextStartElement()) {
|
|
if (reader.name() == "Import") {
|
|
active.in = true;
|
|
reader.skipCurrentElement();
|
|
} else if (reader.name() == "Export") {
|
|
active.out = true;
|
|
reader.skipCurrentElement();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
qWarning("Unknown KeeShareSettings element %s", qPrintable(reader.name().toString()));
|
|
reader.skipCurrentElement();
|
|
}
|
|
}
|
|
});
|
|
return active;
|
|
}
|
|
|
|
bool Own::operator==(const Own& other) const
|
|
{
|
|
return key == other.key && certificate == other.certificate;
|
|
}
|
|
|
|
bool Own::operator!=(const Own& other) const
|
|
{
|
|
return !operator==(other);
|
|
}
|
|
|
|
QString Own::serialize(const Own& own)
|
|
{
|
|
return xmlSerialize([&](QXmlStreamWriter& writer) {
|
|
writer.writeStartElement("PrivateKey");
|
|
Key::serialize(writer, own.key);
|
|
writer.writeEndElement();
|
|
writer.writeStartElement("PublicKey");
|
|
Certificate::serialize(writer, own.certificate);
|
|
writer.writeEndElement();
|
|
});
|
|
}
|
|
|
|
Own Own::deserialize(const QString& raw)
|
|
{
|
|
Own own;
|
|
xmlDeserialize(raw, [&](QXmlStreamReader& reader) {
|
|
while (!reader.error() && reader.readNextStartElement()) {
|
|
if (reader.name() == "PrivateKey") {
|
|
own.key = Key::deserialize(reader);
|
|
} else if (reader.name() == "PublicKey") {
|
|
own.certificate = Certificate::deserialize(reader);
|
|
} else {
|
|
qWarning("Unknown KeeShareSettings element %s", qPrintable(reader.name().toString()));
|
|
reader.skipCurrentElement();
|
|
}
|
|
}
|
|
});
|
|
return own;
|
|
}
|
|
|
|
Reference::Reference()
|
|
: type(Inactive)
|
|
, uuid(QUuid::createUuid())
|
|
{
|
|
}
|
|
|
|
bool Reference::isNull() const
|
|
{
|
|
return type == Inactive && path.isEmpty() && password.isEmpty();
|
|
}
|
|
|
|
bool Reference::isValid() const
|
|
{
|
|
return type != Inactive && !path.isEmpty();
|
|
}
|
|
|
|
bool Reference::isExporting() const
|
|
{
|
|
return (type & ExportTo) != 0 && !path.isEmpty();
|
|
}
|
|
|
|
bool Reference::isImporting() const
|
|
{
|
|
return (type & ImportFrom) != 0 && !path.isEmpty();
|
|
}
|
|
|
|
bool Reference::operator<(const Reference& other) const
|
|
{
|
|
if (type != other.type) {
|
|
return type < other.type;
|
|
}
|
|
return path < other.path;
|
|
}
|
|
|
|
bool Reference::operator==(const Reference& other) const
|
|
{
|
|
return path == other.path && uuid == other.uuid && password == other.password && type == other.type;
|
|
}
|
|
|
|
QString Reference::serialize(const Reference& reference)
|
|
{
|
|
return xmlSerialize([&](QXmlStreamWriter& writer) {
|
|
writer.writeStartElement("Type");
|
|
if ((reference.type & ImportFrom) == ImportFrom) {
|
|
writer.writeEmptyElement("Import");
|
|
}
|
|
if ((reference.type & ExportTo) == ExportTo) {
|
|
writer.writeEmptyElement("Export");
|
|
}
|
|
writer.writeEndElement();
|
|
writer.writeStartElement("Group");
|
|
writer.writeCharacters(reference.uuid.toRfc4122().toBase64());
|
|
writer.writeEndElement();
|
|
writer.writeStartElement("Path");
|
|
writer.writeCharacters(reference.path.toUtf8().toBase64());
|
|
writer.writeEndElement();
|
|
writer.writeStartElement("Password");
|
|
writer.writeCharacters(reference.password.toUtf8().toBase64());
|
|
writer.writeEndElement();
|
|
});
|
|
}
|
|
|
|
Reference Reference::deserialize(const QString& raw)
|
|
{
|
|
Reference reference;
|
|
xmlDeserialize(raw, [&](QXmlStreamReader& reader) {
|
|
while (!reader.error() && reader.readNextStartElement()) {
|
|
if (reader.name() == "Type") {
|
|
while (reader.readNextStartElement()) {
|
|
if (reader.name() == "Import") {
|
|
reference.type |= ImportFrom;
|
|
reader.skipCurrentElement();
|
|
} else if (reader.name() == "Export") {
|
|
reference.type |= ExportTo;
|
|
reader.skipCurrentElement();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
} else if (reader.name() == "Group") {
|
|
reference.uuid = QUuid::fromRfc4122(QByteArray::fromBase64(reader.readElementText().toLatin1()));
|
|
} else if (reader.name() == "Path") {
|
|
reference.path = QString::fromUtf8(QByteArray::fromBase64(reader.readElementText().toLatin1()));
|
|
} else if (reader.name() == "Password") {
|
|
reference.password = QString::fromUtf8(QByteArray::fromBase64(reader.readElementText().toLatin1()));
|
|
} else {
|
|
qWarning("Unknown Reference element %s", qPrintable(reader.name().toString()));
|
|
reader.skipCurrentElement();
|
|
}
|
|
}
|
|
});
|
|
return reference;
|
|
}
|
|
|
|
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");
|
|
writer.writeStartElement("Signer");
|
|
writer.writeCharacters(sign.certificate.signer);
|
|
writer.writeEndElement();
|
|
writer.writeStartElement("Key");
|
|
writer.writeCharacters(rsaKeySerialized.toBase64());
|
|
writer.writeEndElement();
|
|
writer.writeEndElement();
|
|
});
|
|
}
|
|
} // namespace KeeShareSettings
|