Initial commit

This commit is contained in:
timoreo 2023-12-09 14:50:39 +01:00
commit 9edfaa9105
Signed by: timoreo
GPG key ID: 121A72C3512BA288
8 changed files with 471 additions and 0 deletions

20
.clang-format Normal file
View file

@ -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

145
.gitignore vendored Normal file
View file

@ -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

9
.gitmodules vendored Normal file
View file

@ -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

35
CMakeLists.txt Normal file
View file

@ -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)

1
matrix-3ds-sdk Submodule

@ -0,0 +1 @@
Subproject commit 833de44d81c7a79bb1327cd829116c1cce3861f6

1
olm Submodule

@ -0,0 +1 @@
Subproject commit cb3fe622ae742674436e361bdefe534d300ced47

1
quirc Submodule

@ -0,0 +1 @@
Subproject commit 71c1eebcb77e7bf37c0a89c0b6ab3b39df2870ee

259
src/main.cpp Normal file
View file

@ -0,0 +1,259 @@
#include <3ds.h>
#include <matrixclient.h>
#include <olm/base64.h>
#include <olm/olm.h>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iostream>
#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<char> otkrandom = std::make_unique<char>(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<char> random = std::make_unique<char>(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<char> random = std::make_unique<char>(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<char*>(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<char *>(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<char *>(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<OlmAccount*>(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<uint8_t *>(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<uint8_t*>(malloc(overhead + fsize));
ifs.seekg(0, std::ios::beg);
ifs.read(reinterpret_cast<char*>(overhead + data), static_cast<std::streamsize>(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<uint8_t> identity_keys = std::make_unique<uint8_t>(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;
}