Page MenuHomePhorge

No OneTemporary

Size
20 KB
Referenced Files
None
Subscribers
None
diff --git a/src/crypto/verification-tracker.cpp b/src/crypto/verification-tracker.cpp
index f46fac2..59769a9 100644
--- a/src/crypto/verification-tracker.cpp
+++ b/src/crypto/verification-tracker.cpp
@@ -1,109 +1,133 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2021 Tusooa Zhu <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <algorithm>
#include <immer/set.hpp>
#include <types.hpp>
#include <debug.hpp>
#include "verification-tracker.hpp"
#include "verification-process.hpp"
namespace Kazv
{
static const auto supportedVerificationMethods =
immer::set<std::string>{}.insert("m.sas.v1");
static bool methodSupported(immer::flex_vector<std::string> methods)
{
return std::any_of(methods.begin(), methods.end(),
[](auto method) { return supportedVerificationMethods.count(method); });
}
static std::string idealMethod(immer::flex_vector<std::string> /* methods */)
{
return "m.sas.v1";
}
static std::size_t processRequestRandomSize(std::string method)
{
if (method == "m.sas.v1") {
return SASVerificationProcess::constructRandomSize();
}
return 0;
}
static std::size_t processStartRandomSize(std::string method)
{
if (method == "m.sas.v1") {
return SASVerificationProcess::constructRandomSize();
}
return 0;
}
static bool isEventWellFormed(const nlohmann::json &event)
{
return event.is_object()
&& event.contains("content")
&& event["content"].is_object()
&& event.contains("type")
&& event["type"].is_string()
&& (event["content"].contains("transaction_id")
&& event["content"]["transaction_id"].is_string());
}
+ struct VerificationTracker::Private
+ {
+ };
+
+ VerificationTracker::VerificationTracker()
+ {
+ }
+
+ VerificationTracker::VerificationTracker(std::string userId, std::string deviceId)
+ {
+ }
+
+ KAZV_DEFINE_COPYABLE_UNIQUE_PTR(VerificationTracker, m_d);
+
+ VerificationTracker::~VerificationTracker()
+ {
+ }
+
std::size_t VerificationTracker::processRandomSize(const nlohmann::json &event)
{
if (!isEventWellFormed(event)) {
return 0;
}
kzo.crypto.dbg() << "event: " << event.dump() << std::endl;
if (event["type"].template get<std::string>() == "m.key.verification.request") {
if (!(event["content"].contains("methods") && event["content"]["methods"].is_array())) {
kzo.crypto.dbg() << "methods not an array" << std::endl;
return 0;
}
auto methods = event["content"]["methods"]
.template get<immer::flex_vector<std::string>>();
if (methodSupported(methods)) {
kzo.crypto.dbg() << "methods supported" << std::endl;
return processRequestRandomSize(idealMethod(methods));
}
kzo.crypto.dbg() << "methods not supported" << std::endl;
return 0;
}
if (event["type"].template get<std::string>() == "m.key.verification.start") {
if (!(event["content"].contains("method") && event["content"]["method"].is_string())) {
return 0;
}
auto method = event["content"]["method"]
.template get<std::string>();
if (methodSupported({ method })) {
return processStartRandomSize(method);
}
return 0;
}
return 0;
}
VerificationTrackerResult VerificationTracker::process(const nlohmann::json &event, RandomData random, Timestamp ts)
{
return {};
}
+
+ VerificationTrackerResult VerificationTracker::requestVerification(
+ std::string userId, immer::flex_vector<std::string> deviceIds, Timestamp ts)
+ {
+ return {};
+ }
}
diff --git a/src/crypto/verification-tracker.hpp b/src/crypto/verification-tracker.hpp
index 130b68e..c8d7de6 100644
--- a/src/crypto/verification-tracker.hpp
+++ b/src/crypto/verification-tracker.hpp
@@ -1,83 +1,104 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2021 Tusooa Zhu <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <libkazv-config.hpp>
#include <immer/flex_vector.hpp>
#include <nlohmann/json.hpp>
#include <copy-helper.hpp>
#include <event.hpp>
#include "crypto-util.hpp"
namespace Kazv
{
namespace VerificationTrackerActions
{
struct SendEvent
{
std::string userId;
immer::flex_vector<std::string> deviceIds;
nlohmann::json event;
};
struct DisplayCodes
{
std::string emojiCode;
std::string decimalCode;
};
struct ShowStatus
{
enum Status
{
Cancelled,
Verified,
VerificationFailed
};
std::string userId;
std::string deviceId;
Status status;
};
}
using VerificationTrackerAction =
std::variant<VerificationTrackerActions::SendEvent,
VerificationTrackerActions::DisplayCodes,
VerificationTrackerActions::ShowStatus>;
using VerificationTrackerResult = immer::flex_vector<VerificationTrackerAction>;
class VerificationTracker
{
public:
+ /**
+ * Construct an invalid verification tracker.
+ */
+ VerificationTracker();
+
+ /**
+ * Construct an verification tracker for a specific device.
+ *
+ * @param userId The user id for the current user.
+ * @param deviceId the device id for the current device.
+ */
+ VerificationTracker(std::string userId, std::string deviceId);
+
+ KAZV_DECLARE_COPYABLE(VerificationTracker);
+
+ ~VerificationTracker();
+
/**
* @return The random size needed for process().
*/
static std::size_t processRandomSize(const nlohmann::json &event);
/**
* Process the event.
*
* @param event A json of the m.key.verification.* event.
* @param random Random data needed to process the event.
* @param ts The current timestamp.
*/
VerificationTrackerResult process(const nlohmann::json &event, RandomData random, Timestamp ts);
/**
* Create a new verification request.
*
* @param userId The user id of the user to verify.
* @param deviceIds The device ids of the devices to verify.
* @param ts The current timestamp.
*/
VerificationTrackerResult requestVerification(std::string userId, immer::flex_vector<std::string> deviceIds, Timestamp ts);
+
+ private:
+ struct Private;
+ std::unique_ptr<Private> m_d;
};
}
diff --git a/src/tests/crypto/verification-process-test.cpp b/src/tests/crypto/verification-process-test.cpp
index 2a335c3..dad32d8 100644
--- a/src/tests/crypto/verification-process-test.cpp
+++ b/src/tests/crypto/verification-process-test.cpp
@@ -1,275 +1,384 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2021 Tusooa Zhu <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <catch2/catch.hpp>
+#include <iostream>
+
#include <verification-process.hpp>
#include <verification-tracker.hpp>
using namespace Kazv;
// Taken from https://matrix.org/docs/spec/client_server/r0.6.1
static auto requestEvent = R"({
"content": {
"from_device": "AliceDevice2",
"methods": [
"m.sas.v1"
],
"timestamp": 1559598944869,
"transaction_id": "S0meUniqueAndOpaqueString"
},
"type": "m.key.verification.request"
})"_json;
static auto requestEventUnsupportedMethod = R"({
"content": {
"from_device": "AliceDevice2",
"methods": [
"moe.kazv.mxc.fake-method"
],
"timestamp": 1559598944869,
"transaction_id": "S0meUniqueAndOpaqueString"
},
"type": "m.key.verification.request"
})"_json;
static auto requestEventMultiMethods = R"({
"content": {
"from_device": "AliceDevice2",
"methods": [
"m.sas.v1",
"moe.kazv.mxc.fake-method"
],
"timestamp": 1559598944869,
"transaction_id": "S0meUniqueAndOpaqueString"
},
"type": "m.key.verification.request"
})"_json;
static auto startEvent = R"({
"content": {
"from_device": "BobDevice1",
"method": "m.sas.v1",
"transaction_id": "S0meUniqueAndOpaqueString"
},
"type": "m.key.verification.start"
})"_json;
static auto startEventUnsupportedMethod = R"({
"content": {
"from_device": "BobDevice1",
"method": "moe.kazv.mxc.fake-method",
"transaction_id": "S0meUniqueAndOpaqueString"
},
"type": "m.key.verification.start"
})"_json;
static auto cancelEvent = R"({
"content": {
"code": "m.user",
"reason": "User rejected the key verification request",
"transaction_id": "S0meUniqueAndOpaqueString"
},
"type": "m.key.verification.cancel"
})"_json;
static auto sasStartEvent = R"({
"content": {
"from_device": "BobDevice1",
"hashes": [
"sha256"
],
"key_agreement_protocols": [
"curve25519"
],
"message_authentication_codes": [
"hkdf-hmac-sha256"
],
"method": "m.sas.v1",
"short_authentication_string": [
"decimal",
"emoji"
],
"transaction_id": "S0meUniqueAndOpaqueString"
},
"type": "m.key.verification.start"
})"_json;
static auto sasAcceptEvent = R"({
"content": {
"commitment": "fQpGIW1Snz+pwLZu6sTy2aHy/DYWWTspTJRPyNp0PKkymfIsNffysMl6ObMMFdIJhk6g6pwlIqZ54rxo8SLmAg",
"hash": "sha256",
"key_agreement_protocol": "curve25519",
"message_authentication_code": "hkdf-hmac-sha256",
"method": "m.sas.v1",
"short_authentication_string": [
"decimal",
"emoji"
],
"transaction_id": "S0meUniqueAndOpaqueString"
},
"type": "m.key.verification.accept"
})"_json;
static auto sasKeyEvent = R"({
"content": {
"key": "fQpGIW1Snz+pwLZu6sTy2aHy/DYWWTspTJRPyNp0PKkymfIsNffysMl6ObMMFdIJhk6g6pwlIqZ54rxo8SLmAg",
"transaction_id": "S0meUniqueAndOpaqueString"
},
"type": "m.key.verification.key"
})"_json;
static auto sasMacEvent = R"({
"content": {
"keys": "2Wptgo4CwmLo/Y8B8qinxApKaCkBG2fjTWB7AbP5Uy+aIbygsSdLOFzvdDjww8zUVKCmI02eP9xtyJxc/cLiBA",
"mac": {
"ed25519:ABCDEF": "fQpGIW1Snz+pwLZu6sTy2aHy/DYWWTspTJRPyNp0PKkymfIsNffysMl6ObMMFdIJhk6g6pwlIqZ54rxo8SLmAg"
},
"transaction_id": "S0meUniqueAndOpaqueString"
},
"type": "m.key.verification.mac"
})"_json;
static auto unknownEvent = R"({
"content": {
"transaction_id": "xxx"
},
"type": "moe.kazv.mxc.key.verification.unknown"
})"_json;
static auto malformedEvent = R"({
"type": "moe.kazv.mxc.key.verification.unknown"
})"_json;
static bool displays(const VerificationTrackerAction &action)
{
return
- !std::holds_alternative<VerificationTrackerActions::DisplayCodes>(action)
- && !std::holds_alternative<VerificationTrackerActions::ShowStatus>(action);
+ std::holds_alternative<VerificationTrackerActions::DisplayCodes>(action)
+ || std::holds_alternative<VerificationTrackerActions::ShowStatus>(action);
+}
+
+static bool displaysCode(const VerificationTrackerAction &action)
+{
+ return std::holds_alternative<VerificationTrackerActions::DisplayCodes>(action);
}
static bool displaysNothing(VerificationTrackerResult res)
{
return std::all_of(res.begin(), res.end(), [](const auto &action) {
return !displays(action);
});
}
static bool sendsEventOfType(VerificationTrackerResult res, std::string type)
{
return std::any_of(res.begin(), res.end(), [=](const auto &action) {
return std::holds_alternative<VerificationTrackerActions::SendEvent>(action)
&& std::get<VerificationTrackerActions::SendEvent>(action)
.event
.at("type")
.template get<std::string>() == type;
});
}
static bool sendsCancellation(VerificationTrackerResult res)
{
return sendsEventOfType(res, "m.key.verification.cancel");
}
+static bool sendsRequest(VerificationTrackerResult res)
+{
+ return sendsEventOfType(res, "m.key.verification.request");
+}
+
+static bool sendsStart(VerificationTrackerResult res)
+{
+ return sendsEventOfType(res, "m.key.verification.start");
+}
+
+static bool sendsAccept(VerificationTrackerResult res)
+{
+ return sendsEventOfType(res, "m.key.verification.accept");
+}
+
+static bool sendsKey(VerificationTrackerResult res)
+{
+ return sendsEventOfType(res, "m.key.verification.key");
+}
+
+static bool sendsMac(VerificationTrackerResult res)
+{
+ return sendsEventOfType(res, "m.key.verification.mac");
+}
+
+static nlohmann::json firstEventSent(VerificationTrackerResult res)
+{
+ auto it = std::find_if(res.begin(), res.end(), [](const auto &action) {
+ return std::holds_alternative<VerificationTrackerActions::SendEvent>(action);
+ });
+ return std::get<VerificationTrackerActions::SendEvent>(*it).event;
+}
+
static bool sendsNothing(VerificationTrackerResult res)
{
return std::all_of(res.begin(), res.end(), [](const auto &action) {
return !std::holds_alternative<VerificationTrackerActions::SendEvent>(action);
});
}
// TEST_CASE("Construct sas verification process", "[client][verification-proc]")
// {
// auto proc1 = SASVerificationProcess();
// REQUIRE(!proc1.valid());
// auto proc2 = SASVerificationProcess(RandomTag{}, );
// }
TEST_CASE("VerificationTracker is a value type", "[client][verification-proc]")
{
auto tracker = VerificationTracker{};
auto tracker2 = tracker;
(void) tracker2;
auto tracker3 = std::move(tracker);
(void) tracker3;
}
TEST_CASE("VerificationTracker processRandomSize", "[client][verification-proc]")
{
auto sasConstructRandomSize = SASVerificationProcess::constructRandomSize();
REQUIRE(sasConstructRandomSize != 0);
REQUIRE(VerificationTracker::processRandomSize(requestEvent) == sasConstructRandomSize);
REQUIRE(VerificationTracker::processRandomSize(requestEventUnsupportedMethod) == 0);
REQUIRE(VerificationTracker::processRandomSize(requestEventMultiMethods) == sasConstructRandomSize);
REQUIRE(VerificationTracker::processRandomSize(startEvent) == sasConstructRandomSize);
REQUIRE(VerificationTracker::processRandomSize(sasStartEvent) == sasConstructRandomSize);
REQUIRE(VerificationTracker::processRandomSize(startEventUnsupportedMethod) == 0);
REQUIRE(VerificationTracker::processRandomSize(cancelEvent) == 0);
REQUIRE(VerificationTracker::processRandomSize(sasAcceptEvent) == 0);
REQUIRE(VerificationTracker::processRandomSize(sasKeyEvent) == 0);
REQUIRE(VerificationTracker::processRandomSize(sasMacEvent) == 0);
REQUIRE(VerificationTracker::processRandomSize(unknownEvent) == 0);
REQUIRE(VerificationTracker::processRandomSize(malformedEvent) == 0);
}
TEST_CASE("VerificationTracker process() error handling", "[client][verification-proc]")
{
auto reqRandomSize = VerificationTracker::processRandomSize(requestEvent);
auto random = genRandomData(reqRandomSize);
auto tracker = VerificationTracker{};
auto afterReasonablyShortTime = requestEvent["content"]["timestamp"].template get<Timestamp>() + 1;
WHEN ("processing a request way long ago") {
auto afterTenMins = requestEvent["content"]["timestamp"].template get<Timestamp>() + 10 * 60 * 1000 + 1;
auto res = tracker.process(requestEvent, random, afterTenMins);
THEN ("we should ignore and send cancellation") {
REQUIRE(displaysNothing(res));
REQUIRE(sendsCancellation(res));
}
}
WHEN ("processing a request way in the future") {
auto beforeFiveMins = requestEvent["content"]["timestamp"].template get<Timestamp>() - 5 * 60 * 1000 - 1;
auto res = tracker.process(requestEvent, random, beforeFiveMins);
THEN ("we should ignore and send cancellation") {
REQUIRE(displaysNothing(res));
REQUIRE(sendsCancellation(res));
}
}
WHEN ("processing a non-request, non-cancel event whose transaction id is never encountered") {
auto res = tracker.process(sasAcceptEvent, random, afterReasonablyShortTime);
THEN ("we should ignore and send cancellation") {
REQUIRE(displaysNothing(res));
REQUIRE(sendsCancellation(res));
}
}
WHEN ("processing a cancel event whose transaction id is never encountered") {
auto res = tracker.process(cancelEvent, random, afterReasonablyShortTime);
THEN ("we should ignore only") {
REQUIRE(displaysNothing(res));
REQUIRE(sendsNothing(res));
}
}
}
+
+TEST_CASE("VerificationTracker full process", "[client][verification-proc]")
+{
+ auto alice = VerificationTracker("@alice:example.org", "AliceDevice1");
+ auto bob = VerificationTracker("@bob:example.org", "BobDevice1");
+
+ auto ts = 1000;
+ auto requestRes = alice.requestVerification("@bob:example.org", {"BobDevice1"}, ts);
+
+ THEN ("Alice should send a request event and notify") {
+ REQUIRE(sendsRequest(requestRes));
+ }
+ auto requestEvent = firstEventSent(requestRes);
+
+ auto processRequestRes = bob.process(requestEvent,
+ genRandomData(VerificationTracker::processRandomSize(requestEvent)), ts);
+ THEN ("Bob should notify first and start sas") {
+ REQUIRE(displays(processRequestRes.at(0)));
+ REQUIRE(sendsStart(processRequestRes));
+ }
+ auto startEvent = firstEventSent(processRequestRes);
+
+ auto processStartRes = alice.process(startEvent,
+ genRandomData(VerificationTracker::processRandomSize(startEvent)), ts);
+ THEN ("Alice should notify first and accept sas") {
+ REQUIRE(displays(processStartRes.at(0)));
+ REQUIRE(sendsAccept(processStartRes));
+ }
+ auto acceptEvent = firstEventSent(processStartRes);
+
+ auto processAcceptRes = bob.process(acceptEvent,
+ genRandomData(VerificationTracker::processRandomSize(acceptEvent)), ts);
+ THEN ("Bob should notify and send key") {
+ REQUIRE(!displaysNothing(processAcceptRes));
+ REQUIRE(sendsKey(processAcceptRes));
+ }
+ auto bobKeyEvent = firstEventSent(processAcceptRes);
+
+ auto aliceProcessKeyRes = alice.process(bobKeyEvent,
+ genRandomData(VerificationTracker::processRandomSize(bobKeyEvent)), ts);
+ THEN ("Alice should send key and display code") {
+ REQUIRE(!displaysNothing(aliceProcessKeyRes));
+ REQUIRE(sendsKey(aliceProcessKeyRes));
+ }
+ auto aliceKeyEvent = firstEventSent(aliceProcessKeyRes);
+
+ auto bobProcessKeyRes = bob.process(aliceKeyEvent,
+ genRandomData(VerificationTracker::processRandomSize(aliceKeyEvent)), ts);
+ THEN ("Bob should display code first and send mac") {
+ REQUIRE(displaysCode(bobProcessKeyRes.at(0)));
+ REQUIRE(sendsMac(bobProcessKeyRes));
+ }
+ auto bobMacEvent = firstEventSent(bobProcessKeyRes);
+
+ auto aliceProcessMacRes = alice.process(bobMacEvent,
+ genRandomData(VerificationTracker::processRandomSize(bobMacEvent)), ts);
+ THEN ("Alice should send mac and show success") {
+ REQUIRE(sendsMac(aliceProcessMacRes));
+ REQUIRE(!displaysNothing(aliceProcessMacRes));
+ }
+ auto aliceMacEvent = firstEventSent(aliceProcessMacRes);
+
+ auto bobProcessMacRes = bob.process(aliceMacEvent,
+ genRandomData(VerificationTracker::processRandomSize(aliceMacEvent)), ts);
+ THEN ("Bob should show success") {
+ REQUIRE(sendsNothing(bobProcessMacRes));
+ REQUIRE(!displaysNothing(bobProcessMacRes));
+ }
+}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Sep 19, 6:28 AM (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1769023
Default Alt Text
(20 KB)

Event Timeline