From 9edfaa910543bf2deaa07948182ef9f11c4260ac Mon Sep 17 00:00:00 2001 From: timoreo Date: Sat, 9 Dec 2023 14:50:39 +0100 Subject: [PATCH] Initial commit --- .clang-format | 20 ++++ .gitignore | 145 +++++++++++++++++++++++++++ .gitmodules | 9 ++ CMakeLists.txt | 35 +++++++ matrix-3ds-sdk | 1 + olm | 1 + quirc | 1 + src/main.cpp | 259 +++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 471 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 160000 matrix-3ds-sdk create mode 160000 olm create mode 160000 quirc create mode 100644 src/main.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..9ae8221 --- /dev/null +++ b/.clang-format @@ -0,0 +1,20 @@ +--- +BasedOnStyle: Chromium +IndentWidth: 4 +AllowShortIfStatementsOnASingleLine: false +ColumnLimit: 140 +--- +Language: Cpp +AlignConsecutiveMacros: None +AlignConsecutiveAssignments: None +BraceWrapping: + AfterFunction: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false + AfterControlStatement: Never +BreakBeforeBraces: Custom +BreakConstructorInitializers: BeforeComma +Cpp11BracedListStyle: false +AllowShortLoopsOnASingleLine: true +AllowShortBlocksOnASingleLine: Empty \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8b65318 --- /dev/null +++ b/.gitignore @@ -0,0 +1,145 @@ +# Created by https://www.toptal.com/developers/gitignore/api/c++,cmake,clion+all +# Edit at https://www.toptal.com/developers/gitignore?templates=c++,cmake,clion+all + +### C++ ### +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +### CLion+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### CLion+all Patch ### +# Ignore everything but code style settings and run configurations +# that are supposed to be shared within teams. + +.idea/* + +!.idea/codeStyles +!.idea/runConfigurations + +### CMake ### +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +### CMake Patch ### +# External projects +*-prefix/ + +# End of https://www.toptal.com/developers/gitignore/api/c++,cmake,clion+all +.idea/ +matrix-security-key.txt \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c0add99 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "quirc"] + path = quirc + url = https://gitea.ahur.ac/timoreo/quirc.git +[submodule "olm"] + path = olm + url = https://gitea.ahur.ac/timoreo/olm.git +[submodule "matrix-3ds-sdk"] + path = matrix-3ds-sdk + url = https://gitea.ahur.ac/timoreo/matrix-3ds-sdk.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..17b5f2d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,35 @@ +set(CMAKE_TOOLCHAIN_FILE "/opt/devkitpro/cmake/3DS.cmake") +cmake_minimum_required(VERSION 3.24) +project(matrix-3ds-client) +# for compiling a part of the pica asm +enable_language(ASM) + +add_subdirectory(matrix-3ds-sdk) +add_subdirectory(olm) +add_executable(matrix-3ds-client src/main.cpp) +find_package(PkgConfig REQUIRED) +pkg_check_modules(jansson REQUIRED IMPORTED_TARGET jansson) +target_link_libraries(matrix-3ds-client curl PkgConfig::jansson matrix-3ds-sdk olm citro2d citro3d) +target_include_directories(matrix-3ds-client PUBLIC src "${DEVKITPRO}/portlibs/3ds/include/" ${CMAKE_CURRENT_BINARY_DIR}/include) + +set(SMDH_AUTHOR timoreo) +set(SMDH_DESCRIPTION "A kinda basic matrix 3ds client") +ctr_create_3dsx(matrix-3ds-client SMDH) +#add_custom_command( +#OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/vshader.shbin" +#COMMAND "${CTR_PICASSO_EXE}" -o "${CMAKE_CURRENT_BINARY_DIR}/vshader.shbin" src/imgui/vshader.v.pica +#DEPENDS src/imgui/vshader.v.pica +#WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +#COMMENT "Building shader library src/imgui/vshader.v.pica" +#VERBATIM +#) +#add_custom_command( +# OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/include/vshader_shbin.h" "${CMAKE_CURRENT_BINARY_DIR}/vshader_shbin.s" +# COMMAND "${DKP_BIN2S}" "${CMAKE_CURRENT_BINARY_DIR}/vshader.shbin" -H "${CMAKE_CURRENT_BINARY_DIR}/include/vshader_shbin.h" > "${CMAKE_CURRENT_BINARY_DIR}/vshader_shbin.s" +# DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/vshader.shbin" +# WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +# COMMENT "Compiling to header src/imgui/vshader.v.pica" +# VERBATIM +#) +#add_custom_target(vshader DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/include/vshader_shbin.h") +#add_dependencies(matrix-3ds-client vshader) diff --git a/matrix-3ds-sdk b/matrix-3ds-sdk new file mode 160000 index 0000000..833de44 --- /dev/null +++ b/matrix-3ds-sdk @@ -0,0 +1 @@ +Subproject commit 833de44d81c7a79bb1327cd829116c1cce3861f6 diff --git a/olm b/olm new file mode 160000 index 0000000..cb3fe62 --- /dev/null +++ b/olm @@ -0,0 +1 @@ +Subproject commit cb3fe622ae742674436e361bdefe534d300ced47 diff --git a/quirc b/quirc new file mode 160000 index 0000000..71c1eeb --- /dev/null +++ b/quirc @@ -0,0 +1 @@ +Subproject commit 71c1eebcb77e7bf37c0a89c0b6ab3b39df2870ee diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..ead75a2 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,259 @@ +#include <3ds.h> +#include +#include +#include +#include +#include +#include +#include + +#define PREFIX_DIRECTORY "./matrix-storage" +#define TOKEN_FILENAME "token" +#define DEVICE_ID "XREVXEDLHU" + +class Store : public Matrix::Store { + void setSyncToken(std::string token) override { + + } + std::string getSyncToken() override { + return ""; + } + void setFilterId(std::string filterId) override {} + std::string getFilterId() override { return ""; } +}; +PrintConsole* bottom = new PrintConsole; +int main() { + fsInit(); + gfxInitDefault(); + consoleInit(GFX_BOTTOM, bottom); + uint8_t major, minor, patch; + olm_get_library_version(&major, &minor, &patch); + std::cout << "Using olm " << major << "." << minor << "." << patch << std::endl; + std::filesystem::create_directories(PREFIX_DIRECTORY); + chdir(PREFIX_DIRECTORY); + Matrix::Client* client = nullptr; + while (aptMainLoop()) { + gspWaitForVBlank(); + hidScanInput(); + u32 kDown = hidKeysDown(); + + if (kDown & KEY_START) { + break; + } + if (kDown & KEY_SELECT) { + std::ifstream stream{TOKEN_FILENAME, std::fstream::in}; + if(!stream.is_open()){ + // File dosn't exist, log in + client = new Matrix::Client("https://matrix.timoreo.fr", "", nullptr); // &s + if (client->login("timoreo-3ds", "***REMOVED***", DEVICE_ID)) { + // logged in + puts("Logged in !!"); + std::ofstream ostr{TOKEN_FILENAME, std::fstream::out}; + ostr << client->getToken() << std::endl; + ostr.close(); + } else { + // wrong endpoint or combo + puts("Wrong combo !"); + goto wrong; + } + }else{ + std::string line; + std::getline(stream, line); + client = new Matrix::Client("https://matrix.timoreo.fr", line, nullptr); // &s + stream.close(); + } + auto userid = client->getUserId(); + if(userid.empty()){ + puts("Userid is empty ! Wrong token ?"); + goto wrong; + } + // Load encryption here + + client->setRoomInfoCallback([](std::string roomId, Matrix::RoomInfo info){ + consoleSelect(bottom); + printf("Joined room %s named %s\n", roomId.c_str(), info.name.c_str()); + }); + client->setEventCallback([](std::string roomId, json_t* event){ + consoleSelect(bottom); + printf("Event in room %s\n", roomId.c_str()); + printf("%s\n", json_dumps(event, JSON_ENSURE_ASCII)); + }); + client->startSyncLoop(); + } + wrong: + ; + } + if(client != nullptr){ + client->stopSyncLoop(); + // DO NOT LOGOUT, KEEP ENCRYPTION KEYS + } + gfxExit(); + fsExit(); +} +void generate_otk(OlmAccount* acc, size_t otkcount) { + size_t len = olm_account_generate_one_time_keys_random_length(acc, otkcount); + std::unique_ptr otkrandom = std::make_unique(len); + PS_GenerateRandomBytes(otkrandom.get(), len); + olm_account_generate_one_time_keys(acc, otkcount, otkrandom.get(), len); +} +void generate_otk(OlmAccount* acc){ + size_t otkcount = olm_account_max_number_of_one_time_keys(acc)/2; + generate_otk(acc, otkcount); +} + + +void generate_device_key(OlmAccount* acc) { + size_t len = olm_create_account_random_length(acc); + std::unique_ptr random = std::make_unique(len); + PS_GenerateRandomBytes(random.get(), len); + olm_create_account(acc, random.get(), len); +} + +void generate_fallback_key(OlmAccount* acc) { + size_t len = olm_account_generate_fallback_key_random_length(acc); + std::unique_ptr random = std::make_unique(len); + PS_GenerateRandomBytes(random.get(), len); + olm_account_generate_fallback_key(acc, random.get(), len); +} + +// Make sure to remove any unsigned and signatures block before doing this ! +void sign_json(Matrix::Client& client, OlmAccount* acc, json_t* json) { + char* jsonStr = json_dumps(json, JSON_COMPACT | JSON_SORT_KEYS); + size_t signlen = olm_account_signature_length(acc); + char* signature = reinterpret_cast(malloc(signlen)); + olm_account_sign(acc, jsonStr, strlen(jsonStr), signature, signlen); + free(jsonStr); + json_t * signitem = json_object(); + json_object_set(signitem, ("ed25519:" + client.getDeviceId()).c_str(), json_stringn(signature, signlen)); + json_t * signobj = json_object(); + json_object_set(signobj, client.getUserId().c_str(), signitem); + json_object_set(json, "signatures", signobj); +} +json_t* get_device_keys(Matrix::Client& client, OlmAccount* acc) { + json_t * device_keys = json_object(); + json_t * algorithms = json_array(); + json_array_append_new(algorithms, json_string("m.olm.v1.curve25519-aes-sha2")); + json_array_append_new(algorithms, json_string("m.megolm.v1.aes-sha2")); + json_object_set_new(device_keys, "algorithms", algorithms); + json_object_set_new(device_keys, "device_id", json_string(client.getDeviceId().c_str())); + json_object_set_new(device_keys, "user_id", json_string(client.getUserId().c_str())); + + // Extract keys + size_t acclen = olm_account_identity_keys_length(acc); + auto acckeys = reinterpret_cast(malloc(acclen)); + olm_account_identity_keys(acc, acckeys, acclen); + json_t * keys = json_loads(acckeys, 0, nullptr); + free(acckeys); + + // Merge keys + json_object_set_new(device_keys, ("ed25519:" + client.getDeviceId()).c_str(), json_object_get(keys, "ed25519")); + json_object_set_new(device_keys, ("curve25519:" + client.getDeviceId()).c_str(), json_object_get(keys, "curve25519")); + json_decref(keys); + return device_keys; +} +json_t* get_fallback_keys(Matrix::Client& client, OlmAccount* acc) { + json_t * fallback_keys = json_object(); + // Extract keys + size_t acclen = olm_account_unpublished_fallback_key_length(acc); + auto acckeys = reinterpret_cast(malloc(acclen)); + olm_account_unpublished_fallback_key(acc, acckeys, acclen); + json_t * keys = json_loads(acckeys, 0, nullptr); + free(acckeys); + json_t* keyobj = json_object_get(keys, "curve25519"); + void* iter = json_object_iter(keyobj); + + while (iter != nullptr) { + const char* keyid = json_object_iter_key(iter); + json_t* keyval = json_object_iter_value(iter); + json_t* tosign = json_object(); + json_object_set_new(tosign, "key", keyval); + // object is fallback key + json_object_set_new(tosign, "fallback", json_true()); + + sign_json(client, acc, tosign); + json_object_set_new(fallback_keys, (std::string("signed_curve25519:") + keyid).c_str(), tosign ); + iter = json_object_iter_next(keyobj, iter); + } + json_decref(keys); + return fallback_keys; +} +OlmAccount* load_account(Matrix::Client client){ + auto* acc = static_cast(malloc(olm_account_size())); + olm_account(acc); + std::filebuf filestorage; + + // std::FILE* file = std::fopen("secret-keys", "r"); + std::ifstream ifs("secret-keys", std::ios::binary|std::ios::ate); + if (ifs.is_open()) { + // Generate and save to file + // Device keys + generate_device_key(acc); + // One time keys + generate_otk(acc); + // Fallback keys + generate_fallback_key(acc); + + // Convert to pickled format + size_t pacclen = olm_pickle_account_length(acc); + + auto m = reinterpret_cast(malloc(pacclen)); + + olm_pickle_account(acc, nullptr, 0, m, pacclen); + + pacclen = _olm_decode_base64(m, pacclen, m); + FILE* file = std::fopen("secret-keys", "w"); + std::fwrite(m, pacclen, 1, file); + + free(m); + std::fclose(file); + // Upload the keys --- + // Build device key json + json_t * upload_keys = json_object(); + + json_t* device_keys = get_device_keys(client, acc); + // Sign keys block + sign_json(client, acc, device_keys); + json_object_set(upload_keys, "device_keys", device_keys); + + json_t* fallback_keys = get_fallback_keys(client, acc); + // fallback keys are already signed + json_object_set(upload_keys, "fallback_keys", fallback_keys); + json_decref(upload_keys); + + } else { + // Load from file + std::ifstream::pos_type fsize = ifs.tellg(); + if(fsize == -1){ + std::cout << "What." << std::endl; + return acc; + } + size_t overhead = fsize/4; + auto data = reinterpret_cast(malloc(overhead + fsize)); + ifs.seekg(0, std::ios::beg); + ifs.read(reinterpret_cast(overhead + data), static_cast(fsize)); + _olm_encode_base64(overhead + data, fsize, data); + olm_unpickle_account(acc, nullptr, 0, data, overhead + fsize); + } + return acc; +} + +void make_keys(OlmAccount* acc){ + + // Generate Device Keys + size_t keylen = olm_account_identity_keys_length(acc); + //ssize_t datalen = _olm_encode_base64_length(keylen); + std::unique_ptr identity_keys = std::make_unique(keylen); + olm_account_identity_keys(acc, identity_keys.get(), keylen); + // _olm_decode_base64(identity_keys.get(), datalen, identity_keys.get()); + // Save keys to disk + std::ofstream storage("account-keys"); + storage << identity_keys.get() << std::endl; + storage.close(); + + + + + + + return acc; +} \ No newline at end of file