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:
Tobias Fella
2024-05-27 20:09:53 +02:00
parent 9a4a70178e
commit 94e650f7ad
5 changed files with 137 additions and 0 deletions

View File

@@ -282,6 +282,7 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/ConsentDialog.qml
qml/AskDirectChatConfirmation.qml
qml/HoverLinkIndicator.qml
qml/CrossSigningSetupDialog.qml
DEPENDENCIES
QtCore
QtQuick

View File

@@ -16,10 +16,14 @@
#include "spacehierarchycache.h"
#include <Quotient/connection.h>
#include <Quotient/csapi/cross_signing.h>
#include <Quotient/e2ee/cryptoutils.h>
#include <Quotient/jobs/basejob.h>
#include <Quotient/quotient_common.h>
#include <qt6keychain/keychain.h>
#include <olm/pk.h>
#include <KLocalizedString>
#include <Quotient/csapi/content-repo.h>
@@ -538,4 +542,95 @@ LinkPreviewer *NeoChatConnection::previewerForLink(const QUrl &link)
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"

View File

@@ -184,6 +184,8 @@ public:
LinkPreviewer *previewerForLink(const QUrl &link);
Q_INVOKABLE void setupCrossSigningKeys(const QString &password);
Q_SIGNALS:
void labelChanged();
void identityServerChanged();

View 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");
}
}
}
}

View File

@@ -309,6 +309,12 @@ Kirigami.ApplicationWindow {
url: url
}).open();
}
function onCrossSigningSetupRequired() {
Qt.createComponent("org.kde.neochat", "CrossSigningSetupDialog").createObject(this, {
connection: root.connection
}).open();
}
}
HoverLinkIndicator {