Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85711615
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
20 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
rL libkazv
Attached
Detach File
Event Timeline
Log In to Comment