Create Cross-signing keys when required
This should be almost entirely in libquotient, but that's not prepared to actually use user-interactive authentication...
This commit is contained in:
@@ -282,6 +282,7 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
|||||||
qml/ConsentDialog.qml
|
qml/ConsentDialog.qml
|
||||||
qml/AskDirectChatConfirmation.qml
|
qml/AskDirectChatConfirmation.qml
|
||||||
qml/HoverLinkIndicator.qml
|
qml/HoverLinkIndicator.qml
|
||||||
|
qml/CrossSigningSetupDialog.qml
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
QtCore
|
QtCore
|
||||||
QtQuick
|
QtQuick
|
||||||
|
|||||||
@@ -16,10 +16,14 @@
|
|||||||
#include "spacehierarchycache.h"
|
#include "spacehierarchycache.h"
|
||||||
|
|
||||||
#include <Quotient/connection.h>
|
#include <Quotient/connection.h>
|
||||||
|
#include <Quotient/csapi/cross_signing.h>
|
||||||
|
#include <Quotient/e2ee/cryptoutils.h>
|
||||||
#include <Quotient/jobs/basejob.h>
|
#include <Quotient/jobs/basejob.h>
|
||||||
#include <Quotient/quotient_common.h>
|
#include <Quotient/quotient_common.h>
|
||||||
#include <qt6keychain/keychain.h>
|
#include <qt6keychain/keychain.h>
|
||||||
|
|
||||||
|
#include <olm/pk.h>
|
||||||
|
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
|
|
||||||
#include <Quotient/csapi/content-repo.h>
|
#include <Quotient/csapi/content-repo.h>
|
||||||
@@ -538,4 +542,95 @@ LinkPreviewer *NeoChatConnection::previewerForLink(const QUrl &link)
|
|||||||
return previewer;
|
return previewer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NeoChatConnection::setupCrossSigningKeys(const QString &password)
|
||||||
|
{
|
||||||
|
auto masterKeyPrivate = getRandom<32>();
|
||||||
|
auto masterKeyContext = makeCStruct(olm_pk_signing, olm_pk_signing_size, olm_clear_pk_signing);
|
||||||
|
QByteArray masterKeyPublic(olm_pk_signing_public_key_length(), 0);
|
||||||
|
olm_pk_signing_key_from_seed(masterKeyContext.get(),
|
||||||
|
masterKeyPublic.data(),
|
||||||
|
masterKeyPublic.length(),
|
||||||
|
masterKeyPrivate.data(),
|
||||||
|
masterKeyPrivate.viewAsByteArray().length());
|
||||||
|
|
||||||
|
auto selfSigningKeyPrivate = getRandom<32>();
|
||||||
|
auto selfSigningKeyContext = makeCStruct(olm_pk_signing, olm_pk_signing_size, olm_clear_pk_signing);
|
||||||
|
QByteArray selfSigningKeyPublic(olm_pk_signing_public_key_length(), 0);
|
||||||
|
olm_pk_signing_key_from_seed(selfSigningKeyContext.get(),
|
||||||
|
selfSigningKeyPublic.data(),
|
||||||
|
selfSigningKeyPublic.length(),
|
||||||
|
selfSigningKeyPrivate.data(),
|
||||||
|
selfSigningKeyPrivate.viewAsByteArray().length());
|
||||||
|
|
||||||
|
auto userSigningKeyPrivate = getRandom<32>();
|
||||||
|
auto userSigningKeyContext = makeCStruct(olm_pk_signing, olm_pk_signing_size, olm_clear_pk_signing);
|
||||||
|
QByteArray userSigningKeyPublic(olm_pk_signing_public_key_length(), 0);
|
||||||
|
olm_pk_signing_key_from_seed(userSigningKeyContext.get(),
|
||||||
|
userSigningKeyPublic.data(),
|
||||||
|
userSigningKeyPublic.length(),
|
||||||
|
userSigningKeyPrivate.data(),
|
||||||
|
userSigningKeyPrivate.viewAsByteArray().length());
|
||||||
|
|
||||||
|
database()->storeEncrypted("m.cross_signing.master"_ls, masterKeyPrivate.viewAsByteArray());
|
||||||
|
database()->storeEncrypted("m.cross_signing.self_signing"_ls, selfSigningKeyPrivate.viewAsByteArray());
|
||||||
|
database()->storeEncrypted("m.cross_signing.user_signing"_ls, userSigningKeyPrivate.viewAsByteArray());
|
||||||
|
|
||||||
|
auto masterKey = CrossSigningKey{
|
||||||
|
.userId = userId(),
|
||||||
|
.usage = {"master"_ls},
|
||||||
|
.keys = {{"ed25519:"_ls + QString::fromLatin1(masterKeyPublic), QString::fromLatin1(masterKeyPublic)}},
|
||||||
|
.signatures = {},
|
||||||
|
};
|
||||||
|
auto selfSigningKey = CrossSigningKey{
|
||||||
|
.userId = userId(),
|
||||||
|
.usage = {"self_signing"_ls},
|
||||||
|
.keys = {{"ed25519:"_ls + QString::fromLatin1(selfSigningKeyPublic), QString::fromLatin1(selfSigningKeyPublic)}},
|
||||||
|
};
|
||||||
|
auto userSigningKey = CrossSigningKey{
|
||||||
|
.userId = userId(),
|
||||||
|
.usage = {"user_signing"_ls},
|
||||||
|
.keys = {{"ed25519:"_ls + QString::fromLatin1(userSigningKeyPublic), QString::fromLatin1(userSigningKeyPublic)}},
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
auto selfSigningKeyJson = toJson(selfSigningKey);
|
||||||
|
selfSigningKeyJson.remove("signatures"_ls);
|
||||||
|
selfSigningKey.signatures = QJsonObject{
|
||||||
|
{userId(),
|
||||||
|
QJsonObject{{"ed25519:"_ls + QString::fromLatin1(masterKeyPublic),
|
||||||
|
QString::fromLatin1(sign(masterKeyPrivate.viewAsByteArray(), QJsonDocument(selfSigningKeyJson).toJson(QJsonDocument::Compact)))}}}};
|
||||||
|
auto userSigningKeyJson = toJson(userSigningKey);
|
||||||
|
userSigningKeyJson.remove("signatures"_ls);
|
||||||
|
userSigningKey.signatures = QJsonObject{
|
||||||
|
{userId(),
|
||||||
|
QJsonObject{{"ed25519:"_ls + QString::fromLatin1(masterKeyPublic),
|
||||||
|
QString::fromLatin1(sign(masterKeyPrivate.viewAsByteArray(), QJsonDocument(userSigningKeyJson).toJson(QJsonDocument::Compact)))}}}};
|
||||||
|
|
||||||
|
auto job = callApi<UploadCrossSigningKeysJob>(masterKey, selfSigningKey, userSigningKey, std::nullopt);
|
||||||
|
connect(job, &BaseJob::failure, this, [this, masterKey, selfSigningKey, userSigningKey, password](const auto &job) {
|
||||||
|
callApi<UploadCrossSigningKeysJob>(masterKey,
|
||||||
|
selfSigningKey,
|
||||||
|
userSigningKey,
|
||||||
|
AuthenticationData{
|
||||||
|
.type = "m.login.password"_ls,
|
||||||
|
.session = job->jsonData()["session"_ls].toString(),
|
||||||
|
.authInfo =
|
||||||
|
QVariantHash{
|
||||||
|
{"password"_ls, password},
|
||||||
|
{"identifier"_ls,
|
||||||
|
QJsonObject{
|
||||||
|
{"type"_ls, "m.id.user"_ls},
|
||||||
|
{"user"_ls, userId()},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
.then([](const auto &job) {
|
||||||
|
// TODO mark key as verified
|
||||||
|
// TODO store keys in accountdata
|
||||||
|
qWarning() << "finished uploading cs keys" << job->jsonData() << job->errorString();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#include "moc_neochatconnection.cpp"
|
#include "moc_neochatconnection.cpp"
|
||||||
|
|||||||
@@ -184,6 +184,8 @@ public:
|
|||||||
|
|
||||||
LinkPreviewer *previewerForLink(const QUrl &link);
|
LinkPreviewer *previewerForLink(const QUrl &link);
|
||||||
|
|
||||||
|
Q_INVOKABLE void setupCrossSigningKeys(const QString &password);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void labelChanged();
|
void labelChanged();
|
||||||
void identityServerChanged();
|
void identityServerChanged();
|
||||||
|
|||||||
33
src/qml/CrossSigningSetupDialog.qml
Normal file
33
src/qml/CrossSigningSetupDialog.qml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat
|
||||||
|
|
||||||
|
Kirigami.PromptDialog {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property NeoChatConnection connection
|
||||||
|
|
||||||
|
title: i18nc("@title:dialog", "Setup Encryption Keys")
|
||||||
|
dialogType: Kirigami.PromptDialog.Information
|
||||||
|
|
||||||
|
onRejected: {
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
footer: QQC2.DialogButtonBox {
|
||||||
|
standardButtons: QQC2.Dialog.Cancel
|
||||||
|
|
||||||
|
QQC2.Button {
|
||||||
|
text: i18nc("@action:button", "Setup Keys")
|
||||||
|
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
|
||||||
|
onClicked: {
|
||||||
|
root.connection.setupCrossSigningKeys("password123");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -309,6 +309,12 @@ Kirigami.ApplicationWindow {
|
|||||||
url: url
|
url: url
|
||||||
}).open();
|
}).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onCrossSigningSetupRequired() {
|
||||||
|
Qt.createComponent("org.kde.neochat", "CrossSigningSetupDialog").createObject(this, {
|
||||||
|
connection: root.connection
|
||||||
|
}).open();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HoverLinkIndicator {
|
HoverLinkIndicator {
|
||||||
|
|||||||
Reference in New Issue
Block a user