#pragma once #include #include #include "CacheCryptoStructs.h" #include "Logging.h" #include "MatrixClient.h" #include "Olm.h" #include "timeline/TimelineModel.h" class QTimer; using sas_ptr = std::unique_ptr; // clang-format off /* * Stolen from fluffy chat :D * * State | +-------------+ +-----------+ | * | | AliceDevice | | BobDevice | | * | | (sender) | | | | * | +-------------+ +-----------+ | * promptStartVerify | | | | * | o | (m.key.verification.request) | | * | p |-------------------------------->| (ASK FOR VERIFICATION REQUEST) | * waitForOtherAccept | t | | | promptStartVerify * && | i | (m.key.verification.ready) | | * no commitment | o |<--------------------------------| | * && | n | | | * no canonical_json | a | (m.key.verification.start) | | waitingForKeys * | l |<--------------------------------| Not sending to prevent the glare resolve| && no commitment * | | | | && no canonical_json * | | m.key.verification.start | | * waitForOtherAccept | |-------------------------------->| (IF NOT ALREADY ASKED, | * && | | | ASK FOR VERIFICATION REQUEST) | promptStartVerify, if not accepted * canonical_json | | m.key.verification.accept | | * | |<--------------------------------| | * waitForOtherAccept | | | | waitingForKeys * && | | m.key.verification.key | | && canonical_json * commitment | |-------------------------------->| | && commitment * | | | | * | | m.key.verification.key | | * | |<--------------------------------| | * compareEmoji/Number| | | | compareEmoji/Number * | | COMPARE EMOJI / NUMBERS | | * | | | | * waitingForMac | | m.key.verification.mac | | waitingForMac * | success |<------------------------------->| success | * | | | | * success/fail | | m.key.verification.done | | success/fail * | |<------------------------------->| | */ // clang-format on class DeviceVerificationFlow : public QObject { Q_OBJECT // Q_CLASSINFO("RegisterEnumClassesUnscoped", "false") Q_PROPERTY(QString state READ state NOTIFY stateChanged) Q_PROPERTY(Error error READ error CONSTANT) Q_PROPERTY(QString userId READ getUserId CONSTANT) Q_PROPERTY(QString deviceId READ getDeviceId CONSTANT) Q_PROPERTY(bool sender READ getSender CONSTANT) Q_PROPERTY(std::vector sasList READ getSasList CONSTANT) public: enum State { PromptStartVerification, WaitingForOtherToAccept, WaitingForKeys, CompareEmoji, CompareNumber, WaitingForMac, Success, Failed, }; Q_ENUM(State) enum Type { ToDevice, RoomMsg }; enum Error { UnknownMethod, MismatchedCommitment, MismatchedSAS, KeyMismatch, Timeout, User, OutOfOrder, }; Q_ENUM(Error) static QSharedPointer NewInRoomVerification( QObject *parent_, TimelineModel *timelineModel_, const mtx::events::msg::KeyVerificationRequest &msg, QString other_user_, QString event_id_); static QSharedPointer NewToDeviceVerification( QObject *parent_, const mtx::events::msg::KeyVerificationRequest &msg, QString other_user_, QString txn_id_); static QSharedPointer NewToDeviceVerification( QObject *parent_, const mtx::events::msg::KeyVerificationStart &msg, QString other_user_, QString txn_id_); static QSharedPointer InitiateUserVerification(QObject *parent_, TimelineModel *timelineModel_, QString userid); static QSharedPointer InitiateDeviceVerification(QObject *parent, QString userid, QString device); // getters QString state(); Error error() { return error_; } QString getUserId(); QString getDeviceId(); bool getSender(); std::vector getSasList(); QString transactionId() { return QString::fromStdString(this->transaction_id); } // setters void setDeviceId(QString deviceID); void setEventId(std::string event_id); void callback_fn(const UserKeyCache &res, mtx::http::RequestErr err, std::string user_id); public slots: //! unverifies a device void unverify(); //! Continues the flow void next(); //! Cancel the flow void cancel() { cancelVerification(User); } signals: void refreshProfile(); void stateChanged(); void errorChanged(); private: DeviceVerificationFlow(QObject *, DeviceVerificationFlow::Type flow_type, TimelineModel *model, QString userID, QString deviceId_); void setState(State state) { if (state != state_) { state_ = state; emit stateChanged(); } } void handleStartMessage(const mtx::events::msg::KeyVerificationStart &msg, std::string); //! sends a verification request void sendVerificationRequest(); //! accepts a verification request void sendVerificationReady(); //! completes the verification flow(); void sendVerificationDone(); //! accepts a verification void acceptVerificationRequest(); //! starts the verification flow void startVerificationRequest(); //! cancels a verification flow void cancelVerification(DeviceVerificationFlow::Error error_code); //! sends the verification key void sendVerificationKey(); //! sends the mac of the keys void sendVerificationMac(); //! Completes the verification flow void acceptDevice(); // for to_device messages std::string transaction_id; // for room messages std::optional room_id; std::optional event_id; bool sender; Type type; mtx::identifiers::User toClient; QString deviceId; mtx::events::msg::SASMethods method = mtx::events::msg::SASMethods::Emoji; QTimer *timeout = nullptr; sas_ptr sas; std::string mac_method; std::string commitment; nlohmann::json canonical_json; std::vector sasList; std::map device_keys; TimelineModel *model_; mtx::common::RelatesTo relation; State state_ = PromptStartVerification; Error error_; bool isMacVerified = false; template void send(T msg) { if (this->type == DeviceVerificationFlow::Type::ToDevice) { mtx::requests::ToDeviceMessages body; msg.transaction_id = this->transaction_id; body[this->toClient][deviceId.toStdString()] = msg; http::client()->send_to_device( this->transaction_id, body, [this](mtx::http::RequestErr err) { if (err) nhlog::net()->warn( "failed to send verification to_device message: {} {}", err->matrix_error.error, static_cast(err->status_code)); }); } else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) { if constexpr (!std::is_same_v) msg.relates_to = this->relation; (model_)->sendMessageEvent(msg, mtx::events::to_device_content_to_type); } nhlog::net()->debug( "Sent verification step: {} in state: {}", mtx::events::to_string(mtx::events::to_device_content_to_type), state().toStdString()); } };