Newer
Older
const size_t nkeys = MAX_ONETIME_KEYS - count->second;
nhlog::crypto()->info("uploading {} {} keys", nkeys, mtx::crypto::SIGNED_CURVE25519);
olm::client()->generate_one_time_keys(nkeys);
http::client()->upload_keys(
olm::client()->create_upload_keys_request(),
[](const mtx::responses::UploadKeys &, mtx::http::RequestErr err) {
if (err) {
nhlog::crypto()->warn("failed to update one-time keys: {}", err);
if (err->status_code < 400 || err->status_code >= 500)
return;
}
// mark as published anyway, otherwise we may end up in a loop.
olm::mark_keys_as_published();
});
} else if (count->second > 2 * MAX_ONETIME_KEYS) {
nhlog::crypto()->warn("too many one-time keys, deleting 1");
mtx::requests::ClaimKeys req;
req.one_time_keys[http::client()->user_id().to_string()][http::client()->device_id()] =
std::string(mtx::crypto::SIGNED_CURVE25519);
http::client()->claim_keys(
req, [](const mtx::responses::ClaimKeys &, mtx::http::RequestErr err) {
if (err)
nhlog::crypto()->warn("failed to clear 1 one-time key: {}", err);
else
nhlog::crypto()->info("cleared 1 one-time key");
});
void
ChatPage::getProfileInfo()
{
const auto userid = utils::localUser().toStdString();
http::client()->get_profile(
userid, [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) {
if (err) {
nhlog::net()->warn("failed to retrieve own profile info");
return;
}
emit setUserDisplayName(QString::fromStdString(res.display_name));
emit setUserAvatar(QString::fromStdString(res.avatar_url));
});
void
ChatPage::getBackupVersion()
{
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
if (!UserSettings::instance()->useOnlineKeyBackup()) {
nhlog::crypto()->info("Online key backup disabled.");
return;
}
http::client()->backup_version(
[this](const mtx::responses::backup::BackupVersion &res, mtx::http::RequestErr err) {
if (err) {
nhlog::net()->warn("Failed to retrieve backup version");
if (err->status_code == 404)
cache::client()->deleteBackupVersion();
return;
}
// switch to UI thread for secrets stuff
QTimer::singleShot(0, this, [res] {
auto auth_data = nlohmann::json::parse(res.auth_data);
if (res.algorithm == "m.megolm_backup.v1.curve25519-aes-sha2") {
auto key = cache::secret(mtx::secret_storage::secrets::megolm_backup_v1);
if (!key) {
nhlog::crypto()->info("No key for online key backup.");
cache::client()->deleteBackupVersion();
return;
using namespace mtx::crypto;
auto pubkey = CURVE25519_public_key_from_private(to_binary_buf(base642bin(*key)));
if (auth_data["public_key"].get<std::string>() != pubkey) {
nhlog::crypto()->info("Our backup key {} does not match the one "
"used in the online backup {}",
pubkey,
auth_data["public_key"]);
cache::client()->deleteBackupVersion();
return;
}
nhlog::crypto()->info("Using online key backup.");
OnlineBackupVersion data{};
data.algorithm = res.algorithm;
data.version = res.version;
cache::client()->saveBackupVersion(data);
} else {
nhlog::crypto()->info("Unsupported key backup algorithm: {}", res.algorithm);
cache::client()->deleteBackupVersion();
}
void
ChatPage::initiateLogout()
{
http::client()->logout([this](const mtx::responses::Logout &, mtx::http::RequestErr err) {
if (err) {
// TODO: handle special errors
emit contentLoaded();
nhlog::net()->warn("failed to logout: {}", err);
template<typename T>
void
ChatPage::connectCallMessage()
{
connect(callManager_,
qOverload<const QString &, const T &>(&CallManager::newMessage),
view_manager_,
qOverload<const QString &, const T &>(&TimelineViewManager::queueCallMessage));
void
ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
const SecretsToDecrypt &secrets)
{
QCoreApplication::translate("CrossSigningSecrets", "Decrypt secrets"),
keyDesc.name.empty()
? QCoreApplication::translate(
"CrossSigningSecrets", "Enter your recovery key or passphrase to decrypt your secrets:")
: QCoreApplication::translate(
"CrossSigningSecrets",
"Enter your recovery key or passphrase called %1 to decrypt your secrets:")
.arg(QString::fromStdString(keyDesc.name)),
QLineEdit::Password);
if (text.isEmpty())
return;
// strip space chars from a recovery key. It can't contain those, but some clients insert them
// to make them easier to read.
QString stripped = text;
stripped.remove(' ');
stripped.remove('\n');
stripped.remove('\t');
auto decryptionKey = mtx::crypto::key_from_recoverykey(stripped.toStdString(), keyDesc);
if (!decryptionKey && keyDesc.passphrase) {
try {
decryptionKey = mtx::crypto::key_from_passphrase(text.toStdString(), keyDesc);
} catch (std::exception &e) {
nhlog::crypto()->error("Failed to derive secret key from passphrase: {}", e.what());
if (!decryptionKey) {
QMessageBox::information(
QCoreApplication::translate("CrossSigningSecrets", "Decryption failed"),
QCoreApplication::translate("CrossSigningSecrets",
"Failed to decrypt secrets with the "
"provided recovery key or passphrase"));
return;
}
auto deviceKeys = cache::client()->userKeys(http::client()->user_id().to_string());
mtx::requests::KeySignaturesUpload req;
for (const auto &[secretName, encryptedSecret] : secrets) {
auto decrypted = mtx::crypto::decrypt(encryptedSecret, *decryptionKey, secretName);
cache::storeSecret(secretName, decrypted);
if (deviceKeys && deviceKeys->device_keys.count(http::client()->device_id()) &&
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
secretName == mtx::secret_storage::secrets::cross_signing_self_signing) {
auto myKey = deviceKeys->device_keys.at(http::client()->device_id());
if (myKey.user_id == http::client()->user_id().to_string() &&
myKey.device_id == http::client()->device_id() &&
myKey.keys["ed25519:" + http::client()->device_id()] ==
olm::client()->identity_keys().ed25519 &&
myKey.keys["curve25519:" + http::client()->device_id()] ==
olm::client()->identity_keys().curve25519) {
json j = myKey;
j.erase("signatures");
j.erase("unsigned");
auto ssk = mtx::crypto::PkSigning::from_seed(decrypted);
myKey.signatures[http::client()->user_id().to_string()]
["ed25519:" + ssk.public_key()] = ssk.sign(j.dump());
req.signatures[http::client()->user_id().to_string()]
[http::client()->device_id()] = myKey;
}
} else if (deviceKeys &&
secretName == mtx::secret_storage::secrets::cross_signing_master) {
auto mk = mtx::crypto::PkSigning::from_seed(decrypted);
if (deviceKeys->master_keys.user_id == http::client()->user_id().to_string() &&
deviceKeys->master_keys.keys["ed25519:" + mk.public_key()] == mk.public_key()) {
json j = deviceKeys->master_keys;
j.erase("signatures");
j.erase("unsigned");
mtx::crypto::CrossSigningKeys master_key = j;
master_key.signatures[http::client()->user_id().to_string()]
["ed25519:" + http::client()->device_id()] =
olm::client()->sign_message(j.dump());
req.signatures[http::client()->user_id().to_string()][mk.public_key()] =
master_key;
}
}
}
if (!req.signatures.empty())
http::client()->keys_signatures_upload(
req, [](const mtx::responses::KeySignaturesUpload &res, mtx::http::RequestErr err) {
if (err) {
nhlog::net()->error("failed to upload signatures: {},{}",
mtx::errors::to_string(err->matrix_error.errcode),
static_cast<int>(err->status_code));
}
for (const auto &[user_id, tmp] : res.errors)
for (const auto &[key_id, e] : tmp)
nhlog::net()->error("signature error for user '{}' and key "
user_id,
key_id,
mtx::errors::to_string(e.errcode),
e.error);
});
ChatPage::startChat(QString userid, std::optional<bool> encryptionEnabled)
auto joined_rooms = cache::joinedRooms();
auto room_infos = cache::getRoomInfo(joined_rooms);
for (const std::string &room_id : joined_rooms) {
if (const auto &info = room_infos[QString::fromStdString(room_id)];
info.member_count == 2 && !info.is_space) {
auto room_members = cache::roomMembers(room_id);
if (std::find(room_members.begin(), room_members.end(), (userid).toStdString()) !=
room_members.end()) {
view_manager_->rooms()->setCurrentRoom(QString::fromStdString(room_id));
}
if (QMessageBox::Yes !=
QMessageBox::question(
tr("Confirm invite"),
tr("Do you really want to start a private chat with %1?").arg(userid)))
return;
mtx::requests::CreateRoom req;
req.preset = mtx::requests::Preset::PrivateChat;
req.visibility = mtx::common::RoomVisibility::Private;
if (encryptionEnabled.value_or(false)) {
mtx::events::StrippedEvent<mtx::events::state::Encryption> enc;
enc.type = mtx::events::EventType::RoomEncryption;
enc.content.algorithm = mtx::crypto::MEGOLM_ALGO;
req.initial_state.emplace_back(std::move(enc));
}
if (utils::localUser() != userid) {
req.invite = {userid.toStdString()};
req.is_direct = true;
}
emit ChatPage::instance()->createRoom(req);
mxidFromSegments(QStringView sigil, QStringView mxid)
auto mxid_ = QUrl::fromPercentEncoding(mxid.toUtf8());
return "#" + mxid_;
//} else if (sigil == "group") {
// return "+" + mxid_;
} else {
ChatPage::handleMatrixUri(QString uri)
nhlog::ui()->info("Received uri! {}", uri.toStdString());
// Convert matrix.to URIs to proper format
if (uri_.scheme() == QLatin1String("https") && uri_.host() == QLatin1String("matrix.to")) {
QString p = uri_.fragment(QUrl::FullyEncoded);
if (p.startsWith(QLatin1String("/")))
auto temp = p.split(QStringLiteral("?"));
QString query;
if (temp.size() >= 2)
query = QUrl::fromPercentEncoding(temp.takeAt(1).toUtf8());
temp = temp.first().split(QStringLiteral("/"));
auto identifier = QUrl::fromPercentEncoding(temp.takeFirst().toUtf8());
QString eventId = QUrl::fromPercentEncoding(temp.join('/').toUtf8());
if (!identifier.isEmpty()) {
if (identifier.startsWith(QLatin1String("@"))) {
QByteArray newUri = "matrix:u/" + QUrl::toPercentEncoding(identifier.remove(0, 1));
if (!query.isEmpty())
newUri.append("?" + query.toUtf8());
return handleMatrixUri(QUrl::fromEncoded(newUri));
} else if (identifier.startsWith(QLatin1String("#"))) {
QByteArray newUri = "matrix:r/" + QUrl::toPercentEncoding(identifier.remove(0, 1));
if (!eventId.isEmpty())
newUri.append("/e/" + QUrl::toPercentEncoding(eventId.remove(0, 1)));
if (!query.isEmpty())
newUri.append("?" + query.toUtf8());
return handleMatrixUri(QUrl::fromEncoded(newUri));
} else if (identifier.startsWith(QLatin1String("!"))) {
QByteArray newUri =
"matrix:roomid/" + QUrl::toPercentEncoding(identifier.remove(0, 1));
if (!eventId.isEmpty())
newUri.append("/e/" + QUrl::toPercentEncoding(eventId.remove(0, 1)));
if (!query.isEmpty())
newUri.append("?" + query.toUtf8());
return handleMatrixUri(QUrl::fromEncoded(newUri));
}
// non-matrix URIs are not handled by us, return false
if (uri_.scheme() != QLatin1String("matrix"))
auto tempPath = uri_.path(QUrl::ComponentFormattingOption::FullyEncoded);
if (tempPath.startsWith('/'))
tempPath.remove(0, 1);
if (segments.size() != 2 && segments.size() != 4)
return false;
auto sigil1 = segments[0];
auto mxid1 = mxidFromSegments(sigil1, segments[1]);
if (mxid1.isEmpty())
return false;
QString mxid2;
if (segments.size() == 4 && segments[2] == QStringView(u"e")) {
if (segments[3].isEmpty())
return false;
else
mxid2 = "$" + QUrl::fromPercentEncoding(segments[3].toUtf8());
}
std::vector<std::string> vias;
QString action;
auto items =
uri_.query(QUrl::ComponentFormattingOption::FullyEncoded).split('&', Qt::SkipEmptyParts);
for (QString item : qAsConst(items)) {
nhlog::ui()->info("item: {}", item.toStdString());
if (item.startsWith(QLatin1String("action="))) {
action = item.remove(QStringLiteral("action="));
} else if (item.startsWith(QLatin1String("via="))) {
vias.push_back(QUrl::fromPercentEncoding(item.remove(QStringLiteral("via=")).toUtf8())
.toStdString());
if (action.isEmpty()) {
auto t = view_manager_->rooms()->currentRoom();
if (t && cache::isRoomMember(mxid1.toStdString(), t->roomId().toStdString())) {
t->openUserProfile(mxid1);
}
emit view_manager_->openGlobalUserProfile(mxid1);
this->startChat(mxid1);
}
return true;
auto joined_rooms = cache::joinedRooms();
auto targetRoomId = mxid1.toStdString();
for (const auto &roomid : joined_rooms) {
if (roomid == targetRoomId) {
view_manager_->rooms()->setCurrentRoom(mxid1);
if (!mxid2.isEmpty())
view_manager_->showEvent(mxid1, mxid2);
return true;
}
}
joinRoomVia(targetRoomId, vias);
return true;
} else if (action == u"knock" || action.isEmpty()) {
knockRoom(mxid1, vias);
return true;
auto joined_rooms = cache::joinedRooms();
auto targetRoomAlias = mxid1.toStdString();
for (const auto &roomid : joined_rooms) {
auto aliases = cache::client()->getRoomAliases(roomid);
if (aliases) {
if (aliases->alias == targetRoomAlias) {
view_manager_->rooms()->setCurrentRoom(QString::fromStdString(roomid));
if (!mxid2.isEmpty())
view_manager_->showEvent(QString::fromStdString(roomid), mxid2);
return true;
joinRoomVia(mxid1.toStdString(), vias);
return true;
} else if (action == u"knock" || action.isEmpty()) {
knockRoom(mxid1, vias);
return true;
ChatPage::handleMatrixUri(const QUrl &uri)
{
return handleMatrixUri(uri.toString(QUrl::ComponentFormattingOption::FullyEncoded).toUtf8());
bool
ChatPage::isRoomActive(const QString &room_id)
{
return MainWindow::instance()->isActive() && currentRoom() == room_id;
}
QString
ChatPage::currentRoom() const
if (view_manager_->rooms()->currentRoom())
return view_manager_->rooms()->currentRoom()->roomId();
else